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)
Beispiel #2
0
    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)
Beispiel #3
0
 def test_load_strings_loads_other_languages(self, common_obj, locale_fr,
                                             sys_onionshare_dev_mode):
     """ load_strings() loads other languages in different locales """
     common_obj.settings = Settings(common_obj)
     common_obj.settings.set("locale", "fr")
     strings.load_strings(common_obj)
     assert strings._("preparing_files") == "Compression des fichiers."
Beispiel #4
0
 def test_load_invalid_locale(self, common_obj, locale_invalid,
                              sys_onionshare_dev_mode):
     """ load_strings() raises a KeyError for an invalid locale """
     with pytest.raises(KeyError):
         common_obj.settings = Settings(common_obj)
         common_obj.settings.set("locale", "XX")
         strings.load_strings(common_obj)
Beispiel #5
0
 def test_load_strings_defaults_to_english(self, common_obj, locale_en,
                                           sys_onionshare_dev_mode):
     """ load_strings() loads English by default """
     common_obj.settings = Settings(common_obj)
     strings.load_strings(common_obj)
     assert strings._(
         "not_a_readable_file") == "{0:s} is not a readable file."
def web_obj(common_obj, mode, num_files=0):
    """ Creates a Web object, in either share mode or receive mode, ready for testing """
    common_obj.settings = Settings(common_obj)
    strings.load_strings(common_obj)
    web = Web(common_obj, False, mode)
    web.generate_password()
    web.stay_open = True
    web.running = True

    web.app.testing = True

    # Share mode
    if mode == "share":
        # Add files
        files = []
        for i in range(num_files):
            with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
                tmp_file.write(b"*" * 1024)
                files.append(tmp_file.name)
        web.share_mode.set_file_info(files)
    # Receive mode
    else:
        pass

    return web
Beispiel #7
0
 def test_load_strings_loads_other_languages(self, common_obj, locale_fr,
                                             sys_onionshare_dev_mode):
     """ load_strings() loads other languages in different locales """
     common_obj.settings = Settings(common_obj)
     common_obj.settings.set("locale", "fr")
     strings.load_strings(common_obj)
     assert strings._(
         "not_a_readable_file") == "{0:s} n’est pas un fichier lisible."
Beispiel #8
0
    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 set_up(test_settings):
        """Create GUI with given settings"""
        # Create our test file
        testfile = open("/tmp/test.txt", "w")
        testfile.write("onionshare")
        testfile.close()

        # Create a test dir and files
        if not os.path.exists("/tmp/testdir"):
            testdir = os.mkdir("/tmp/testdir")
        testfile = open("/tmp/testdir/test.txt", "w")
        testfile.write("onionshare")
        testfile.close()

        common = Common()
        common.settings = Settings(common)
        common.define_css()
        strings.load_strings(common)

        # Get all of the settings in test_settings
        test_settings["connection_type"] = "automatic"
        test_settings["data_dir"] = "/tmp/OnionShare"
        for key, val in common.settings.default_settings.items():
            if key not in test_settings:
                test_settings[key] = val

        # Start the Onion
        testonion = Onion(common)
        global qtapp
        qtapp = Application(common)
        app = OnionShare(common, testonion, False, 0)

        web = Web(common, False, False)
        open("/tmp/settings.json", "w").write(json.dumps(test_settings))

        gui = OnionShareGui(
            common,
            testonion,
            qtapp,
            app,
            ["/tmp/test.txt", "/tmp/testdir"],
            "/tmp/settings.json",
            False,
        )
        return gui
