def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ settings = Settings() settings.load() # Check for updates def update_available(update_url, installed_version, latest_version): Alert( strings._("update_available", True).format(update_url, installed_version, latest_version)) def update_not_available(): Alert(strings._('update_not_available', True)) u = UpdateChecker() u.update_available.connect(update_available) u.update_not_available.connect(update_not_available) # Show Tor connection status if connection type is bundled tor if settings.get('connection_type') == 'bundled': self.tor_status.show() self._disable_buttons() u.tor_status_update.connect(self._tor_status_update) try: u.check(force=True) except UpdateCheckerTorError: Alert(strings._('update_error_tor', True), QtWidgets.QMessageBox.Warning) except UpdateCheckerSOCKSHTTPError: Alert(strings._('update_error_sockshttp', True), QtWidgets.QMessageBox.Warning) except UpdateCheckerInvalidLatestVersion as e: Alert( strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) # Clean up afterwards if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() # Update the last checked label settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp)
def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() # Check for updates def update_available(update_url, installed_version, latest_version): Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) def update_not_available(): Alert(strings._('update_not_available', True)) u = UpdateChecker(self.onion) u.update_available.connect(update_available) u.update_not_available.connect(update_not_available) try: u.check(force=True) except UpdateCheckerCheckError: Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) except UpdateCheckerInvalidLatestVersion as e: Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) # Enable buttons self._enable_buttons() # Update the last checked label settings = Settings(self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp)
class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ settings_saved = QtCore.pyqtSignal() def __init__(self, common): super(SettingsDialog, self).__init__() self.common = common self.common.log("SettingsDialog", "__init__") self.setModal(True) self.setWindowTitle(strings._("gui_settings_window_title")) self.setWindowIcon( QtGui.QIcon(self.common.get_resource_path("images/logo.png"))) self.system = platform.system() # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog self.hide_tor_settings = os.environ.get( "ONIONSHARE_HIDE_TOR_SETTINGS") == "1" # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText( strings._("gui_settings_autoupdate_option")) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton( strings._("gui_settings_autoupdate_check_button")) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox( strings._("gui_settings_autoupdate_label")) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if self.system != "Windows" and self.system != "Darwin": autoupdate_group.hide() # Language settings language_label = QtWidgets.QLabel( strings._("gui_settings_language_label")) self.language_combobox = QtWidgets.QComboBox() # Populate the dropdown with all of OnionShare's available languages language_names_to_locales = { v: k for k, v in self.common.settings.available_locales.items() } language_names = list(language_names_to_locales) language_names.sort() for language_name in language_names: locale = language_names_to_locales[language_name] self.language_combobox.addItem(language_name, QtCore.QVariant(locale)) language_layout = QtWidgets.QHBoxLayout() language_layout.addWidget(language_label) language_layout.addWidget(self.language_combobox) language_layout.addStretch() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_bundled_option")) self.connection_type_bundled_radio.toggled.connect( self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (self.system == "Windows" or self.system == "Darwin") and getattr( sys, "onionshare_dev_mode", False): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_no_bridges_radio_option")) self.tor_bridges_no_bridges_radio.toggled.connect( self.tor_bridges_no_bridges_radio_toggled) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports ( self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, ) = self.common.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings. _("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy")) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_obfs4_radio_option")) self.tor_bridges_use_obfs4_radio.toggled.connect( self.tor_bridges_use_obfs4_radio_toggled) # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports ( self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, ) = self.common.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path): self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( strings. _("gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" )) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( strings._( "gui_settings_tor_bridges_meek_lite_azure_radio_option")) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( self.tor_bridges_use_meek_lite_azure_radio_toggled) # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_custom_radio_option")) self.tor_bridges_use_custom_radio.toggled.connect( self.tor_bridges_use_custom_radio_toggled) self.tor_bridges_use_custom_label = QtWidgets.QLabel( strings._("gui_settings_tor_bridges_custom_label")) self.tor_bridges_use_custom_label.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) self.tor_bridges_use_custom_textbox.setPlaceholderText( "[address:port] [identifier]") tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_label) tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_textbox) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() self.tor_bridges_use_custom_textbox_options.setLayout( tor_bridges_use_custom_textbox_options_layout) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_automatic_option")) self.connection_type_automatic_radio.toggled.connect( self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_control_port_option")) self.connection_type_control_port_radio.toggled.connect( self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel( strings._("gui_settings_control_port_label")) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit( ) self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget( connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout( connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_socket_file_option")) self.connection_type_socket_file_radio.toggled.connect( self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel( strings._("gui_settings_socket_file_label")) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget( connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget( self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout( connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel( strings._("gui_settings_socks_label")) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget( self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton( strings._("gui_settings_authenticate_no_auth_option")) self.authenticate_no_auth_radio.toggled.connect( self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton( strings._("gui_settings_authenticate_password_option")) self.authenticate_password_radio.toggled.connect( self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel( strings._("gui_settings_password_label")) self.authenticate_password_extras_password = QtWidgets.QLineEdit("") authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget( authenticate_password_extras_label) authenticate_password_extras_layout.addWidget( self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout( authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox( strings._("gui_settings_authenticate_label")) self.authenticate_group.setLayout(authenticate_group_layout) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget( self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget( self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget( self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget( self.connection_type_socket_file_radio) connection_type_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_connection_type_label")) connection_type_radio_group.setLayout( connection_type_radio_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_tor_bridges")) self.connection_type_bridges_radio_group.setLayout( connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton( strings._("gui_settings_connection_type_test_button")) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget( self.connection_type_test_button) connection_type_test_button_layout.addStretch() # Connection type layout connection_type_layout = QtWidgets.QVBoxLayout() connection_type_layout.addWidget( self.connection_type_control_port_extras) connection_type_layout.addWidget( self.connection_type_socket_file_extras) connection_type_layout.addWidget(self.connection_type_socks) connection_type_layout.addWidget(self.authenticate_group) connection_type_layout.addWidget( self.connection_type_bridges_radio_group) connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons self.save_button = QtWidgets.QPushButton( strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton( strings._("gui_settings_button_cancel")) self.cancel_button.clicked.connect(self.cancel_clicked) version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") version_label.setStyleSheet(self.common.gui.css["settings_version"]) self.help_button = QtWidgets.QPushButton( strings._("gui_settings_button_help")) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet( self.common.gui.css["settings_tor_status"]) self.tor_status.hide() # Layout tor_layout = QtWidgets.QVBoxLayout() tor_layout.addWidget(connection_type_radio_group) tor_layout.addLayout(connection_type_layout) tor_layout.addWidget(self.tor_status) tor_layout.addStretch() layout = QtWidgets.QVBoxLayout() if not self.hide_tor_settings: layout.addLayout(tor_layout) layout.addSpacing(20) layout.addWidget(autoupdate_group) if autoupdate_group.isVisible(): layout.addSpacing(20) layout.addLayout(language_layout) layout.addSpacing(20) layout.addStretch() layout.addLayout(buttons_layout) self.setLayout(layout) self.cancel_button.setFocus() self.reload_settings() def reload_settings(self): # Load settings, and fill them in self.old_settings = Settings(self.common) self.old_settings.load() use_autoupdate = self.old_settings.get("use_autoupdate") if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) autoupdate_timestamp = self.old_settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) locale = self.old_settings.get("locale") locale_index = self.language_combobox.findData(QtCore.QVariant(locale)) self.language_combobox.setCurrentIndex(locale_index) connection_type = self.old_settings.get("connection_type") if connection_type == "bundled": if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == "automatic": self.connection_type_automatic_radio.setChecked(True) elif connection_type == "control_port": self.connection_type_control_port_radio.setChecked(True) elif connection_type == "socket_file": self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText( self.old_settings.get("control_port_address")) self.connection_type_control_port_extras_port.setText( str(self.old_settings.get("control_port_port"))) self.connection_type_socket_file_extras_path.setText( self.old_settings.get("socket_file_path")) self.connection_type_socks_address.setText( self.old_settings.get("socks_address")) self.connection_type_socks_port.setText( str(self.old_settings.get("socks_port"))) auth_type = self.old_settings.get("auth_type") if auth_type == "no_auth": self.authenticate_no_auth_radio.setChecked(True) elif auth_type == "password": self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText( self.old_settings.get("auth_password")) if self.old_settings.get("no_bridges"): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) self.tor_bridges_use_obfs4_radio.setChecked( self.old_settings.get("tor_bridges_use_obfs4")) self.tor_bridges_use_meek_lite_azure_radio.setChecked( self.old_settings.get("tor_bridges_use_meek_lite_azure")) if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] bridges = self.old_settings.get( "tor_bridges_use_custom_bridges").split("Bridge ") for bridge in bridges: new_bridges.append(bridge) new_bridges = "".join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ self.common.log("SettingsDialog", "connection_type_bundled_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() def tor_bridges_no_bridges_radio_toggled(self, checked): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): Alert( self.common, strings._("gui_settings_meek_lite_expensive_warning"), QtWidgets.QMessageBox.Warning, ) def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ self.common.log("SettingsDialog", "connection_type_automatic_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ self.common.log("SettingsDialog", "connection_type_control_port_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ self.common.log("SettingsDialog", "connection_type_socket_file_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ self.common.log("SettingsDialog", "authenticate_no_auth_toggled") def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ self.common.log("SettingsDialog", "authenticate_password_toggled") if checked: self.authenticate_password_extras.show() else: self.authenticate_password_extras.hide() def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ self.common.log("SettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor if settings.get("connection_type") == "bundled": self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True else: tor_status_update_func = None onion = Onion(self.common, use_tmp_dir=True) onion.connect( custom_settings=settings, tor_status_update_func=tor_status_update_func, ) # If an exception hasn't been raised yet, the Tor settings work Alert( self.common, strings._("settings_test_success").format( onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_v3_onions, ), ) # Clean up onion.cleanup() except ( TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout, ) as e: Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ self.common.log("SettingsDialog", "check_for_updates") # Disable buttons self._disable_buttons() self.common.gui.qtapp.processEvents() def update_timestamp(): # Update the last checked label settings = Settings(self.common) settings.load() autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): forced_update_thread.quit() # Enable buttons self._enable_buttons() # Update timestamp update_timestamp() # Check for updates def update_available(update_url, installed_version, latest_version): Alert( self.common, strings._("update_available").format(update_url, installed_version, latest_version), ) close_forced_update_thread() def update_not_available(): Alert(self.common, strings._("update_not_available")) close_forced_update_thread() def update_error(): Alert( self.common, strings._("update_error_check_error"), QtWidgets.QMessageBox.Warning, ) close_forced_update_thread() def update_invalid_version(latest_version): Alert( self.common, strings._("update_error_invalid_latest_version").format( latest_version), QtWidgets.QMessageBox.Warning, ) close_forced_update_thread() forced_update_thread = UpdateThread(self.common, self.onion, force=True) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) forced_update_thread.update_invalid_version.connect( update_invalid_version) forced_update_thread.start() def save_clicked(self): """ Save button clicked. Save current settings to disk. """ self.common.log("SettingsDialog", "save_clicked") def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values have changed for the given keys. """ for key in keys: if s1.get(key) != s2.get(key): return True return False settings = self.settings_from_fields() if settings: # If language changed, inform user they need to restart OnionShare if changed(settings, self.old_settings, ["locale"]): # Look up error message in different locale new_locale = settings.get("locale") if (new_locale in strings.translations and "gui_settings_language_changed_notice" in strings.translations[new_locale]): notice = strings.translations[new_locale][ "gui_settings_language_changed_notice"] else: notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if not self.common.gui.local_only: if self.common.gui.onion.is_authenticated(): self.common.log("SettingsDialog", "save_clicked", "Connected to Tor") if changed( settings, self.old_settings, [ "connection_type", "control_port_address", "control_port_port", "socks_address", "socks_port", "socket_file_path", "auth_type", "auth_password", "no_bridges", "tor_bridges_use_obfs4", "tor_bridges_use_meek_lite_azure", "tor_bridges_use_custom_bridges", ], ): reboot_onion = True else: self.common.log("SettingsDialog", "save_clicked", "Not connected to Tor") # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object self.common.log("SettingsDialog", "save_clicked", "rebooting the Onion") self.common.gui.onion.cleanup() tor_con = TorConnectionDialog(self.common, settings) tor_con.start() self.common.log( "SettingsDialog", "save_clicked", f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}", ) if (self.common.gui.onion.is_authenticated() and not tor_con.wasCanceled()): self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() def cancel_clicked(self): """ Cancel button clicked. """ self.common.log("SettingsDialog", "cancel_clicked") if (not self.common.gui.local_only and not self.common.gui.onion.is_authenticated()): Alert( self.common, strings._("gui_tor_connection_canceled"), QtWidgets.QMessageBox.Warning, ) sys.exit() else: self.close() def help_clicked(self): """ Help button clicked. """ self.common.log("SettingsDialog", "help_clicked") SettingsDialog.open_help() @staticmethod def open_help(): help_url = "https://docs.onionshare.org/" QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ self.common.log("SettingsDialog", "settings_from_fields") settings = Settings(self.common) settings.load() # To get the last update timestamp # Language locale_index = self.language_combobox.currentIndex() locale = self.language_combobox.itemData(locale_index) settings.set("locale", locale) # Tor connection if self.connection_type_bundled_radio.isChecked(): settings.set("connection_type", "bundled") if self.connection_type_automatic_radio.isChecked(): settings.set("connection_type", "automatic") if self.connection_type_control_port_radio.isChecked(): settings.set("connection_type", "control_port") if self.connection_type_socket_file_radio.isChecked(): settings.set("connection_type", "socket_file") if self.autoupdate_checkbox.isChecked(): settings.set("use_autoupdate", True) else: settings.set("use_autoupdate", False) settings.set( "control_port_address", self.connection_type_control_port_extras_address.text(), ) settings.set("control_port_port", self.connection_type_control_port_extras_port.text()) settings.set("socket_file_path", self.connection_type_socket_file_extras_path.text()) settings.set("socks_address", self.connection_type_socks_address.text()) settings.set("socks_port", self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): settings.set("auth_type", "no_auth") if self.authenticate_password_radio.isChecked(): settings.set("auth_type", "password") settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): settings.set("no_bridges", True) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", True) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", True) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] bridges = self.tor_bridges_use_custom_textbox.toPlainText().split( "\n") bridges_valid = False for bridge in bridges: if bridge != "": # Check the syntax of the custom bridge to make sure it looks legitimate ipv4_pattern = re.compile( "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" ) ipv6_pattern = re.compile( "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" ) meek_lite_pattern = re.compile( "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" ) if (ipv4_pattern.match(bridge) or ipv6_pattern.match(bridge) or meek_lite_pattern.match(bridge)): new_bridges.append("".join(["Bridge ", bridge, "\n"])) bridges_valid = True if bridges_valid: new_bridges = "".join(new_bridges) settings.set("tor_bridges_use_custom_bridges", new_bridges) else: Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) settings.set("no_bridges", True) return False return settings def closeEvent(self, e): self.common.log("SettingsDialog", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.common.gui.local_only: if not self.common.gui.onion.is_authenticated(): self.common.log("SettingsDialog", "closeEvent", "Closing while not connected to Tor") # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): self.common.log("SettingsDialog", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime("%B %d, %Y %H:%M") else: last_checked = strings._("gui_settings_autoupdate_timestamp_never") self.autoupdate_timestamp.setText( strings._("gui_settings_autoupdate_timestamp").format( last_checked)) def _tor_status_update(self, progress, summary): self.tor_status.setText( f"<strong>{strings._('connecting_to_tor')}</strong><br>{progress}% {summary}" ) self.common.gui.qtapp.processEvents() if "Done" in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): self.common.log("SettingsDialog", "_disable_buttons") self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): self.common.log("SettingsDialog", "_enable_buttons") # We can't check for updates if we're still not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True)
def __init__(self, parent=None): super(SettingsDialog, self).__init__(parent) self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) # Connection type: either automatic, control port, or socket file # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True)) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True)) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Connection type layout connection_type_group_layout = QtWidgets.QVBoxLayout() connection_type_group_layout.addWidget(self.connection_type_automatic_radio) connection_type_group_layout.addWidget(self.connection_type_control_port_radio) connection_type_group_layout.addWidget(self.connection_type_socket_file_radio) connection_type_group_layout.addWidget(self.connection_type_control_port_extras) connection_type_group_layout.addWidget(self.connection_type_socket_file_extras) connection_type_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True)) connection_type_group.setLayout(connection_type_group_layout) # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True)) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) # Buttons test_button = QtWidgets.QPushButton(strings._('gui_settings_button_test', True)) test_button.clicked.connect(self.test_clicked) save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) save_button.clicked.connect(self.save_clicked) cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) cancel_button.clicked.connect(self.cancel_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(test_button) buttons_layout.addWidget(save_button) buttons_layout.addWidget(cancel_button) # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(connection_type_group) layout.addWidget(self.authenticate_group) layout.addStretch() layout.addLayout(buttons_layout) self.setLayout(layout) # Load settings, and fill them in settings = Settings() settings.load() connection_type = settings.get('connection_type') if connection_type == 'automatic': self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'control_port': self.connection_type_control_port_radio.setChecked(True) elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText(settings.get('control_port_address')) self.connection_type_control_port_extras_port.setText(str(settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText(settings.get('socket_file_path')) auth_type = settings.get('auth_type') if auth_type == 'no_auth': self.authenticate_no_auth_radio.setChecked(True) elif auth_type == 'password': self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText(settings.get('auth_password')) # Show the dialog self.exec_()
def check(self, force=False, config=False): self.common.log('UpdateChecker', 'check', 'force={}'.format(force)) # Load the settings settings = Settings(self.common, config) settings.load() # If force=True, then definitely check if force: check_for_updates = True else: check_for_updates = False # See if it's been 1 day since the last check autoupdate_timestamp = settings.get('autoupdate_timestamp') if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp( autoupdate_timestamp) now = datetime.datetime.now() one_day = datetime.timedelta(days=1) if now - last_checked > one_day: check_for_updates = True else: check_for_updates = True # Check for updates if check_for_updates: self.common.log('UpdateChecker', 'check', 'checking for updates') # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform user_agent = 'OnionShare {}, {}'.format( self.common.version, self.common.platform) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users path = '/latest-version.txt' if force: path += '?force=1' if Version(self.onion.tor_version) >= Version('0.3.2.9'): onion_domain = 'lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion' else: onion_domain = 'elx57ue5uyfplgva.onion' self.common.log( 'UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() s.settimeout(15) # 15 second timeout s.connect((onion_domain, 80)) http_request = 'GET {} HTTP/1.0\r\n'.format(path) http_request += 'Host: {}\r\n'.format(onion_domain) http_request += 'User-Agent: {}\r\n'.format(user_agent) http_request += '\r\n' s.sendall(http_request.encode('utf-8')) http_response = s.recv(1024) latest_version = http_response[ http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') self.common.log( 'UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) except Exception as e: self.common.log('UpdateChecker', 'check', '{}'.format(e)) self.update_error.emit() raise UpdateCheckerCheckError # Validate that latest_version looks like a version string # This regex is: 1-3 dot-separated numeric components version_re = r"^(\d+\.)?(\d+\.)?(\d+)$" if not re.match(version_re, latest_version): self.update_invalid_version.emit(latest_version) raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) timestamp = datetime.datetime.now().replace(microsecond=0).replace( second=0).timestamp() # Re-load the settings first before saving, just in case they've changed since we started our thread settings.load() settings.set('autoupdate_timestamp', timestamp) settings.save() # Do we need to update? update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format( latest_version) installed_version = self.common.version if installed_version < latest_version: self.update_available.emit(update_url, installed_version, latest_version) return # No updates are available self.update_not_available.emit()
class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ settings_saved = QtCore.pyqtSignal() def __init__(self, onion, qtapp, config=False): super(SettingsDialog, self).__init__() common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp self.config = config self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) system = platform.system() # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True)) # Whether or not to show systray notifications self.systray_notifications_checkbox = QtWidgets.QCheckBox() self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True)) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.systray_notifications_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) # Stealth options # Stealth stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) stealth_details.setWordWrap(True) self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) # Stealth options layout stealth_group_layout = QtWidgets.QVBoxLayout() stealth_group_layout.addWidget(stealth_details) stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) stealth_group.setLayout(stealth_group_layout) # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option", True)) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button', True)) self.check_for_updates_button.clicked.connect(self.check_for_updates) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label", True)) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if system != 'Windows' and system != 'Darwin': autoupdate_group.hide() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (system == 'Windows' or system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): self.connection_type_bundled_radio.setEnabled(False) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True)) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True)) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label', True)) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget(self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True)) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True)) connection_type_radio_group.setLayout(connection_type_radio_group_layout) # Connection type layout connection_type_group_layout = QtWidgets.QVBoxLayout() connection_type_group_layout.addWidget(self.connection_type_control_port_extras) connection_type_group_layout.addWidget(self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) connection_type_group_layout.addWidget(self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox() connection_type_group.setLayout(connection_type_group_layout) # Buttons self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet('background-color: #ffffff; color: #000000; padding: 10px') self.tor_status.hide() # Layout left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() col_layout = QtWidgets.QHBoxLayout() col_layout.addLayout(left_col_layout) col_layout.addLayout(right_col_layout) layout = QtWidgets.QVBoxLayout() layout.addLayout(col_layout) layout.addLayout(buttons_layout) self.setLayout(layout) self.cancel_button.setFocus() # Load settings, and fill them in self.old_settings = Settings(self.config) self.old_settings.load() close_after_first_download = self.old_settings.get('close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) systray_notifications = self.old_settings.get('systray_notifications') if systray_notifications: self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) else: self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked) use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) connection_type = self.old_settings.get('connection_type') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'automatic': self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'control_port': self.connection_type_control_port_radio.setChecked(True) elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText(self.old_settings.get('control_port_address')) self.connection_type_control_port_extras_port.setText(str(self.old_settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText(self.old_settings.get('socket_file_path')) self.connection_type_socks_address.setText(self.old_settings.get('socks_address')) self.connection_type_socks_port.setText(str(self.old_settings.get('socks_port'))) auth_type = self.old_settings.get('auth_type') if auth_type == 'no_auth': self.authenticate_no_auth_radio.setChecked(True) elif auth_type == 'password': self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_bundled_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_automatic_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_control_port_toggled') if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_socket_file_toggled') if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ common.log('SettingsDialog', 'authenticate_no_auth_toggled') def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() else: self.authenticate_password_extras.hide() def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ common.log('SettingsDialog', 'test_tor_clicked') settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor if settings.get('connection_type') == 'bundled': self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True else: tor_status_update_func = None onion = Onion() onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) # If an exception hasn't been raised yet, the Tor settings work Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) # Clean up onion.cleanup() except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: Alert(e.args[0], QtWidgets.QMessageBox.Warning) if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() # Check for updates def update_available(update_url, installed_version, latest_version): Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) def update_not_available(): Alert(strings._('update_not_available', True)) u = UpdateChecker(self.onion) u.update_available.connect(update_available) u.update_not_available.connect(update_not_available) try: u.check(force=True) except UpdateCheckerCheckError: Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) except UpdateCheckerInvalidLatestVersion as e: Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) # Enable buttons self._enable_buttons() # Update the last checked label settings = Settings(self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) def save_clicked(self): """ Save button clicked. Save current settings to disk. """ common.log('SettingsDialog', 'save_clicked') settings = self.settings_from_fields() settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if self.onion.connected_to_tor: def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values have changed for the given keys. """ for key in keys: if s1.get(key) != s2.get(key): return True return False if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', 'control_port_port', 'socks_address', 'socks_port', 'socket_file_path', 'auth_type', 'auth_password']): reboot_onion = True else: # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) tor_con.start() common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) if self.onion.connected_to_tor and not tor_con.wasCanceled(): self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() def cancel_clicked(self): """ Cancel button clicked. """ common.log('SettingsDialog', 'cancel_clicked') if not self.onion.connected_to_tor: Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() def help_clicked(self): """ Help button clicked. """ common.log('SettingsDialog', 'help_clicked') help_site = 'https://github.com/micahflee/onionshare/wiki' QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ common.log('SettingsDialog', 'settings_from_fields') settings = Settings(self.config) settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) if self.connection_type_bundled_radio.isChecked(): settings.set('connection_type', 'bundled') if self.connection_type_automatic_radio.isChecked(): settings.set('connection_type', 'automatic') if self.connection_type_control_port_radio.isChecked(): settings.set('connection_type', 'control_port') if self.connection_type_socket_file_radio.isChecked(): settings.set('connection_type', 'socket_file') settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) settings.set('socks_address', self.connection_type_socks_address.text()) settings.set('socks_port', self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): settings.set('auth_type', 'no_auth') if self.authenticate_password_radio.isChecked(): settings.set('auth_type', 'password') settings.set('auth_password', self.authenticate_password_extras_password.text()) return settings def closeEvent(self, e): common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether if not self.onion.connected_to_tor: common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): common.log('SettingsDialog', '_update_autoupdate_timestamp') if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime('%B %d, %Y %H:%M') else: last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) def _tor_status_update(self, progress, summary): self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor', True), progress, summary)) self.qtapp.processEvents() if 'Done' in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): common.log('SettingsDialog', '_disable_buttons') self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): common.log('SettingsDialog', '_enable_buttons') self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True)
class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ settings_saved = QtCore.pyqtSignal() def __init__(self, onion, qtapp, config=False): super(SettingsDialog, self).__init__() common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp self.config = config self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.system = platform.system() # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True)) # Whether or not to show systray notifications self.systray_notifications_checkbox = QtWidgets.QCheckBox() self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True)) # Whether or not to use a shutdown timer self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) # Whether or not to save the Onion private key for reuse self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.systray_notifications_checkbox) sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) sharing_group_layout.addWidget(self.save_private_key_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) # Stealth options # Stealth stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) stealth_details.setWordWrap(True) stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) stealth_details.setOpenExternalLinks(True) stealth_details.setMinimumSize(stealth_details.sizeHint()) self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) hidservauth_details.hide() self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) self.hidservauth_copy_button.hide() # Stealth options layout stealth_group_layout = QtWidgets.QVBoxLayout() stealth_group_layout.addWidget(stealth_details) stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group_layout.addWidget(hidservauth_details) stealth_group_layout.addWidget(self.hidservauth_copy_button) stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) stealth_group.setLayout(stealth_group_layout) # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option", True)) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button', True)) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label", True)) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if self.system != 'Windows' and self.system != 'Darwin': autoupdate_group.hide() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option', True)) self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) # meek_lite-amazon option radio # if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False) else: self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option', True)) self.tor_bridges_use_meek_lite_amazon_radio.toggled.connect(self.tor_bridges_use_meek_lite_amazon_radio_toggled) # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option', True)) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser if self.system == 'Windows' or self.system == 'Darwin': self.tor_bridges_use_meek_lite_amazon_radio.hide() self.tor_bridges_use_meek_lite_azure_radio.hide() # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option', True)) self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled) self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label', True)) self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]') tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_label) tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_textbox) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() self.tor_bridges_use_custom_textbox_options.setLayout(tor_bridges_use_custom_textbox_options_layout) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_amazon_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True)) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True)) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label', True)) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget(self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True)) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True)) connection_type_radio_group.setLayout(connection_type_radio_group_layout) # Connection type layout connection_type_group_layout = QtWidgets.QVBoxLayout() connection_type_group_layout.addWidget(self.connection_type_control_port_extras) connection_type_group_layout.addWidget(self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) connection_type_group_layout.addWidget(self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox() connection_type_group.setLayout(connection_type_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() # Buttons self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version())) version_label.setStyleSheet('color: #666666') self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet('background-color: #ffffff; color: #000000; padding: 10px') self.tor_status.hide() # Layout left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(self.connection_type_bridges_radio_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() col_layout = QtWidgets.QHBoxLayout() col_layout.addLayout(left_col_layout) col_layout.addLayout(right_col_layout) layout = QtWidgets.QVBoxLayout() layout.addLayout(col_layout) layout.addLayout(buttons_layout) self.setLayout(layout) self.cancel_button.setFocus() # Load settings, and fill them in self.old_settings = Settings(self.config) self.old_settings.load() close_after_first_download = self.old_settings.get('close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) systray_notifications = self.old_settings.get('systray_notifications') if systray_notifications: self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) else: self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked) shutdown_timeout = self.old_settings.get('shutdown_timeout') if shutdown_timeout: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) else: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) if save_private_key: hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) connection_type = self.old_settings.get('connection_type') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'automatic': self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'control_port': self.connection_type_control_port_radio.setChecked(True) elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText(self.old_settings.get('control_port_address')) self.connection_type_control_port_extras_port.setText(str(self.old_settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText(self.old_settings.get('socket_file_path')) self.connection_type_socks_address.setText(self.old_settings.get('socks_address')) self.connection_type_socks_port.setText(str(self.old_settings.get('socks_port'))) auth_type = self.old_settings.get('auth_type') if auth_type == 'no_auth': self.authenticate_no_auth_radio.setChecked(True) elif auth_type == 'password': self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) if self.old_settings.get('no_bridges'): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_amazon_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4')) self.tor_bridges_use_meek_lite_amazon_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_amazon')) self.tor_bridges_use_meek_lite_azure_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_azure')) if self.old_settings.get('tor_bridges_use_custom_bridges'): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] bridges = self.old_settings.get('tor_bridges_use_custom_bridges').split('Bridge ') for bridge in bridges: new_bridges.append(bridge) new_bridges = ''.join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_bundled_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() def tor_bridges_no_bridges_radio_toggled(self, checked): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_meek_lite_amazon_radio_toggled(self, checked): """ meek_lite-amazon bridges option was toggled. If checked, disable custom bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_automatic_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_control_port_toggled') if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_socket_file_toggled') if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ common.log('SettingsDialog', 'authenticate_no_auth_toggled') def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() else: self.authenticate_password_extras.hide() def hidservauth_copy_button_clicked(self): """ Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ common.log('SettingsDialog', 'test_tor_clicked') settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor if settings.get('connection_type') == 'bundled': self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True else: tor_status_update_func = None onion = Onion() onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) # If an exception hasn't been raised yet, the Tor settings work Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) # Clean up onion.cleanup() except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: Alert(e.args[0], QtWidgets.QMessageBox.Warning) if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() def update_timestamp(): # Update the last checked label settings = Settings(self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): forced_update_thread.quit() # Enable buttons self._enable_buttons() # Update timestamp update_timestamp() # Check for updates def update_available(update_url, installed_version, latest_version): Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) close_forced_update_thread() def update_not_available(): Alert(strings._('update_not_available', True)) close_forced_update_thread() def update_error(): Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) close_forced_update_thread() def update_invalid_version(): Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) close_forced_update_thread() forced_update_thread = UpdateThread(self.onion, self.config, force=True) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) forced_update_thread.update_invalid_version.connect(update_invalid_version) forced_update_thread.start() def save_clicked(self): """ Save button clicked. Save current settings to disk. """ common.log('SettingsDialog', 'save_clicked') settings = self.settings_from_fields() if settings: settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if self.onion.is_authenticated(): common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values have changed for the given keys. """ for key in keys: if s1.get(key) != s2.get(key): return True return False if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', 'control_port_port', 'socks_address', 'socks_port', 'socket_file_path', 'auth_type', 'auth_password', 'no_bridges', 'tor_bridges_use_obfs4', 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure', 'tor_bridges_use_custom_bridges']): reboot_onion = True else: common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) tor_con.start() common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() def cancel_clicked(self): """ Cancel button clicked. """ common.log('SettingsDialog', 'cancel_clicked') if not self.onion.is_authenticated(): Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() def help_clicked(self): """ Help button clicked. """ common.log('SettingsDialog', 'help_clicked') help_site = 'https://github.com/micahflee/onionshare/wiki' QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ common.log('SettingsDialog', 'settings_from_fields') settings = Settings(self.config) settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked()) settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked()) if self.save_private_key_checkbox.isChecked(): settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) else: settings.set('save_private_key', False) settings.set('private_key', '') settings.set('slug', '') # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): settings.set('hidservauth_string', '') if self.connection_type_bundled_radio.isChecked(): settings.set('connection_type', 'bundled') if self.connection_type_automatic_radio.isChecked(): settings.set('connection_type', 'automatic') if self.connection_type_control_port_radio.isChecked(): settings.set('connection_type', 'control_port') if self.connection_type_socket_file_radio.isChecked(): settings.set('connection_type', 'socket_file') if self.autoupdate_checkbox.isChecked(): settings.set('use_autoupdate', True) else: settings.set('use_autoupdate', False) settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) settings.set('socks_address', self.connection_type_socks_address.text()) settings.set('socks_port', self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): settings.set('auth_type', 'no_auth') if self.authenticate_password_radio.isChecked(): settings.set('auth_type', 'password') settings.set('auth_password', self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): settings.set('no_bridges', True) settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', True) settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_meek_lite_amazon_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_meek_lite_amazon', True) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', True) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_custom_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] bridges = self.tor_bridges_use_custom_textbox.toPlainText().split('\n') bridges_valid = False for bridge in bridges: if bridge != '': # Check the syntax of the custom bridge to make sure it looks legitimate ipv4_pattern = re.compile("(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$") ipv6_pattern = re.compile("(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$") meek_lite_pattern = re.compile("(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)") if ipv4_pattern.match(bridge) or \ ipv6_pattern.match(bridge): new_bridges.append(''.join(['Bridge ', bridge, '\n'])) bridges_valid = True if self.system != 'Windows' and self.system != 'Darwin' and meek_lite_pattern.match(bridge): new_bridges.append(''.join(['Bridge ', bridge, '\n'])) bridges_valid = True if bridges_valid: new_bridges = ''.join(new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges) else: Alert(strings._('gui_settings_tor_bridges_invalid', True)) settings.set('no_bridges', True) return False return settings def closeEvent(self, e): common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether if not self.onion.is_authenticated(): common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): common.log('SettingsDialog', '_update_autoupdate_timestamp') if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime('%B %d, %Y %H:%M') else: last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) def _tor_status_update(self, progress, summary): self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor', True), progress, summary)) self.qtapp.processEvents() if 'Done' in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): common.log('SettingsDialog', '_disable_buttons') self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): common.log('SettingsDialog', '_enable_buttons') # We can't check for updates if we're still not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True)
class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) def __init__(self, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() self._initSystemTray() common.log('OnionShareGui', '__init__') self.onion = onion self.qtapp = qtapp self.app = app self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setMinimumWidth(430) # Load settings self.config = config self.settings = Settings(self.config) self.settings.load() # File selection self.file_selection = FileSelection() if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.update_server_status_indicator) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_stopped.connect(self.update_server_status_indicator) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.cancel_server) self.server_status.server_canceled.connect(self.file_selection.server_stopped) self.server_status.server_canceled.connect(self.update_primary_action) self.start_server_finished.connect(self.clear_message) self.start_server_finished.connect(self.server_status.start_server_finished) self.start_server_finished.connect(self.update_server_status_indicator) self.stop_server_finished.connect(self.server_status.stop_server_finished) self.stop_server_finished.connect(self.update_server_status_indicator) self.file_selection.file_list.files_updated.connect(self.server_status.update) self.file_selection.file_list.files_updated.connect(self.update_primary_action) self.server_status.url_copied.connect(self.copy_url) self.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) self.server_status.button_clicked.connect(self.clear_message) # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') self.filesize_warning.hide() # Downloads self.downloads = Downloads() self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) self.downloads_container.setMaximumHeight(200) self.downloads_container.setMinimumHeight(75) self.vbar = self.downloads_container.verticalScrollBar() self.downloads_container.hide() # downloads start out hidden self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 # Info label along top of screen self.info_layout = QtWidgets.QHBoxLayout() self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.info_in_progress_downloads_count = QtWidgets.QLabel() self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.info_completed_downloads_count = QtWidgets.QLabel() self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.update_downloads_completed(self.downloads_in_progress) self.update_downloads_in_progress(self.downloads_in_progress) self.info_layout.addWidget(self.info_label) self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) self.info_widget.hide() # Settings button on the status bar self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFlat(True) self.settings_button.setFixedWidth(40) self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) ) self.settings_button.clicked.connect(self.open_settings) # Server status indicator on the status bar self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png')) self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png')) self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png')) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) self.server_status_label = QtWidgets.QLabel() self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget(self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) self.server_status_indicator = QtWidgets.QWidget() self.server_status_indicator.setLayout(server_status_indicator_layout) self.update_server_status_indicator() # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) statusBar_cssStyleData =""" QStatusBar { font-style: italic; color: #666666; } QStatusBar::item { border: 0px; }""" self.status_bar.setStyleSheet(statusBar_cssStyleData) self.status_bar.addPermanentWidget(self.server_status_indicator) self.status_bar.addPermanentWidget(self.settings_button) self.setStatusBar(self.status_bar) # Status bar, zip progress bar self._zip_progress_bar = None # Status bar, sharing messages self.server_share_status_label = QtWidgets.QLabel('') self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') self.status_bar.insertWidget(0, self.server_share_status_label) # Primary action layout primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout.addWidget(self.server_status) primary_action_layout.addWidget(self.filesize_warning) primary_action_layout.addWidget(self.downloads_container) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(primary_action_layout) self.primary_action.hide() self.update_primary_action() # Main layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.info_widget) self.layout.addLayout(self.file_selection) self.layout.addWidget(self.primary_action) central_widget = QtWidgets.QWidget() central_widget.setLayout(self.layout) self.setCentralWidget(central_widget) self.show() # Always start with focus on file selection self.file_selection.setFocus() # The server isn't active yet self.set_server_active(False) # Create the timer self.timer = QtCore.QTimer() self.timer.timeout.connect(self.check_for_requests) # Start the "Connecting to Tor" dialog, which calls onion.connect() tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() # Start the timer self.timer.start(500) # After connecting to Tor, check for updates self.check_for_updates() def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() if file_count > 0: self.primary_action.show() self.info_widget.show() # Update the file count in the info label total_size_bytes = 0 for index in range(self.file_selection.file_list.count()): item = self.file_selection.file_list.item(index) total_size_bytes += item.size_bytes total_size_readable = common.human_readable_filesize(total_size_bytes) if file_count > 1: self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) else: self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) else: self.primary_action.hide() self.info_widget.hide() # Resize window self.adjustSize() def update_server_status_indicator(self): common.log('OnionShareGui', 'update_server_status_indicator') # Set the status image if self.server_status.status == self.server_status.STATUS_STOPPED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) elif self.server_status.status == self.server_status.STATUS_WORKING: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) self.server_status_label.setText(strings._('gui_status_indicator_working', True)) elif self.server_status.status == self.server_status.STATUS_STARTED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) self.server_status_label.setText(strings._('gui_status_indicator_started', True)) def _initSystemTray(self): system = common.get_platform() menu = QtWidgets.QMenu() self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) self.helpAction = menu.addAction(strings._('gui_settings_button_help', True)) self.helpAction.triggered.connect(SettingsDialog.help_clicked) self.exitAction = menu.addAction(strings._('systray_menu_exit', True)) self.exitAction.triggered.connect(self.close) self.systemTray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale if system == 'Darwin': self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png'))) else: self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.systemTray.setContextMenu(menu) self.systemTray.show() def _tor_connection_canceled(self): """ If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ common.log('OnionShareGui', '_tor_connection_canceled') def ask(): a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) a.exec_() if a.clickedButton() == settings_button: # Open settings common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() if a.clickedButton() == quit_button: # Quit common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) # Wait 100ms before asking QtCore.QTimer.singleShot(100, ask) def _tor_connection_open_settings(self): """ The TorConnectionDialog wants to open the Settings dialog """ common.log('OnionShareGui', '_tor_connection_open_settings') # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) def open_settings(self): """ Open the SettingsDialog. """ common.log('OnionShareGui', 'open_settings') def reload_settings(): common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.settings.load() # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. if self.onion.is_authenticated(): if not self.timer.isActive(): self.timer.start(500) # If there were some files listed for sharing, we should be ok to # re-enable the 'Start Sharing' button now. if self.server_status.file_selection.get_num_files() > 0: self.server_status.server_button.setEnabled(True) self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() d = SettingsDialog(self.onion, self.qtapp, self.config) d.settings_saved.connect(reload_settings) d.exec_() # When settings close, refresh the server status UI self.server_status.update() def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ common.log('OnionShareGui', 'start_server') self.set_server_active(True) self.app.set_stealth(self.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared self.downloads_container.hide() self.downloads.reset_downloads() self.reset_info_counters() self.status_bar.clearMessage() self.server_share_status_label.setText('') # Reset web counters web.download_count = 0 web.error404_count = 0 web.set_gui_mode() # start the onion service in a new thread def start_onion_service(self): try: self.app.start_onion_service() self.starting_server_step2.emit() except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: self.starting_server_error.emit(e.args[0]) return self.app.stay_open = not self.settings.get('close_after_first_download') # start onionshare http service in new thread t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug'))) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) common.log('OnionshareGui', 'start_server', 'Starting an onion thread') self.t = OnionThread(function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() def start_server_step2(self): """ Step 2 in starting the onionshare server. Zipping up files. """ common.log('OnionShareGui', 'start_server_step2') # add progress bar to the status bar, indicating the crunching of files. self._zip_progress_bar = ZipProgressBar(0) self.filenames = [] for index in range(self.file_selection.file_list.count()): self.filenames.append(self.file_selection.file_list.item(index).filename) self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(self.filenames) self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread def finish_starting_server(self): # prepare files to share def _set_processed_size(x): if self._zip_progress_bar != None: self._zip_progress_bar.update_processed_size_signal.emit(x) try: web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) self.app.cleanup_filenames.append(web.zip_filename) self.starting_server_step3.emit() # done self.start_server_finished.emit() except OSError as e: self.starting_server_error.emit(e.strerror) return t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) t.daemon = True t.start() def start_server_step3(self): """ Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ common.log('OnionShareGui', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None # warn about sending large files over Tor if web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() if self.settings.get('shutdown_timeout'): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: self.app.shutdown_timer = common.close_after_seconds(self.timeout) self.app.shutdown_timer.start() # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: self.stop_server() self.start_server_error(strings._('gui_server_started_after_timeout')) def start_server_error(self, error): """ If there's an error when trying to start the onion service """ common.log('OnionShareGui', 'start_server_error') self.set_server_active(False) Alert(error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None self.status_bar.clearMessage() def cancel_server(self): """ Cancel the server while it is preparing to start """ if self.t: self.t.quit() self.stop_server() def stop_server(self): """ Stop the onionshare server. """ common.log('OnionShareGui', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: web.stop(self.app.port) except: # Probably we had no port to begin with (Onion service didn't start) pass self.app.cleanup() # Remove ephemeral service, but don't disconnect from Tor self.onion.cleanup(stop_tor=False) self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 self.update_downloads_in_progress(0) self.file_selection.file_list.adjustSize() self.set_server_active(False) self.stop_server_finished.emit() def check_for_updates(self): """ Check for updates in a new thread, if enabled. """ system = common.get_platform() if system == 'Windows' or system == 'Darwin': if self.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) self.update_thread = UpdateThread(self.onion, self.config) self.update_thread.update_available.connect(update_available) self.update_thread.start() @staticmethod def _compute_total_size(filenames): total_size = 0 for filename in filenames: if os.path.isfile(filename): total_size += os.path.getsize(filename) if os.path.isdir(filename): total_size += common.dir_size(filename) return total_size def check_for_requests(self): """ Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() if self.server_status.status != self.server_status.STATUS_STOPPED: self.server_status.stop_server() self.server_status.server_button.setEnabled(False) self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: self.vbar.setValue(self.vbar.maximum()) self.new_download = False events = [] done = False while not done: try: r = web.q.get(False) events.append(r) except web.queue.Empty: done = True for event in events: if event["type"] == web.REQUEST_LOAD: self.status_bar.showMessage(strings._('download_page_loaded', True)) elif event["type"] == web.REQUEST_DOWNLOAD: self.downloads_container.show() # show the downloads layout self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) elif event["type"] == web.REQUEST_RATE_LIMIT: self.stop_server() Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) # is the download complete? if event["data"]["bytes"] == web.zip_filesize: if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info self.downloads_completed += 1 self.update_downloads_completed(self.downloads_completed) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) # close on finish? if not web.get_stay_open(): self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) self.downloads_in_progress = 0 self.update_downloads_in_progress(self.downloads_in_progress) elif event["type"] == web.REQUEST_CANCELED: self.downloads.cancel_download(event["data"]["id"]) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: if self.app.shutdown_timer and self.settings.get('shutdown_timeout'): if self.timeout > 0: now = QtCore.QDateTime.currentDateTime() seconds_remaining = now.secsTo(self.server_status.timeout) self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) if not self.app.shutdown_timer.is_alive(): # If there were no attempts to download the share, or all downloads are done, we can stop if web.download_count == 0 or web.done: self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('close_on_timeout', True)) # A download is probably still running - hold off on stopping the share else: self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_url') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_hidservauth') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) def clear_message(self): """ Clear messages from the status bar. """ self.status_bar.clearMessage() def set_server_active(self, active): """ Disable the Settings button while an OnionShare server is active. """ if active: self.settings_button.hide() else: self.settings_button.show() # Disable settings menu action when server is active self.settingsAction.setEnabled(not active) def reset_info_counters(self): """ Set the info counters back to zero. """ self.update_downloads_completed(0) self.update_downloads_in_progress(0) def update_downloads_completed(self, count): """ Update the 'Downloads completed' info widget. """ if count == 0: self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png') else: self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png') self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count)) self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) def update_downloads_in_progress(self, count): """ Update the 'Downloads in progress' info widget. """ if count == 0: self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png') else: self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png') self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) def closeEvent(self, e): common.log('OnionShareGui', 'closeEvent') try: if self.server_status.status != self.server_status.STATUS_STOPPED: common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() dialog.setWindowTitle(strings._('gui_quit_title', True)) dialog.setText(strings._('gui_quit_warning', True)) dialog.setIcon(QtWidgets.QMessageBox.Critical) quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() # Quit if reply == 0: self.stop_server() e.accept() # Don't Quit else: e.ignore() except: e.accept()
def __init__(self, qtapp, app, filenames): super(OnionShareGui, self).__init__() self.qtapp = qtapp self.app = app self.setWindowTitle('OnionShare') self.setWindowIcon( QtGui.QIcon(helpers.get_resource_path('images/logo.png'))) # Menu bar self.setMenuBar(Menu(self.qtapp)) # Check for updates in a new thread, if enabled system = platform.system() if system == 'Windows' or system == 'Darwin': settings = Settings() settings.load() if settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): Alert( strings._("update_available", True).format(update_url, installed_version, latest_version)) t = UpdateThread() t.update_available.connect(update_available) t.start() # File selection self.file_selection = FileSelection() if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection) self.server_status.server_started.connect( self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect( self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) self.start_server_finished.connect(self.clear_message) self.start_server_finished.connect( self.server_status.start_server_finished) self.stop_server_finished.connect( self.server_status.stop_server_finished) self.file_selection.file_list.files_updated.connect( self.server_status.update) self.server_status.url_copied.connect(self.copy_url) self.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setStyleSheet( 'padding: 10px 0; font-weight: bold; color: #333333;') self.filesize_warning.hide() # Downloads self.downloads = Downloads() self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) self.downloads_container.setMaximumHeight(200) self.vbar = self.downloads_container.verticalScrollBar() self.downloads_container.hide() # downloads start out hidden self.new_download = False # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) version_label = QtWidgets.QLabel('v{0:s}'.format( helpers.get_version())) version_label.setStyleSheet('color: #666666; padding: 0 10px;') self.status_bar.addPermanentWidget(version_label) self.setStatusBar(self.status_bar) # Status bar, zip progress bar self._zip_progress_bar = None # Main layout self.layout = QtWidgets.QVBoxLayout() self.layout.addLayout(self.file_selection) self.layout.addLayout(self.server_status) self.layout.addWidget(self.filesize_warning) self.layout.addWidget(self.downloads_container) central_widget = QtWidgets.QWidget() central_widget.setLayout(self.layout) self.setCentralWidget(central_widget) self.show() # check for requests frequently self.timer = QtCore.QTimer() self.timer.timeout.connect(self.check_for_requests) self.timer.start(500)
def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ # First, load settings and configure settings = Settings() settings.load() self.app.set_stealth(settings.get('use_stealth')) web.set_stay_open(not settings.get('close_after_first_download')) # Reset web counters web.download_count = 0 web.error404_count = 0 web.set_gui_mode() # pick an available local port for the http service to listen on self.app.choose_port() # start onionshare http service in new thread t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.app.transparent_torification)) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) # start the onion service in a new thread def start_onion_service(self): try: # Show Tor connection status if connection type is bundled tor if settings.get('connection_type') == 'bundled': def bundled_tor_func(message): self.status_bar.showMessage(message) if 'Done' in message: self.status_bar.showMessage( strings._('gui_starting_server1', True)) else: self.status_bar.showMessage( strings._('gui_starting_server1', True)) bundled_tor_func = None self.app.start_onion_service(bundled_tor_func) self.starting_server_step2.emit() except (onionshare.onion.TorTooOld, onionshare.onion.TorErrorInvalidSetting, onionshare.onion.TorErrorAutomatic, onionshare.onion.TorErrorSocketPort, onionshare.onion.TorErrorSocketFile, onionshare.onion.TorErrorMissingPassword, onionshare.onion.TorErrorUnreadableCookieFile, onionshare.onion.TorErrorAuthError, onionshare.onion.TorErrorProtocolError, onionshare.onion.BundledTorTimeout) as e: self.starting_server_error.emit(e.args[0]) return t = threading.Thread(target=start_onion_service, kwargs={'self': self}) t.daemon = True t.start()
def check(self, force=False, config=False): common.log('UpdateChecker', 'check', 'force={}'.format(force)) # Load the settings settings = Settings(config) settings.load() # If force=True, then definitely check if force: check_for_updates = True else: check_for_updates = False # See if it's been 1 day since the last check autoupdate_timestamp = settings.get('autoupdate_timestamp') if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp) now = datetime.datetime.now() one_day = datetime.timedelta(days=1) if now - last_checked > one_day: check_for_updates = True else: check_for_updates = True # Check for updates if check_for_updates: common.log('UpdateChecker', 'check', 'checking for updates') # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform user_agent = 'OnionShare {}, {}'.format(common.get_version(), platform.system()) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users path = '/latest-version.txt' if force: path += '?force=1' onion_domain = 'elx57ue5uyfplgva.onion' common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() s.settimeout(15) # 15 second timeout s.connect((onion_domain, 80)) http_request = 'GET {} HTTP/1.0\r\n'.format(path) http_request += 'Host: {}\r\n'.format(onion_domain) http_request += 'User-Agent: {}\r\n'.format(user_agent) http_request += '\r\n' s.sendall(http_request.encode('utf-8')) http_response = s.recv(1024) latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) except Exception as e: common.log('UpdateChecker', 'check', '{}'.format(e)) raise UpdateCheckerCheckError # Validate that latest_version looks like a version string # This regex is: 1-3 dot-separated numeric components version_re = r"^(\d+\.)?(\d+\.)?(\d+)$" if not re.match(version_re, latest_version): raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp() settings.set('autoupdate_timestamp', timestamp) settings.save() # Do we need to update? update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version) installed_version = common.get_version() if installed_version < latest_version: self.update_available.emit(update_url, installed_version, latest_version) return # No updates are available self.update_not_available.emit()
def update_timestamp(): # Update the last checked label settings = Settings(self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp)
class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) def __init__(self, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() self._initSystemTray() common.log('OnionShareGui', '__init__') self.onion = onion self.qtapp = qtapp self.app = app self.setWindowTitle('OnionShare') self.setWindowIcon( QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setMinimumWidth(430) # Load settings self.config = config self.settings = Settings(self.config) self.settings.load() # File selection self.file_selection = FileSelection() if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings) self.server_status.server_started.connect( self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect( self.update_server_status_indicator) self.server_status.server_stopped.connect( self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_stopped.connect( self.update_server_status_indicator) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.cancel_server) self.server_status.server_canceled.connect( self.file_selection.server_stopped) self.server_status.server_canceled.connect(self.update_primary_action) self.start_server_finished.connect(self.clear_message) self.start_server_finished.connect( self.server_status.start_server_finished) self.start_server_finished.connect(self.update_server_status_indicator) self.stop_server_finished.connect( self.server_status.stop_server_finished) self.stop_server_finished.connect(self.update_server_status_indicator) self.file_selection.file_list.files_updated.connect( self.server_status.update) self.file_selection.file_list.files_updated.connect( self.update_primary_action) self.server_status.url_copied.connect(self.copy_url) self.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) self.server_status.button_clicked.connect(self.clear_message) # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) self.filesize_warning.setStyleSheet( 'padding: 10px 0; font-weight: bold; color: #333333;') self.filesize_warning.hide() # Downloads self.downloads = Downloads() self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) self.downloads_container.setMaximumHeight(200) self.downloads_container.setMinimumHeight(75) self.vbar = self.downloads_container.verticalScrollBar() self.downloads_container.hide() # downloads start out hidden self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 # Info label along top of screen self.info_layout = QtWidgets.QHBoxLayout() self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet( 'QLabel { font-size: 12px; color: #666666; }') self.info_in_progress_downloads_count = QtWidgets.QLabel() self.info_in_progress_downloads_count.setStyleSheet( 'QLabel { font-size: 12px; color: #666666; }') self.info_completed_downloads_count = QtWidgets.QLabel() self.info_completed_downloads_count.setStyleSheet( 'QLabel { font-size: 12px; color: #666666; }') self.update_downloads_completed(self.downloads_in_progress) self.update_downloads_in_progress(self.downloads_in_progress) self.info_layout.addWidget(self.info_label) self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) self.info_widget.hide() # Settings button on the status bar self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFlat(True) self.settings_button.setFixedWidth(40) self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png'))) self.settings_button.clicked.connect(self.open_settings) # Server status indicator on the status bar self.server_status_image_stopped = QtGui.QImage( common.get_resource_path('images/server_stopped.png')) self.server_status_image_working = QtGui.QImage( common.get_resource_path('images/server_working.png')) self.server_status_image_started = QtGui.QImage( common.get_resource_path('images/server_started.png')) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) self.server_status_label = QtWidgets.QLabel() self.server_status_label.setStyleSheet( 'QLabel { font-style: italic; color: #666666; }') server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget( self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) self.server_status_indicator = QtWidgets.QWidget() self.server_status_indicator.setLayout(server_status_indicator_layout) self.update_server_status_indicator() # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) statusBar_cssStyleData = """ QStatusBar { font-style: italic; color: #666666; } QStatusBar::item { border: 0px; }""" self.status_bar.setStyleSheet(statusBar_cssStyleData) self.status_bar.addPermanentWidget(self.server_status_indicator) self.status_bar.addPermanentWidget(self.settings_button) self.setStatusBar(self.status_bar) # Status bar, zip progress bar self._zip_progress_bar = None # Status bar, sharing messages self.server_share_status_label = QtWidgets.QLabel('') self.server_share_status_label.setStyleSheet( 'QLabel { font-style: italic; color: #666666; padding: 2px; }') self.status_bar.insertWidget(0, self.server_share_status_label) # Primary action layout primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout.addWidget(self.server_status) primary_action_layout.addWidget(self.filesize_warning) primary_action_layout.addWidget(self.downloads_container) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(primary_action_layout) self.primary_action.hide() self.update_primary_action() # Main layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.info_widget) self.layout.addLayout(self.file_selection) self.layout.addWidget(self.primary_action) central_widget = QtWidgets.QWidget() central_widget.setLayout(self.layout) self.setCentralWidget(central_widget) self.show() # Always start with focus on file selection self.file_selection.setFocus() # The server isn't active yet self.set_server_active(False) # Create the timer self.timer = QtCore.QTimer() self.timer.timeout.connect(self.check_for_requests) # Start the "Connecting to Tor" dialog, which calls onion.connect() tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() # Start the timer self.timer.start(500) # After connecting to Tor, check for updates self.check_for_updates() def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() if file_count > 0: self.primary_action.show() self.info_widget.show() # Update the file count in the info label total_size_bytes = 0 for index in range(self.file_selection.file_list.count()): item = self.file_selection.file_list.item(index) total_size_bytes += item.size_bytes total_size_readable = common.human_readable_filesize( total_size_bytes) if file_count > 1: self.info_label.setText( strings._('gui_file_info', True).format(file_count, total_size_readable)) else: self.info_label.setText( strings._('gui_file_info_single', True).format(file_count, total_size_readable)) else: self.primary_action.hide() self.info_widget.hide() # Resize window self.adjustSize() def update_server_status_indicator(self): common.log('OnionShareGui', 'update_server_status_indicator') # Set the status image if self.server_status.status == self.server_status.STATUS_STOPPED: self.server_status_image_label.setPixmap( QtGui.QPixmap.fromImage(self.server_status_image_stopped)) self.server_status_label.setText( strings._('gui_status_indicator_stopped', True)) elif self.server_status.status == self.server_status.STATUS_WORKING: self.server_status_image_label.setPixmap( QtGui.QPixmap.fromImage(self.server_status_image_working)) self.server_status_label.setText( strings._('gui_status_indicator_working', True)) elif self.server_status.status == self.server_status.STATUS_STARTED: self.server_status_image_label.setPixmap( QtGui.QPixmap.fromImage(self.server_status_image_started)) self.server_status_label.setText( strings._('gui_status_indicator_started', True)) def _initSystemTray(self): system = common.get_platform() menu = QtWidgets.QMenu() self.settingsAction = menu.addAction( strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) self.helpAction = menu.addAction( strings._('gui_settings_button_help', True)) self.helpAction.triggered.connect(SettingsDialog.help_clicked) self.exitAction = menu.addAction(strings._('systray_menu_exit', True)) self.exitAction.triggered.connect(self.close) self.systemTray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale if system == 'Darwin': self.systemTray.setIcon( QtGui.QIcon( common.get_resource_path('images/logo_grayscale.png'))) else: self.systemTray.setIcon( QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.systemTray.setContextMenu(menu) self.systemTray.show() def _tor_connection_canceled(self): """ If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ common.log('OnionShareGui', '_tor_connection_canceled') def ask(): a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) settings_button = QtWidgets.QPushButton( strings._('gui_tor_connection_ask_open_settings', True)) quit_button = QtWidgets.QPushButton( strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) a.exec_() if a.clickedButton() == settings_button: # Open settings common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() if a.clickedButton() == quit_button: # Quit common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) # Wait 100ms before asking QtCore.QTimer.singleShot(100, ask) def _tor_connection_open_settings(self): """ The TorConnectionDialog wants to open the Settings dialog """ common.log('OnionShareGui', '_tor_connection_open_settings') # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) def open_settings(self): """ Open the SettingsDialog. """ common.log('OnionShareGui', 'open_settings') def reload_settings(): common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.settings.load() # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. if self.onion.is_authenticated(): if not self.timer.isActive(): self.timer.start(500) # If there were some files listed for sharing, we should be ok to # re-enable the 'Start Sharing' button now. if self.server_status.file_selection.get_num_files() > 0: self.server_status.server_button.setEnabled(True) self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() d = SettingsDialog(self.onion, self.qtapp, self.config) d.settings_saved.connect(reload_settings) d.exec_() # When settings close, refresh the server status UI self.server_status.update() def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ common.log('OnionShareGui', 'start_server') self.set_server_active(True) self.app.set_stealth(self.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared self.downloads_container.hide() self.downloads.reset_downloads() self.reset_info_counters() self.status_bar.clearMessage() self.server_share_status_label.setText('') # Reset web counters web.download_count = 0 web.error404_count = 0 web.set_gui_mode() # start the onion service in a new thread def start_onion_service(self): try: self.app.start_onion_service() self.starting_server_step2.emit() except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: self.starting_server_error.emit(e.args[0]) return self.app.stay_open = not self.settings.get( 'close_after_first_download') # start onionshare http service in new thread t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug'))) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) common.log('OnionshareGui', 'start_server', 'Starting an onion thread') self.t = OnionThread(function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() def start_server_step2(self): """ Step 2 in starting the onionshare server. Zipping up files. """ common.log('OnionShareGui', 'start_server_step2') # add progress bar to the status bar, indicating the crunching of files. self._zip_progress_bar = ZipProgressBar(0) self.filenames = [] for index in range(self.file_selection.file_list.count()): self.filenames.append( self.file_selection.file_list.item(index).filename) self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size( self.filenames) self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread def finish_starting_server(self): # prepare files to share def _set_processed_size(x): if self._zip_progress_bar != None: self._zip_progress_bar.update_processed_size_signal.emit(x) try: web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) self.app.cleanup_filenames.append(web.zip_filename) self.starting_server_step3.emit() # done self.start_server_finished.emit() except OSError as e: self.starting_server_error.emit(e.strerror) return t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) t.daemon = True t.start() def start_server_step3(self): """ Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ common.log('OnionShareGui', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None # warn about sending large files over Tor if web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() if self.settings.get('shutdown_timeout'): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: self.app.shutdown_timer = common.close_after_seconds( self.timeout) self.app.shutdown_timer.start() # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: self.stop_server() self.start_server_error( strings._('gui_server_started_after_timeout')) def start_server_error(self, error): """ If there's an error when trying to start the onion service """ common.log('OnionShareGui', 'start_server_error') self.set_server_active(False) Alert(error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None self.status_bar.clearMessage() def cancel_server(self): """ Cancel the server while it is preparing to start """ if self.t: self.t.quit() self.stop_server() def stop_server(self): """ Stop the onionshare server. """ common.log('OnionShareGui', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: web.stop(self.app.port) except: # Probably we had no port to begin with (Onion service didn't start) pass self.app.cleanup() # Remove ephemeral service, but don't disconnect from Tor self.onion.cleanup(stop_tor=False) self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 self.update_downloads_in_progress(0) self.file_selection.file_list.adjustSize() self.set_server_active(False) self.stop_server_finished.emit() def check_for_updates(self): """ Check for updates in a new thread, if enabled. """ system = common.get_platform() if system == 'Windows' or system == 'Darwin': if self.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): Alert( strings._("update_available", True).format(update_url, installed_version, latest_version)) self.update_thread = UpdateThread(self.onion, self.config) self.update_thread.update_available.connect(update_available) self.update_thread.start() @staticmethod def _compute_total_size(filenames): total_size = 0 for filename in filenames: if os.path.isfile(filename): total_size += os.path.getsize(filename) if os.path.isdir(filename): total_size += common.dir_size(filename) return total_size def check_for_requests(self): """ Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() if self.server_status.status != self.server_status.STATUS_STOPPED: self.server_status.stop_server() self.server_status.server_button.setEnabled(False) self.status_bar.showMessage( strings._('gui_tor_connection_lost', True)) if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: self.vbar.setValue(self.vbar.maximum()) self.new_download = False events = [] done = False while not done: try: r = web.q.get(False) events.append(r) except web.queue.Empty: done = True for event in events: if event["type"] == web.REQUEST_LOAD: self.status_bar.showMessage( strings._('download_page_loaded', True)) elif event["type"] == web.REQUEST_DOWNLOAD: self.downloads_container.show() # show the downloads layout self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) elif event["type"] == web.REQUEST_RATE_LIMIT: self.stop_server() Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) # is the download complete? if event["data"]["bytes"] == web.zip_filesize: if self.systemTray.supportsMessages( ) and self.settings.get('systray_notifications'): self.systemTray.showMessage( strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info self.downloads_completed += 1 self.update_downloads_completed(self.downloads_completed) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress( self.downloads_in_progress) # close on finish? if not web.get_stay_open(): self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText( strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) self.downloads_in_progress = 0 self.update_downloads_in_progress( self.downloads_in_progress) elif event["type"] == web.REQUEST_CANCELED: self.downloads.cancel_download(event["data"]["id"]) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format( web.error404_count, strings._('other_page_loaded', True), event["path"])) # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: if self.app.shutdown_timer and self.settings.get( 'shutdown_timeout'): if self.timeout > 0: now = QtCore.QDateTime.currentDateTime() seconds_remaining = now.secsTo(self.server_status.timeout) self.server_status.server_button.setText( strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) if not self.app.shutdown_timer.is_alive(): # If there were no attempts to download the share, or all downloads are done, we can stop if web.download_count == 0 or web.done: self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText( strings._('close_on_timeout', True)) # A download is probably still running - hold off on stopping the share else: self.status_bar.clearMessage() self.server_share_status_label.setText( strings._('timeout_download_still_running', True)) def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_url') if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_hidservauth') if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) def clear_message(self): """ Clear messages from the status bar. """ self.status_bar.clearMessage() def set_server_active(self, active): """ Disable the Settings button while an OnionShare server is active. """ if active: self.settings_button.hide() else: self.settings_button.show() # Disable settings menu action when server is active self.settingsAction.setEnabled(not active) def reset_info_counters(self): """ Set the info counters back to zero. """ self.update_downloads_completed(0) self.update_downloads_in_progress(0) def update_downloads_completed(self, count): """ Update the 'Downloads completed' info widget. """ if count == 0: self.info_completed_downloads_image = common.get_resource_path( 'images/download_completed_none.png') else: self.info_completed_downloads_image = common.get_resource_path( 'images/download_completed.png') self.info_completed_downloads_count.setText( '<img src="{0:s}" /> {1:d}'.format( self.info_completed_downloads_image, count)) self.info_completed_downloads_count.setToolTip( strings._('info_completed_downloads_tooltip', True).format(count)) def update_downloads_in_progress(self, count): """ Update the 'Downloads in progress' info widget. """ if count == 0: self.info_in_progress_downloads_image = common.get_resource_path( 'images/download_in_progress_none.png') else: self.info_in_progress_downloads_image = common.get_resource_path( 'images/download_in_progress.png') self.info_in_progress_downloads_count.setText( '<img src="{0:s}" /> {1:d}'.format( self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip( strings._('info_in_progress_downloads_tooltip', True).format(count)) def closeEvent(self, e): common.log('OnionShareGui', 'closeEvent') try: if self.server_status.status != self.server_status.STATUS_STOPPED: common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() dialog.setWindowTitle(strings._('gui_quit_title', True)) dialog.setText(strings._('gui_quit_warning', True)) dialog.setIcon(QtWidgets.QMessageBox.Critical) quit_button = dialog.addButton( strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) dont_quit_button = dialog.addButton( strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() # Quit if reply == 0: self.stop_server() e.accept() # Don't Quit else: e.ignore() except: e.accept()
def update_timestamp(): # Update the last checked label settings = Settings(self.common) settings.load() autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp)
class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) def __init__(self, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() self._initSystemTray() common.log('OnionShareGui', '__init__') self.onion = onion self.qtapp = qtapp self.app = app self.setWindowTitle('OnionShare') self.setWindowIcon( QtGui.QIcon(common.get_resource_path('images/logo.png'))) # Load settings self.config = config self.settings = Settings(self.config) self.settings.load() # File selection self.file_selection = FileSelection() if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection) self.server_status.server_started.connect( self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect( self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) self.start_server_finished.connect(self.clear_message) self.start_server_finished.connect( self.server_status.start_server_finished) self.stop_server_finished.connect( self.server_status.stop_server_finished) self.file_selection.file_list.files_updated.connect( self.server_status.update) self.server_status.url_copied.connect(self.copy_url) self.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setStyleSheet( 'padding: 10px 0; font-weight: bold; color: #333333;') self.filesize_warning.hide() # Downloads self.downloads = Downloads() self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) self.downloads_container.setMaximumHeight(200) self.downloads_container.setMinimumHeight(75) self.vbar = self.downloads_container.verticalScrollBar() self.downloads_container.hide() # downloads start out hidden self.new_download = False # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) self.status_bar.setStyleSheet("QStatusBar::item { border: 0px; }") version_label = QtWidgets.QLabel('v{0:s}'.format(common.get_version())) version_label.setStyleSheet('color: #666666') self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFlat(True) self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png'))) self.settings_button.clicked.connect(self.open_settings) self.status_bar.addPermanentWidget(version_label) self.status_bar.addPermanentWidget(self.settings_button) self.setStatusBar(self.status_bar) # Status bar, zip progress bar self._zip_progress_bar = None # Main layout self.layout = QtWidgets.QVBoxLayout() self.layout.addLayout(self.file_selection) self.layout.addLayout(self.server_status) self.layout.addWidget(self.filesize_warning) self.layout.addWidget(self.downloads_container) central_widget = QtWidgets.QWidget() central_widget.setLayout(self.layout) self.setCentralWidget(central_widget) self.show() # Check for requests frequently self.timer = QtCore.QTimer() self.timer.timeout.connect(self.check_for_requests) self.timer.start(500) # Always start with focus on file selection self.file_selection.setFocus() # The server isn't active yet self.set_server_active(False) # Start the "Connecting to Tor" dialog, which calls onion.connect() tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() # After connecting to Tor, check for updates self.check_for_updates() def _initSystemTray(self): system = common.get_platform() menu = QtWidgets.QMenu() self.settingsAction = menu.addAction( strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) self.helpAction = menu.addAction( strings._('gui_settings_button_help', True)) self.helpAction.triggered.connect(SettingsDialog.help_clicked) self.exitAction = menu.addAction(strings._('systray_menu_exit', True)) self.exitAction.triggered.connect(self.close) self.systemTray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale if system == 'Darwin': self.systemTray.setIcon( QtGui.QIcon( common.get_resource_path('images/logo_grayscale.png'))) else: self.systemTray.setIcon( QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.systemTray.setContextMenu(menu) self.systemTray.show() def _tor_connection_canceled(self): """ If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ common.log('OnionShareGui', '_tor_connection_canceled') def ask(): a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) settings_button = QtWidgets.QPushButton( strings._('gui_tor_connection_ask_open_settings', True)) quit_button = QtWidgets.QPushButton( strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) a.exec_() if a.clickedButton() == settings_button: # Open settings common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() if a.clickedButton() == quit_button: # Quit common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) # Wait 100ms before asking QtCore.QTimer.singleShot(100, ask) def _tor_connection_open_settings(self): """ The TorConnectionDialog wants to open the Settings dialog """ common.log('OnionShareGui', '_tor_connection_open_settings') # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) def open_settings(self): """ Open the SettingsDialog. """ common.log('OnionShareGui', 'open_settings') def reload_settings(): common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.settings.load() d = SettingsDialog(self.onion, self.qtapp, self.config) d.settings_saved.connect(reload_settings) d.exec_() def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ common.log('OnionShareGui', 'start_server') self.set_server_active(True) self.app.set_stealth(self.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared self.downloads_container.hide() self.downloads.reset_downloads() # Reset web counters web.download_count = 0 web.error404_count = 0 web.set_gui_mode() # start the onion service in a new thread def start_onion_service(self): try: self.app.start_onion_service() self.starting_server_step2.emit() except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout) as e: self.starting_server_error.emit(e.args[0]) return self.app.stay_open = not self.settings.get( 'close_after_first_download') # start onionshare http service in new thread t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) t = threading.Thread(target=start_onion_service, kwargs={'self': self}) t.daemon = True t.start() def start_server_step2(self): """ Step 2 in starting the onionshare server. Zipping up files. """ common.log('OnionShareGui', 'start_server_step2') # add progress bar to the status bar, indicating the crunching of files. self._zip_progress_bar = ZipProgressBar(0) self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size( self.file_selection.file_list.filenames) self.status_bar.clearMessage() self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread def finish_starting_server(self): # prepare files to share def _set_processed_size(x): if self._zip_progress_bar != None: self._zip_progress_bar.update_processed_size_signal.emit(x) web.set_file_info(self.file_selection.file_list.filenames, processed_size_callback=_set_processed_size) self.app.cleanup_filenames.append(web.zip_filename) self.starting_server_step3.emit() # done self.start_server_finished.emit() #self.status_bar.showMessage(strings._('gui_starting_server2', True)) t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) t.daemon = True t.start() def start_server_step3(self): """ Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ common.log('OnionShareGui', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None # warn about sending large files over Tor if web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() def start_server_error(self, error): """ If there's an error when trying to start the onion service """ common.log('OnionShareGui', 'start_server_error') self.set_server_active(False) Alert(error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() self.status_bar.clearMessage() def stop_server(self): """ Stop the onionshare server. """ common.log('OnionShareGui', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: web.stop(self.app.port) self.app.cleanup() self.filesize_warning.hide() self.stop_server_finished.emit() self.set_server_active(False) def check_for_updates(self): """ Check for updates in a new thread, if enabled. """ system = common.get_platform() if system == 'Windows' or system == 'Darwin': if self.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): Alert( strings._("update_available", True).format(update_url, installed_version, latest_version)) self.update_thread = UpdateThread(self.onion, self.config) self.update_thread.update_available.connect(update_available) self.update_thread.start() @staticmethod def _compute_total_size(filenames): total_size = 0 for filename in filenames: if os.path.isfile(filename): total_size += os.path.getsize(filename) if os.path.isdir(filename): total_size += common.dir_size(filename) return total_size def check_for_requests(self): """ Check for messages communicated from the web app, and update the GUI accordingly. """ self.update() # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: self.vbar.setValue(self.vbar.maximum()) self.new_download = False # only check for requests if the server is running if self.server_status.status != self.server_status.STATUS_STARTED: return events = [] done = False while not done: try: r = web.q.get(False) events.append(r) except web.queue.Empty: done = True for event in events: if event["type"] == web.REQUEST_LOAD: self.status_bar.showMessage( strings._('download_page_loaded', True)) elif event["type"] == web.REQUEST_DOWNLOAD: self.downloads_container.show() # show the downloads layout self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) elif event["type"] == web.REQUEST_RATE_LIMIT: self.stop_server() Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) # is the download complete? if event["data"]["bytes"] == web.zip_filesize: if self.systemTray.supportsMessages( ) and self.settings.get('systray_notifications'): self.systemTray.showMessage( strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # close on finish? if not web.get_stay_open(): self.server_status.stop_server() elif event["type"] == web.REQUEST_CANCELED: self.downloads.cancel_download(event["data"]["id"]) if self.systemTray.supportsMessages() and self.settings.get( 'systray_notifications'): self.systemTray.showMessage( strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format( web.error404_count, strings._('other_page_loaded', True), event["path"])) def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_url') self.status_bar.showMessage(strings._('gui_copied_url', True), 2000) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ common.log('OnionShareGui', 'copy_hidservauth') self.status_bar.showMessage(strings._('gui_copied_hidservauth', True), 2000) def clear_message(self): """ Clear messages from the status bar. """ self.status_bar.clearMessage() def set_server_active(self, active): """ Disable the Settings button while an OnionShare server is active. """ self.settings_button.setEnabled(not active) if active: self.settings_button.setIcon( QtGui.QIcon( common.get_resource_path('images/settings_inactive.png'))) else: self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png'))) # Disable settings menu action when server is active self.settingsAction.setEnabled(not active) def closeEvent(self, e): common.log('OnionShareGui', 'closeEvent') try: if self.server_status.status != self.server_status.STATUS_STOPPED: dialog = QtWidgets.QMessageBox() dialog.setWindowTitle("OnionShare") dialog.setText(strings._('gui_quit_warning', True)) quit_button = dialog.addButton( strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) dont_quit_button = dialog.addButton( strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() # Quit if reply == 0: self.stop_server() e.accept() # Don't Quit else: e.ignore() except: e.accept()
class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ settings_saved = QtCore.pyqtSignal() def __init__(self, onion, qtapp, config=False): super(SettingsDialog, self).__init__() common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp self.config = config self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowIcon( QtGui.QIcon(common.get_resource_path('images/logo.png'))) system = platform.system() # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText( strings._("gui_settings_close_after_first_download_option", True)) # Whether or not to show systray notifications self.systray_notifications_checkbox = QtWidgets.QCheckBox() self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) self.systray_notifications_checkbox.setText( strings._("gui_settings_systray_notifications", True)) # Whether or not to save the Onion private key for reuse self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.save_private_key_checkbox.setText( strings._("gui_save_private_key_checkbox", True)) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget( self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.systray_notifications_checkbox) sharing_group_layout.addWidget(self.save_private_key_checkbox) sharing_group = QtWidgets.QGroupBox( strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) # Stealth options # Stealth stealth_details = QtWidgets.QLabel( strings._("gui_settings_stealth_option_details", True)) stealth_details.setWordWrap(True) stealth_details.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction) stealth_details.setOpenExternalLinks(True) self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText( strings._("gui_settings_stealth_option", True)) hidservauth_details = QtWidgets.QLabel( strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) hidservauth_details.hide() self.hidservauth_copy_button = QtWidgets.QPushButton( strings._('gui_copy_hidservauth', True)) self.hidservauth_copy_button.clicked.connect( self.hidservauth_copy_button_clicked) self.hidservauth_copy_button.hide() # Stealth options layout stealth_group_layout = QtWidgets.QVBoxLayout() stealth_group_layout.addWidget(stealth_details) stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group_layout.addWidget(hidservauth_details) stealth_group_layout.addWidget(self.hidservauth_copy_button) stealth_group = QtWidgets.QGroupBox( strings._("gui_settings_stealth_label", True)) stealth_group.setLayout(stealth_group_layout) # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText( strings._("gui_settings_autoupdate_option", True)) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton( strings._('gui_settings_autoupdate_check_button', True)) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox( strings._("gui_settings_autoupdate_label", True)) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if system != 'Windows' and system != 'Darwin': autoupdate_group.hide() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio.toggled.connect( self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (system == 'Windows' or system == 'Darwin') and getattr( sys, 'onionshare_dev_mode', False): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( strings._('gui_settings_tor_bridges_no_bridges_radio_option', True)) self.tor_bridges_no_bridges_radio.toggled.connect( self.tor_bridges_no_bridges_radio_toggled) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings. _('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) self.tor_bridges_use_obfs4_radio.toggled.connect( self.tor_bridges_use_obfs4_radio_toggled) # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( strings._('gui_settings_tor_bridges_custom_radio_option', True)) self.tor_bridges_use_custom_radio.toggled.connect( self.tor_bridges_use_custom_radio_toggled) self.tor_bridges_use_custom_label = QtWidgets.QLabel( strings._('gui_settings_tor_bridges_custom_label', True)) self.tor_bridges_use_custom_label.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) self.tor_bridges_use_custom_textbox.setPlaceholderText( '[address:port] [identifier]') tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_label) tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_textbox) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() self.tor_bridges_use_custom_textbox_options.setLayout( tor_bridges_use_custom_textbox_options_layout) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio.toggled.connect( self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio.toggled.connect( self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel( strings._('gui_settings_control_port_label', True)) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit( ) self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget( connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout( connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio.toggled.connect( self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel( strings._('gui_settings_socket_file_label', True)) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget( connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget( self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout( connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel( strings._('gui_settings_socks_label', True)) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget( self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton( strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio.toggled.connect( self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton( strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio.toggled.connect( self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel( strings._('gui_settings_password_label', True)) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget( authenticate_password_extras_label) authenticate_password_extras_layout.addWidget( self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout( authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox( strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton( strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget( self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget( self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget( self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget( self.connection_type_socket_file_radio) connection_type_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_connection_type_label", True)) connection_type_radio_group.setLayout( connection_type_radio_group_layout) # Connection type layout connection_type_group_layout = QtWidgets.QVBoxLayout() connection_type_group_layout.addWidget( self.connection_type_control_port_extras) connection_type_group_layout.addWidget( self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) connection_type_group_layout.addWidget( self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox() connection_type_group.setLayout(connection_type_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_tor_bridges", True)) self.connection_type_bridges_radio_group.setLayout( connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() # Buttons self.save_button = QtWidgets.QPushButton( strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton( strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) self.help_button = QtWidgets.QPushButton( strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet( 'background-color: #ffffff; color: #000000; padding: 10px') self.tor_status.hide() # Layout left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(self.connection_type_bridges_radio_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() col_layout = QtWidgets.QHBoxLayout() col_layout.addLayout(left_col_layout) col_layout.addLayout(right_col_layout) layout = QtWidgets.QVBoxLayout() layout.addLayout(col_layout) layout.addLayout(buttons_layout) self.setLayout(layout) self.cancel_button.setFocus() # Load settings, and fill them in self.old_settings = Settings(self.config) self.old_settings.load() close_after_first_download = self.old_settings.get( 'close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Unchecked) systray_notifications = self.old_settings.get('systray_notifications') if systray_notifications: self.systray_notifications_checkbox.setCheckState( QtCore.Qt.Checked) else: self.systray_notifications_checkbox.setCheckState( QtCore.Qt.Unchecked) save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) if save_private_key: hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) connection_type = self.old_settings.get('connection_type') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'automatic': self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'control_port': self.connection_type_control_port_radio.setChecked(True) elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText( self.old_settings.get('control_port_address')) self.connection_type_control_port_extras_port.setText( str(self.old_settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText( self.old_settings.get('socket_file_path')) self.connection_type_socks_address.setText( self.old_settings.get('socks_address')) self.connection_type_socks_port.setText( str(self.old_settings.get('socks_port'))) auth_type = self.old_settings.get('auth_type') if auth_type == 'no_auth': self.authenticate_no_auth_radio.setChecked(True) elif auth_type == 'password': self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText( self.old_settings.get('auth_password')) if self.old_settings.get('no_bridges'): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) self.tor_bridges_use_obfs4_radio.setChecked( self.old_settings.get('tor_bridges_use_obfs4')) if self.old_settings.get('tor_bridges_use_custom_bridges'): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] bridges = self.old_settings.get( 'tor_bridges_use_custom_bridges').split('Bridge ') for bridge in bridges: new_bridges.append(bridge) new_bridges = ''.join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_bundled_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() def tor_bridges_no_bridges_radio_toggled(self, checked): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ if checked: self.tor_bridges_use_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_automatic_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_control_port_toggled') if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_socket_file_toggled') if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ common.log('SettingsDialog', 'authenticate_no_auth_toggled') def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() else: self.authenticate_password_extras.hide() def hidservauth_copy_button_clicked(self): """ Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ common.log('SettingsDialog', 'test_tor_clicked') settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor if settings.get('connection_type') == 'bundled': self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True else: tor_status_update_func = None onion = Onion() onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) # If an exception hasn't been raised yet, the Tor settings work Alert( strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) # Clean up onion.cleanup() except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: Alert(e.args[0], QtWidgets.QMessageBox.Warning) if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() # Check for updates def update_available(update_url, installed_version, latest_version): Alert( strings._("update_available", True).format(update_url, installed_version, latest_version)) def update_not_available(): Alert(strings._('update_not_available', True)) u = UpdateChecker(self.onion) u.update_available.connect(update_available) u.update_not_available.connect(update_not_available) try: u.check(force=True) except UpdateCheckerCheckError: Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) except UpdateCheckerInvalidLatestVersion as e: Alert( strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) # Enable buttons self._enable_buttons() # Update the last checked label settings = Settings(self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) def save_clicked(self): """ Save button clicked. Save current settings to disk. """ common.log('SettingsDialog', 'save_clicked') settings = self.settings_from_fields() settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if self.onion.is_authenticated(): common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values have changed for the given keys. """ for key in keys: if s1.get(key) != s2.get(key): return True return False if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', 'control_port_port', 'socks_address', 'socks_port', 'socket_file_path', 'auth_type', 'auth_password', 'no_bridges', 'tor_bridges_use_obfs4', 'tor_bridges_use_custom_bridges' ]): reboot_onion = True else: common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) tor_con.start() common.log( 'SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format( self.onion.connected_to_tor)) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() def cancel_clicked(self): """ Cancel button clicked. """ common.log('SettingsDialog', 'cancel_clicked') if not self.onion.is_authenticated(): Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() def help_clicked(self): """ Help button clicked. """ common.log('SettingsDialog', 'help_clicked') help_site = 'https://github.com/micahflee/onionshare/wiki' QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ common.log('SettingsDialog', 'settings_from_fields') settings = Settings(self.config) settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked()) if self.save_private_key_checkbox.isChecked(): settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) else: settings.set('save_private_key', False) settings.set('private_key', '') settings.set('slug', '') # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): settings.set('hidservauth_string', '') if self.connection_type_bundled_radio.isChecked(): settings.set('connection_type', 'bundled') if self.connection_type_automatic_radio.isChecked(): settings.set('connection_type', 'automatic') if self.connection_type_control_port_radio.isChecked(): settings.set('connection_type', 'control_port') if self.connection_type_socket_file_radio.isChecked(): settings.set('connection_type', 'socket_file') if self.autoupdate_checkbox.isChecked(): settings.set('use_autoupdate', True) else: settings.set('use_autoupdate', False) settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) settings.set('socks_address', self.connection_type_socks_address.text()) settings.set('socks_port', self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): settings.set('auth_type', 'no_auth') if self.authenticate_password_radio.isChecked(): settings.set('auth_type', 'password') settings.set('auth_password', self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): settings.set('no_bridges', True) settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', True) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_custom_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] bridges = self.tor_bridges_use_custom_textbox.toPlainText().split( '\n') bridges_valid = False for bridge in bridges: if bridge != '': # Check the syntax of the custom bridge to make sure it looks legitimate pattern = re.compile("[0-9.]+:[0-9]+\s[A-Z0-9]+$") if pattern.match(bridge): new_bridges.append(''.join(['Bridge ', bridge, '\n'])) bridges_valid = True if bridges_valid: new_bridges = ''.join(new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges) else: Alert(strings._('gui_settings_tor_bridges_invalid', True)) settings.set('no_bridges', True) return settings def closeEvent(self, e): common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether if not self.onion.is_authenticated(): common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): common.log('SettingsDialog', '_update_autoupdate_timestamp') if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime('%B %d, %Y %H:%M') else: last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) self.autoupdate_timestamp.setText( strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) def _tor_status_update(self, progress, summary): self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format( strings._('connecting_to_tor', True), progress, summary)) self.qtapp.processEvents() if 'Done' in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): common.log('SettingsDialog', '_disable_buttons') self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): common.log('SettingsDialog', '_enable_buttons') # We can't check for updates if we're still not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True)
class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ settings_saved = QtCore.pyqtSignal() def __init__(self, onion, qtapp, config=False): super(SettingsDialog, self).__init__() common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp self.config = config self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowIcon( QtGui.QIcon(common.get_resource_path('images/logo.png'))) system = platform.system() # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText( strings._("gui_settings_close_after_first_download_option", True)) # Whether or not to show systray notifications self.systray_notifications_checkbox = QtWidgets.QCheckBox() self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) self.systray_notifications_checkbox.setText( strings._("gui_settings_systray_notifications", True)) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget( self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.systray_notifications_checkbox) sharing_group = QtWidgets.QGroupBox( strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) # Stealth options # Stealth stealth_details = QtWidgets.QLabel( strings._("gui_settings_stealth_option_details", True)) stealth_details.setWordWrap(True) self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText( strings._("gui_settings_stealth_option", True)) # Stealth options layout stealth_group_layout = QtWidgets.QVBoxLayout() stealth_group_layout.addWidget(stealth_details) stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group = QtWidgets.QGroupBox( strings._("gui_settings_stealth_label", True)) stealth_group.setLayout(stealth_group_layout) # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText( strings._("gui_settings_autoupdate_option", True)) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton( strings._('gui_settings_autoupdate_check_button', True)) self.check_for_updates_button.clicked.connect(self.check_for_updates) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox( strings._("gui_settings_autoupdate_label", True)) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if system != 'Windows' and system != 'Darwin': autoupdate_group.hide() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio.toggled.connect( self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (system == 'Windows' or system == 'Darwin') and getattr( sys, 'onionshare_dev_mode', False): self.connection_type_bundled_radio.setEnabled(False) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio.toggled.connect( self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio.toggled.connect( self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel( strings._('gui_settings_control_port_label', True)) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit( ) self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget( connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout( connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio.toggled.connect( self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel( strings._('gui_settings_socket_file_label', True)) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget( connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget( self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout( connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel( strings._('gui_settings_socks_label', True)) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget( self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton( strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio.toggled.connect( self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton( strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio.toggled.connect( self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel( strings._('gui_settings_password_label', True)) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget( authenticate_password_extras_label) authenticate_password_extras_layout.addWidget( self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout( authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox( strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton( strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget( self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget( self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget( self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget( self.connection_type_socket_file_radio) connection_type_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_connection_type_label", True)) connection_type_radio_group.setLayout( connection_type_radio_group_layout) # Connection type layout connection_type_group_layout = QtWidgets.QVBoxLayout() connection_type_group_layout.addWidget( self.connection_type_control_port_extras) connection_type_group_layout.addWidget( self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) connection_type_group_layout.addWidget( self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox() connection_type_group.setLayout(connection_type_group_layout) # Buttons self.save_button = QtWidgets.QPushButton( strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton( strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) self.help_button = QtWidgets.QPushButton( strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet( 'background-color: #ffffff; color: #000000; padding: 10px') self.tor_status.hide() # Layout left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() col_layout = QtWidgets.QHBoxLayout() col_layout.addLayout(left_col_layout) col_layout.addLayout(right_col_layout) layout = QtWidgets.QVBoxLayout() layout.addLayout(col_layout) layout.addLayout(buttons_layout) self.setLayout(layout) self.cancel_button.setFocus() # Load settings, and fill them in self.old_settings = Settings(self.config) self.old_settings.load() close_after_first_download = self.old_settings.get( 'close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Unchecked) systray_notifications = self.old_settings.get('systray_notifications') if systray_notifications: self.systray_notifications_checkbox.setCheckState( QtCore.Qt.Checked) else: self.systray_notifications_checkbox.setCheckState( QtCore.Qt.Unchecked) use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) connection_type = self.old_settings.get('connection_type') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'automatic': self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'control_port': self.connection_type_control_port_radio.setChecked(True) elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText( self.old_settings.get('control_port_address')) self.connection_type_control_port_extras_port.setText( str(self.old_settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText( self.old_settings.get('socket_file_path')) self.connection_type_socks_address.setText( self.old_settings.get('socks_address')) self.connection_type_socks_port.setText( str(self.old_settings.get('socks_port'))) auth_type = self.old_settings.get('auth_type') if auth_type == 'no_auth': self.authenticate_no_auth_radio.setChecked(True) elif auth_type == 'password': self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText( self.old_settings.get('auth_password')) def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_bundled_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ common.log('SettingsDialog', 'connection_type_automatic_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_control_port_toggled') if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'connection_type_socket_file_toggled') if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ common.log('SettingsDialog', 'authenticate_no_auth_toggled') def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() else: self.authenticate_password_extras.hide() def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ common.log('SettingsDialog', 'test_tor_clicked') settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor if settings.get('connection_type') == 'bundled': self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True else: tor_status_update_func = None onion = Onion() onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) # If an exception hasn't been raised yet, the Tor settings work Alert( strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) # Clean up onion.cleanup() except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: Alert(e.args[0], QtWidgets.QMessageBox.Warning) if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() # Check for updates def update_available(update_url, installed_version, latest_version): Alert( strings._("update_available", True).format(update_url, installed_version, latest_version)) def update_not_available(): Alert(strings._('update_not_available', True)) u = UpdateChecker(self.onion) u.update_available.connect(update_available) u.update_not_available.connect(update_not_available) try: u.check(force=True) except UpdateCheckerCheckError: Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) except UpdateCheckerInvalidLatestVersion as e: Alert( strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) # Enable buttons self._enable_buttons() # Update the last checked label settings = Settings(self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) def save_clicked(self): """ Save button clicked. Save current settings to disk. """ common.log('SettingsDialog', 'save_clicked') settings = self.settings_from_fields() settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if self.onion.connected_to_tor: def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values have changed for the given keys. """ for key in keys: if s1.get(key) != s2.get(key): return True return False if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', 'control_port_port', 'socks_address', 'socks_port', 'socket_file_path', 'auth_type', 'auth_password' ]): reboot_onion = True else: # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) tor_con.start() common.log( 'SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format( self.onion.connected_to_tor)) if self.onion.connected_to_tor and not tor_con.wasCanceled(): self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() def cancel_clicked(self): """ Cancel button clicked. """ common.log('SettingsDialog', 'cancel_clicked') if not self.onion.connected_to_tor: Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() def help_clicked(self): """ Help button clicked. """ common.log('SettingsDialog', 'help_clicked') help_site = 'https://github.com/micahflee/onionshare/wiki' QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ common.log('SettingsDialog', 'settings_from_fields') settings = Settings(self.config) settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) if self.connection_type_bundled_radio.isChecked(): settings.set('connection_type', 'bundled') if self.connection_type_automatic_radio.isChecked(): settings.set('connection_type', 'automatic') if self.connection_type_control_port_radio.isChecked(): settings.set('connection_type', 'control_port') if self.connection_type_socket_file_radio.isChecked(): settings.set('connection_type', 'socket_file') settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) settings.set('socks_address', self.connection_type_socks_address.text()) settings.set('socks_port', self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): settings.set('auth_type', 'no_auth') if self.authenticate_password_radio.isChecked(): settings.set('auth_type', 'password') settings.set('auth_password', self.authenticate_password_extras_password.text()) return settings def closeEvent(self, e): common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether if not self.onion.connected_to_tor: common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): common.log('SettingsDialog', '_update_autoupdate_timestamp') if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime('%B %d, %Y %H:%M') else: last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) self.autoupdate_timestamp.setText( strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) def _tor_status_update(self, progress, summary): self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format( strings._('connecting_to_tor', True), progress, summary)) self.qtapp.processEvents() if 'Done' in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): common.log('SettingsDialog', '_disable_buttons') self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): common.log('SettingsDialog', '_enable_buttons') self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True)
def __init__(self, qtapp): super(SettingsDialog, self).__init__() self.qtapp = qtapp self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowIcon( QtGui.QIcon(helpers.get_resource_path('images/logo.png'))) system = platform.system() # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText( strings._("gui_settings_close_after_first_download_option", True)) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget( self.close_after_first_download_checkbox) sharing_group = QtWidgets.QGroupBox( strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) # Stealth options # Stealth stealth_details = QtWidgets.QLabel( strings._("gui_settings_stealth_option_details", True)) stealth_details.setWordWrap(True) self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText( strings._("gui_settings_stealth_option", True)) # Stealth options layout stealth_group_layout = QtWidgets.QVBoxLayout() stealth_group_layout.addWidget(stealth_details) stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group = QtWidgets.QGroupBox( strings._("gui_settings_stealth_label", True)) stealth_group.setLayout(stealth_group_layout) # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText( strings._("gui_settings_autoupdate_option", True)) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton( strings._('gui_settings_autoupdate_check_button', True)) self.check_for_updates_button.clicked.connect(self.check_for_updates) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox( strings._("gui_settings_autoupdate_label", True)) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if system != 'Windows' and system != 'Darwin': autoupdate_group.hide() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio.toggled.connect( self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (system == 'Windows' or system == 'Darwin') and getattr( sys, 'onionshare_dev_mode', False): self.connection_type_bundled_radio.setEnabled(False) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio.toggled.connect( self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_control_port_option', True)) self.connection_type_control_port_radio.toggled.connect( self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel( strings._('gui_settings_control_port_label', True)) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit( ) self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget( connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout( connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton( strings._('gui_settings_connection_type_socket_file_option', True)) self.connection_type_socket_file_radio.toggled.connect( self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel( strings._('gui_settings_socket_file_label', True)) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget( connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget( self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout( connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel( strings._('gui_settings_socks_label', True)) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget( self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton( strings._('gui_settings_authenticate_no_auth_option', True)) self.authenticate_no_auth_radio.toggled.connect( self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton( strings._('gui_settings_authenticate_password_option', True)) self.authenticate_password_radio.toggled.connect( self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel( strings._('gui_settings_password_label', True)) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget( authenticate_password_extras_label) authenticate_password_extras_layout.addWidget( self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout( authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Authentication options layout authenticate_group_layout = QtWidgets.QVBoxLayout() authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) self.authenticate_group = QtWidgets.QGroupBox( strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton( strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) # Connection type layout connection_type_group_layout = QtWidgets.QVBoxLayout() connection_type_group_layout.addWidget( self.connection_type_bundled_radio) connection_type_group_layout.addWidget( self.connection_type_automatic_radio) connection_type_group_layout.addWidget( self.connection_type_control_port_radio) connection_type_group_layout.addWidget( self.connection_type_socket_file_radio) connection_type_group_layout.addWidget( self.connection_type_control_port_extras) connection_type_group_layout.addWidget( self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) connection_type_group_layout.addWidget( self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox( strings._("gui_settings_connection_type_label", True)) connection_type_group.setLayout(connection_type_group_layout) # Buttons self.save_button = QtWidgets.QPushButton( strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton( strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet( 'background-color: #ffffff; color: #000000; padding: 10px') self.tor_status.hide() # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(sharing_group) layout.addWidget(stealth_group) layout.addWidget(autoupdate_group) layout.addWidget(connection_type_group) layout.addStretch() layout.addLayout(buttons_layout) layout.addWidget(self.tor_status) self.setLayout(layout) self.cancel_button.setFocus() # Load settings, and fill them in settings = Settings() settings.load() close_after_first_download = settings.get('close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState( QtCore.Qt.Unchecked) use_stealth = settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) use_autoupdate = settings.get('use_autoupdate') if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) connection_type = settings.get('connection_type') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'automatic': self.connection_type_automatic_radio.setChecked(True) elif connection_type == 'control_port': self.connection_type_control_port_radio.setChecked(True) elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText( settings.get('control_port_address')) self.connection_type_control_port_extras_port.setText( str(settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText( settings.get('socket_file_path')) self.connection_type_socks_address.setText( settings.get('socks_address')) self.connection_type_socks_port.setText(str( settings.get('socks_port'))) auth_type = settings.get('auth_type') if auth_type == 'no_auth': self.authenticate_no_auth_radio.setChecked(True) elif auth_type == 'password': self.authenticate_password_radio.setChecked(True) self.authenticate_password_extras_password.setText( settings.get('auth_password')) # Show the dialog self.exec_()
def check(self, force=False): # Load the settings settings = Settings() settings.load() # If force=True, then definitely check if force: check_for_updates = True else: check_for_updates = False # See if it's been 1 day since the last check autoupdate_timestamp = settings.get('autoupdate_timestamp') if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp( autoupdate_timestamp) now = datetime.datetime.now() one_day = datetime.timedelta(days=1) if now - last_checked > one_day: check_for_updates = True else: check_for_updates = True # Check for updates if check_for_updates: # Create an Onion object, for checking for updates over tor try: onion = Onion(settings=settings, bundled_tor_func=self._bundled_tor_func) except: raise UpdateCheckerTorError # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform user_agent = 'OnionShare {}, {}'.format( helpers.get_version(), platform.system()) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users path = '/latest-version.txt' if force: path += '?force=1' (socks_address, socks_port) = onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() s.settimeout(15) # 15 second timeout s.connect(('elx57ue5uyfplgva.onion', 80)) http_request = 'GET {} HTTP/1.0\r\n'.format(path) http_request += 'Host: elx57ue5uyfplgva.onion\r\n' http_request += 'User-Agent: {}\r\n'.format(user_agent) http_request += '\r\n' s.sendall(http_request.encode('utf-8')) http_response = s.recv(1024) latest_version = http_response[ http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') # Clean up from Onion onion.cleanup() except: raise UpdateCheckerSOCKSHTTPError # Validate that latest_version looks like a version string # This regex is: 1-3 dot-separated numeric components version_re = r"^(\d+\.)?(\d+\.)?(\d+)$" if not re.match(version_re, latest_version): raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) timestamp = datetime.datetime.now().replace(microsecond=0).replace( second=0).timestamp() settings.set('autoupdate_timestamp', timestamp) settings.save() # Do we need to update? update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format( latest_version) installed_version = helpers.get_version() if installed_version < latest_version: self.update_available.emit(update_url, installed_version, latest_version) return # No updates are available self.update_not_available.emit()