Beispiel #10
0
    def set_up(test_settings):
        '''Create GUI with given settings'''
        # Create our test file
        testfile = open('/tmp/test.txt', 'w')
        testfile.write('onionshare')
        testfile.close()

        # Create a test dir and files
        if not os.path.exists('/tmp/testdir'):
            testdir = os.mkdir('/tmp/testdir')
        testfile = open('/tmp/testdir/test.txt', 'w')
        testfile.write('onionshare')
        testfile.close()

        common = Common()
        common.settings = Settings(common)
        common.define_css()
        strings.load_strings(common)

        # Get all of the settings in test_settings
        test_settings['connection_type'] = 'automatic'
        test_settings['data_dir'] = '/tmp/OnionShare'
        for key, val in common.settings.default_settings.items():
            if key not in test_settings:
                test_settings[key] = val

        # Start the Onion
        testonion = Onion(common)
        global qtapp
        qtapp = Application(common)
        app = OnionShare(common, testonion, False, 0)

        web = Web(common, False, False)
        open('/tmp/settings.json', 'w').write(json.dumps(test_settings))

        gui = OnionShareGui(common, testonion, qtapp, app,
                            ['/tmp/test.txt', '/tmp/testdir'],
                            '/tmp/settings.json', False)
        return gui
Beispiel #11
0
    def set_up(test_settings):
        """Create GUI with given settings"""
        # Create our test file
        testfile = open("/tmp/index.html", "w")
        testfile.write(
            "<html><body><p>This is a test website hosted by OnionShare</p></body></html>"
        )
        testfile.close()

        common = Common()
        common.settings = Settings(common)
        common.define_css()
        strings.load_strings(common)

        # Get all of the settings in test_settings
        test_settings["data_dir"] = "/tmp/OnionShare"
        for key, val in common.settings.default_settings.items():
            if key not in test_settings:
                test_settings[key] = val

        # Start the Onion
        testonion = Onion(common)
        global qtapp
        qtapp = Application(common)
        app = OnionShare(common, testonion, True, 0)

        web = Web(common, False, True)
        open("/tmp/settings.json", "w").write(json.dumps(test_settings))

        gui = OnionShareGui(
            common,
            testonion,
            qtapp,
            app,
            ["/tmp/index.html"],
            "/tmp/settings.json",
            True,
        )
        return gui
Beispiel #12
0
    def set_up():
        '''Create the GUI'''

        # Default settings for the settings GUI tests
        test_settings = {
            "no_bridges":
            False,
            "tor_bridges_use_custom_bridges":
            "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n",
        }

        # Create our test file
        testfile = open('/tmp/test.txt', 'w')
        testfile.write('onionshare')
        testfile.close()

        common = Common()
        common.settings = Settings(common)
        common.define_css()
        strings.load_strings(common)

        # Start the Onion
        testonion = Onion(common)
        global qtapp
        qtapp = Application(common)
        app = OnionShare(common, testonion, True, 0)

        for key, val in common.settings.default_settings.items():
            if key not in test_settings:
                test_settings[key] = val

        open('/tmp/settings.json', 'w').write(json.dumps(test_settings))

        gui = SettingsDialog(common, testonion, qtapp, '/tmp/settings.json',
                             True)
        return gui
Beispiel #13
0
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 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
Beispiel #15
0
    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)
Beispiel #16
0
    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)
Beispiel #17
0
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, 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()
Beispiel #19
0
    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
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, 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'))
Beispiel #22
0
 def test_load_strings_defaults_to_english(self, common_obj, locale_en,
                                           sys_onionshare_dev_mode):
     """ load_strings() loads English by default """
     common_obj.settings = Settings(common_obj)
     strings.load_strings(common_obj)
     assert strings._("preparing_files") == "Compressing files."
    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 __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)
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)
Beispiel #26
0
    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)
Beispiel #27
0
 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)
Beispiel #28
0
    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')

        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
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()
Beispiel #30
0
    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
Beispiel #31
0
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)
Beispiel #32
0
    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
Beispiel #33
0
    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()
Beispiel #34
0
    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()
Beispiel #35
0
    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'))
Beispiel #36
0
    def settings_from_fields(self):
        """
        Return a Settings object that's full of values from the settings dialog.
        """
        settings = Settings()

        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', int(self.connection_type_control_port_extras_port.text()))
        settings.set('socket_file_path', self.connection_type_socket_file_extras_path.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
Beispiel #37
0
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)
Beispiel #38
0
    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_()
Beispiel #39
0
 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 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()
Beispiel #41
0
    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()
    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()