Пример #1
0
    def get_color_mode(self, common):
        curr_settings = Settings(common)
        curr_settings.load()
        current_theme = curr_settings.get("theme")

        if current_theme == 1:
            return "light"
        elif current_theme == 2:
            return "dark"
        else:
            return "dark" if self.is_dark_mode() else "light"
Пример #2
0
def web_obj(temp_dir, 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)
    mode_settings = ModeSettings(common_obj)
    web = Web(common_obj, False, mode_settings, mode)
    web.running = True

    web.cleanup_tempfiles == []
    web.cleanup_tempdirs == []
    web.app.testing = True

    # Share mode
    if mode == "share":
        # Add files
        files = []
        for _ in range(num_files):
            with tempfile.NamedTemporaryFile(delete=False,
                                             dir=temp_dir.name) 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
Пример #3
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(locale)
        self.language_combobox.setCurrentIndex(locale_index)

        theme_choice = self.old_settings.get("theme")
        self.theme_combobox.setCurrentIndex(theme_choice)
Пример #4
0
    def settings_from_fields(self):
        """
        Return a Settings object that's full of values from the settings dialog.
        """
        self.common.log("SettingsTab", "settings_from_fields")
        settings = Settings(self.common)
        settings.load()  # To get the last update timestamp

        # Theme
        theme_index = self.theme_combobox.currentIndex()
        settings.set("theme", theme_index)

        # Language
        locale_index = self.language_combobox.currentIndex()
        locale = self.language_combobox.itemData(locale_index)
        settings.set("locale", locale)

        return settings
Пример #5
0
class SettingsTab(QtWidgets.QWidget):
    """
    Settings dialog.
    """

    def __init__(self, common, tab_id, parent=None):
        super(SettingsTab, self).__init__()

        self.common = common
        self.common.log("SettingsTab", "__init__")

        self.system = platform.system()
        self.tab_id = tab_id
        self.parent = parent

        # 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_layout = QtWidgets.QHBoxLayout()
        autoupdate_layout.addStretch()
        autoupdate_layout.addWidget(autoupdate_group)
        autoupdate_layout.addStretch()
        autoupdate_widget = QtWidgets.QWidget()
        autoupdate_widget.setLayout(autoupdate_layout)

        # Autoupdate is only available for Windows and Mac (Linux updates using package manager)
        if self.system != "Windows" and self.system != "Darwin":
            autoupdate_widget.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, locale)
        language_layout = QtWidgets.QHBoxLayout()
        language_layout.addStretch()
        language_layout.addWidget(language_label)
        language_layout.addWidget(self.language_combobox)
        language_layout.addStretch()

        # Theme Settings
        theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label"))
        self.theme_combobox = QtWidgets.QComboBox()
        theme_choices = [
            strings._("gui_settings_theme_auto"),
            strings._("gui_settings_theme_light"),
            strings._("gui_settings_theme_dark"),
        ]
        self.theme_combobox.addItems(theme_choices)
        theme_layout = QtWidgets.QHBoxLayout()
        theme_layout.addStretch()
        theme_layout.addWidget(theme_label)
        theme_layout.addWidget(self.theme_combobox)
        theme_layout.addStretch()

        # Version and help
        version_label = QtWidgets.QLabel(
            strings._("gui_settings_version_label").format(self.common.version)
        )
        version_label.setAlignment(QtCore.Qt.AlignHCenter)
        help_label = QtWidgets.QLabel(strings._("gui_settings_help_label"))
        help_label.setAlignment(QtCore.Qt.AlignHCenter)
        help_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
        help_label.setOpenExternalLinks(True)

        # Buttons
        self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save"))
        self.save_button.clicked.connect(self.save_clicked)
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.addStretch()
        buttons_layout.addWidget(self.save_button)
        buttons_layout.addStretch()

        # Layout
        layout = QtWidgets.QVBoxLayout()
        layout.addStretch()
        layout.addWidget(autoupdate_widget)
        if autoupdate_widget.isVisible():
            layout.addSpacing(20)
        layout.addLayout(language_layout)
        layout.addLayout(theme_layout)
        layout.addSpacing(20)
        layout.addWidget(version_label)
        layout.addWidget(help_label)
        layout.addSpacing(20)
        layout.addLayout(buttons_layout)
        layout.addStretch()

        self.setLayout(layout)

        self.reload_settings()

        if self.common.gui.onion.connected_to_tor:
            self.tor_is_connected()
        else:
            self.tor_is_disconnected()

    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(locale)
        self.language_combobox.setCurrentIndex(locale_index)

        theme_choice = self.old_settings.get("theme")
        self.theme_combobox.setCurrentIndex(theme_choice)

    def check_for_updates(self):
        """
        Check for Updates button clicked. Manually force an update check.
        """
        self.common.log("SettingsTab", "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.common.gui.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("SettingsTab", "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)

            # If color mode changed, inform user they need to restart OnionShare
            if changed(settings, self.old_settings, ["theme"]):
                notice = strings._("gui_color_mode_changed_notice")
                Alert(self.common, notice, QtWidgets.QMessageBox.Information)

            # Save the new settings
            settings.save()
            self.parent.close_this_tab.emit()

    def help_clicked(self):
        """
        Help button clicked.
        """
        self.common.log("SettingsTab", "help_clicked")
        SettingsTab.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("SettingsTab", "settings_from_fields")
        settings = Settings(self.common)
        settings.load()  # To get the last update timestamp

        # Theme
        theme_index = self.theme_combobox.currentIndex()
        settings.set("theme", theme_index)

        # Language
        locale_index = self.language_combobox.currentIndex()
        locale = self.language_combobox.itemData(locale_index)
        settings.set("locale", locale)

        return settings

    def settings_have_changed(self):
        # Global settings have changed
        self.common.log("SettingsTab", "settings_have_changed")

    def _update_autoupdate_timestamp(self, autoupdate_timestamp):
        self.common.log("SettingsTab", "_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 _disable_buttons(self):
        self.common.log("SettingsTab", "_disable_buttons")

        self.check_for_updates_button.setEnabled(False)
        self.save_button.setEnabled(False)

    def _enable_buttons(self):
        self.common.log("SettingsTab", "_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.save_button.setEnabled(True)

    def tor_is_connected(self):
        self.check_for_updates_button.show()

    def tor_is_disconnected(self):
        self.check_for_updates_button.hide()
Пример #6
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)
Пример #7
0
    def __init__(self, common, tab_id, status_bar, window, parent=None):
        super(AutoConnectTab, self).__init__()
        self.common = common
        self.common.log("AutoConnectTab", "__init__")

        self.status_bar = status_bar
        self.tab_id = tab_id
        self.window = window
        self.parent = parent

        # Was auto connected?
        self.curr_settings = Settings(common)
        self.curr_settings.load()
        self.auto_connect_enabled = self.curr_settings.get("auto_connect")

        # Rocket ship animation images
        self.anim_stars = AnimStars(self, self.window)
        self.anim_ship = AnimShip(self, self.window)
        self.anim_smoke = AnimSmoke(self, self.window)

        # Onionshare logo
        self.image_label = QtWidgets.QLabel()
        self.image_label.setPixmap(
            QtGui.QPixmap.fromImage(
                QtGui.QImage(
                    GuiCommon.get_resource_path(
                        os.path.join(
                            "images",
                            f"{common.gui.color_mode}_logo_text_bg.png")))))
        self.image_label.setFixedSize(322, 65)
        image_layout = QtWidgets.QVBoxLayout()
        image_layout.addWidget(self.image_label)
        self.image = QtWidgets.QWidget()
        self.image.setLayout(image_layout)

        # First launch widget
        self.first_launch_widget = AutoConnectFirstLaunchWidget(
            self.common, self.curr_settings)
        self.first_launch_widget.toggle_auto_connect.connect(
            self.toggle_auto_connect)
        self.first_launch_widget.connect_clicked.connect(
            self.first_launch_widget_connect_clicked)
        self.first_launch_widget.open_tor_settings.connect(
            self.open_tor_settings)
        self.first_launch_widget.show()

        # Use bridge widget
        self.use_bridge_widget = AutoConnectUseBridgeWidget(self.common)
        self.use_bridge_widget.connect_clicked.connect(
            self.use_bridge_connect_clicked)
        self.use_bridge_widget.try_again_clicked.connect(
            self.first_launch_widget_connect_clicked)
        self.use_bridge_widget.open_tor_settings.connect(
            self.open_tor_settings)
        self.use_bridge_widget.hide()

        # Tor connection widget
        self.tor_con = TorConnectionWidget(self.common, self.status_bar)
        self.tor_con.success.connect(self.tor_con_success)
        self.tor_con.fail.connect(self.tor_con_fail)
        self.tor_con.update_progress.connect(self.anim_stars.update)
        self.tor_con.update_progress.connect(self.anim_ship.update)
        self.tor_con.update_progress.connect(self.anim_smoke.update)
        self.tor_con.hide()

        # Layout
        content_layout = QtWidgets.QVBoxLayout()
        content_layout.addStretch()
        content_layout.addWidget(self.image)
        content_layout.addWidget(self.first_launch_widget)
        content_layout.addWidget(self.use_bridge_widget)
        content_layout.addWidget(self.tor_con)
        content_layout.addStretch()
        content_layout.setAlignment(QtCore.Qt.AlignCenter)
        content_widget = QtWidgets.QWidget()
        content_widget.setLayout(content_layout)

        self.layout = QtWidgets.QHBoxLayout()
        self.layout.addWidget(content_widget)
        self.layout.addStretch()

        self.setLayout(self.layout)
Пример #8
0
class AutoConnectTab(QtWidgets.QWidget):
    """
    Initial Tab that appears in the very beginning to ask user if
    should auto connect.
    """

    close_this_tab = QtCore.Signal()
    tor_is_connected = QtCore.Signal()
    tor_is_disconnected = QtCore.Signal()

    def __init__(self, common, tab_id, status_bar, window, parent=None):
        super(AutoConnectTab, self).__init__()
        self.common = common
        self.common.log("AutoConnectTab", "__init__")

        self.status_bar = status_bar
        self.tab_id = tab_id
        self.window = window
        self.parent = parent

        # Was auto connected?
        self.curr_settings = Settings(common)
        self.curr_settings.load()
        self.auto_connect_enabled = self.curr_settings.get("auto_connect")

        # Rocket ship animation images
        self.anim_stars = AnimStars(self, self.window)
        self.anim_ship = AnimShip(self, self.window)
        self.anim_smoke = AnimSmoke(self, self.window)

        # Onionshare logo
        self.image_label = QtWidgets.QLabel()
        self.image_label.setPixmap(
            QtGui.QPixmap.fromImage(
                QtGui.QImage(
                    GuiCommon.get_resource_path(
                        os.path.join(
                            "images",
                            f"{common.gui.color_mode}_logo_text_bg.png")))))
        self.image_label.setFixedSize(322, 65)
        image_layout = QtWidgets.QVBoxLayout()
        image_layout.addWidget(self.image_label)
        self.image = QtWidgets.QWidget()
        self.image.setLayout(image_layout)

        # First launch widget
        self.first_launch_widget = AutoConnectFirstLaunchWidget(
            self.common, self.curr_settings)
        self.first_launch_widget.toggle_auto_connect.connect(
            self.toggle_auto_connect)
        self.first_launch_widget.connect_clicked.connect(
            self.first_launch_widget_connect_clicked)
        self.first_launch_widget.open_tor_settings.connect(
            self.open_tor_settings)
        self.first_launch_widget.show()

        # Use bridge widget
        self.use_bridge_widget = AutoConnectUseBridgeWidget(self.common)
        self.use_bridge_widget.connect_clicked.connect(
            self.use_bridge_connect_clicked)
        self.use_bridge_widget.try_again_clicked.connect(
            self.first_launch_widget_connect_clicked)
        self.use_bridge_widget.open_tor_settings.connect(
            self.open_tor_settings)
        self.use_bridge_widget.hide()

        # Tor connection widget
        self.tor_con = TorConnectionWidget(self.common, self.status_bar)
        self.tor_con.success.connect(self.tor_con_success)
        self.tor_con.fail.connect(self.tor_con_fail)
        self.tor_con.update_progress.connect(self.anim_stars.update)
        self.tor_con.update_progress.connect(self.anim_ship.update)
        self.tor_con.update_progress.connect(self.anim_smoke.update)
        self.tor_con.hide()

        # Layout
        content_layout = QtWidgets.QVBoxLayout()
        content_layout.addStretch()
        content_layout.addWidget(self.image)
        content_layout.addWidget(self.first_launch_widget)
        content_layout.addWidget(self.use_bridge_widget)
        content_layout.addWidget(self.tor_con)
        content_layout.addStretch()
        content_layout.setAlignment(QtCore.Qt.AlignCenter)
        content_widget = QtWidgets.QWidget()
        content_widget.setLayout(content_layout)

        self.layout = QtWidgets.QHBoxLayout()
        self.layout.addWidget(content_widget)
        self.layout.addStretch()

        self.setLayout(self.layout)

    def check_autoconnect(self):
        """
        After rendering, check if autoconnect was clicked, then start connecting
        """
        self.common.log("AutoConnectTab", "autoconnect_checking")
        if self.auto_connect_enabled:
            self.first_launch_widget.enable_autoconnect_checkbox.setChecked(
                True)
            self.first_launch_widget_connect_clicked()

    def toggle_auto_connect(self):
        """
        Auto connect checkbox clicked
        """
        self.common.log("AutoConnectTab", "autoconnect_checkbox_clicked")
        self.curr_settings.set(
            "auto_connect",
            self.first_launch_widget.enable_autoconnect_checkbox.isChecked(),
        )
        self.curr_settings.save()

    def open_tor_settings(self):
        self.parent.open_settings_tab(from_autoconnect=True, active_tab="tor")

    def first_launch_widget_connect_clicked(self):
        """
        Connect button in first launch widget clicked. Try to connect to tor.
        """
        self.common.log("AutoConnectTab",
                        "first_launch_widget_connect_clicked")
        self.first_launch_widget.hide_buttons()

        self.tor_con.show()
        self.tor_con.start(self.curr_settings)

    def _got_bridges(self):
        self.use_bridge_widget.progress.hide()
        self.use_bridge_widget.progress_label.hide()
        # Try and connect again
        self.common.log(
            "AutoConnectTab",
            "_got_bridges",
            "Got bridges. Trying to reconnect to Tor",
        )
        self.tor_con.show()
        self.tor_con.start(self.curr_settings)

    def _got_no_bridges(self):
        # If we got no bridges, even after trying the default bridges
        # provided by the Censorship API, try connecting again using
        # our built-in obfs4 bridges
        self.curr_settings.set("bridges_type", "built-in")
        self.curr_settings.set("bridges_builtin_pt", "obfs4")
        self.curr_settings.set("bridges_enabled", True)
        self.curr_settings.save()

        self._got_bridges()

    def _censorship_progress_update(self, progress, summary):
        self.use_bridge_widget.progress.setValue(int(progress))
        self.use_bridge_widget.progress_label.setText(
            f"<strong>{strings._('gui_autoconnect_circumventing_censorship')}</strong><br>{summary}"
        )

    def network_connection_error(self):
        """
        Display an error if there simply seems no network connection.
        """
        self.use_bridge_widget.connection_status_label.setText(
            strings._("gui_autoconnect_failed_to_connect_to_tor"))
        self.use_bridge_widget.progress.hide()
        self.use_bridge_widget.progress_label.hide()
        self.use_bridge_widget.error_label.show()
        self.use_bridge_widget.country_combobox.setEnabled(True)
        self.use_bridge_widget.show_buttons()
        self.use_bridge_widget.show()

    def use_bridge_connect_clicked(self):
        """
        Connect button in use bridge widget clicked.
        """
        self.common.log(
            "AutoConnectTab",
            "use_bridge_connect_clicked",
            "Trying to automatically obtain bridges",
        )
        self.use_bridge_widget.hide_buttons()
        self.use_bridge_widget.progress.show()
        self.use_bridge_widget.progress_label.show()

        if self.use_bridge_widget.detect_automatic_radio.isChecked():
            country = False
        else:
            country = self.use_bridge_widget.country_combobox.currentData(
            ).lower()

        self._censorship_progress_update(
            50,
            strings._(
                "gui_autoconnect_circumventing_censorship_starting_meek"))
        try:
            self.common.gui.meek.start()
            self.censorship_circumvention = CensorshipCircumvention(
                self.common, self.common.gui.meek)
            self._censorship_progress_update(
                75,
                strings.
                _("gui_autoconnect_circumventing_censorship_requesting_bridges"
                  ),
            )
            bridge_settings = self.censorship_circumvention.request_settings(
                country=country)

            if not bridge_settings:
                # Fall back to trying the default bridges from the API
                self.common.log(
                    "AutoConnectTab",
                    "use_bridge_connect_clicked",
                    "Falling back to trying default bridges provided by the Censorship Circumvention API",
                )
                bridge_settings = (
                    self.censorship_circumvention.request_default_bridges())

            self.common.gui.meek.cleanup()

            if bridge_settings and self.censorship_circumvention.save_settings(
                    self.curr_settings, bridge_settings):
                self._censorship_progress_update(
                    100,
                    strings.
                    _("gui_autoconnect_circumventing_censorship_got_bridges"),
                )
                self._got_bridges()
            else:
                self._got_no_bridges()
        except (
                MeekNotRunning,
                MeekNotFound,
        ) as e:
            self._got_no_bridges()
        except CensorshipCircumventionError as e:
            self.common.log(
                "AutoConnectTab",
                "use_bridge_connect_clicked",
                "Request to the Tor Censorship Circumvention API failed. No network connection?",
            )
            self.network_connection_error()

    def check_for_updates(self):
        """
        Check for OnionShare updates in a new thread, if enabled.
        """
        if self.common.platform == "Windows" or self.common.platform == "Darwin":
            if self.common.settings.get("use_autoupdate"):

                def update_available(update_url, installed_version,
                                     latest_version):
                    Alert(
                        self.common,
                        strings._("update_available").format(
                            update_url, installed_version, latest_version),
                    )

                self.update_thread = UpdateThread(self.common,
                                                  self.common.gui.onion)
                self.update_thread.update_available.connect(update_available)
                self.update_thread.start()

    def tor_con_success(self):
        """
        Finished testing tor connection.
        """
        self.tor_con.hide()
        self.first_launch_widget.show_buttons()
        self.use_bridge_widget.show_buttons()
        self.use_bridge_widget.progress.hide()
        self.use_bridge_widget.progress_label.hide()

        if self.common.gui.onion.is_authenticated(
        ) and not self.tor_con.wasCanceled():
            # Tell the tabs that Tor is connected
            self.tor_is_connected.emit()
            # After connecting to Tor, check for updates
            self.check_for_updates()
            # Close the tab
            self.close_this_tab.emit()

    def tor_con_fail(self, msg):
        """
        Finished testing tor connection.
        """
        self.tor_con.hide()

        # If there is a message, update the text of the bridge widget
        if msg:
            self.use_bridge_widget.connection_error_message.setText(msg)

        # If we're on first launch, check if wasCanceled
        # If cancelled, stay in first launch widget and show buttons
        # Else, switch to use bridge
        if self.first_launch_widget.isVisible():
            if self.tor_con.wasCanceled():
                self.first_launch_widget.show_buttons()
            else:
                self.first_launch_widget.show_buttons()
                self.first_launch_widget.hide()
                self.use_bridge_widget.show()
        else:
            self.use_bridge_widget.show_buttons()

    def reload_settings(self):
        """
        Reload the latest Tor settings, and reset to show the
        first-launch widget if it had been hidden.
        """
        self.curr_settings.load()
        self.auto_connect_enabled = self.curr_settings.get("auto_connect")
        self.first_launch_widget.enable_autoconnect_checkbox.setChecked(
            self.auto_connect_enabled)
        self.use_bridge_widget.hide()
        self.first_launch_widget.show_buttons()
        self.first_launch_widget.show()
Пример #9
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

        # Theme
        theme_index = self.theme_combobox.currentIndex()
        settings.set("theme", theme_index)

        # 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
Пример #10
0
class SettingsDialog(QtWidgets.QDialog):
    """
    Settings dialog.
    """

    settings_saved = QtCore.Signal()

    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(GuiCommon.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, locale)
        language_layout = QtWidgets.QHBoxLayout()
        language_layout.addWidget(language_label)
        language_layout.addWidget(self.language_combobox)
        language_layout.addStretch()

        #Theme Settings
        theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label"))
        self.theme_combobox = QtWidgets.QComboBox()
        theme_choices = [
            strings._("gui_settings_theme_auto"),
            strings._("gui_settings_theme_light"),
            strings._("gui_settings_theme_dark")
        ]
        self.theme_combobox.addItems(theme_choices)
        theme_layout = QtWidgets.QHBoxLayout()
        theme_layout.addWidget(theme_label)
        theme_layout.addWidget(self.theme_combobox)
        theme_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.gui.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.gui.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.addLayout(theme_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(locale)
        self.language_combobox.setCurrentIndex(locale_index)

        theme_choice = self.old_settings.get("theme")
        self.theme_combobox.setCurrentIndex(theme_choice)

        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,
                get_tor_paths=self.common.gui.get_tor_paths,
            )
            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,
                BundledTorTimeout,
                BundledTorBroken,
                TorTooOldEphemeral,
                TorTooOldStealth,
                PortNotAvailable,
        ) as e:
            message = self.common.gui.get_translated_tor_error(e)
            Alert(
                self.common,
                message,
                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.common.gui.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)

            # If color mode changed, inform user they need to restart OnionShare
            if changed(settings, self.old_settings, ["theme"]):
                notice = strings._("gui_color_mode_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

        # Theme
        theme_index = self.theme_combobox.currentIndex()
        settings.set("theme", theme_index)

        # 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)
Пример #11
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(locale)
        self.language_combobox.setCurrentIndex(locale_index)

        theme_choice = self.old_settings.get("theme")
        self.theme_combobox.setCurrentIndex(theme_choice)

        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)
Пример #12
0
    def check(self, force=False):
        self.common.log("UpdateChecker", "check", f"force={force}")
        # Load the settings
        settings = Settings(self.common)
        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 = f"OnionShare {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",
                                f"loading http://{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 = f"GET {path} HTTP/1.0\r\n"
                http_request += f"Host: {onion_domain}\r\n"
                http_request += f"User-Agent: {user_agent}\r\n"
                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",
                    f"latest OnionShare version: {latest_version}",
                )

            except Exception as e:
                self.common.log("UpdateChecker", "check", str(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://onionshare.org"
            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()
Пример #13
0
    def settings_from_fields(self):
        """
        Return a Settings object that's full of values from the settings dialog.
        """
        self.common.log("TorSettingsTab", "settings_from_fields")
        settings = Settings(self.common)
        settings.load()  # To get the last update timestamp

        # autoconnect
        settings.set("auto_connect", self.autoconnect_checkbox.isChecked())

        # 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")

        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_checkbox.checkState(
        ) == QtCore.Qt.Checked:
            settings.set("auth_type", "no_auth")
        else:
            settings.set("auth_type", "password")

        settings.set("auth_password",
                     self.authenticate_password_extras_password.text())

        # Whether we use bridges
        if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked:
            settings.set("bridges_enabled", True)

            if self.bridge_builtin_radio.isChecked():
                settings.set("bridges_type", "built-in")

                selection = self.bridge_builtin_dropdown.currentText()
                settings.set("bridges_builtin_pt", selection)

            if self.bridge_moat_radio.isChecked():
                settings.set("bridges_type", "moat")
                moat_bridges = self.bridge_moat_textbox.toPlainText()
                if (self.connection_type_bundled_radio.isChecked()
                        and moat_bridges.strip() == ""):
                    self.error_label.setText(
                        strings._("gui_settings_moat_bridges_invalid"))
                    return False

                settings.set("bridges_moat", moat_bridges)

            if self.bridge_custom_radio.isChecked():
                settings.set("bridges_type", "custom")

                bridges = self.bridge_custom_textbox.toPlainText().split("\n")
                bridges_valid = self.common.check_bridges_valid(bridges)
                if bridges_valid:
                    new_bridges = "\n".join(bridges_valid) + "\n"
                    settings.set("bridges_custom", new_bridges)
                else:
                    self.error_label.setText(
                        strings._("gui_settings_tor_bridges_invalid"))
                    return False
        else:
            settings.set("bridges_enabled", False)

        return settings
Пример #14
0
    def reload_settings(self):
        # Load settings, and fill them in
        self.old_settings = Settings(self.common)
        self.old_settings.load()

        # Check if autoconnect was enabled
        if self.old_settings.get("auto_connect"):
            self.autoconnect_checkbox.setCheckState(QtCore.Qt.Checked)

        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_checkbox.setCheckState(QtCore.Qt.Checked)
        else:
            self.authenticate_no_auth_checkbox.setChecked(QtCore.Qt.Unchecked)
        self.authenticate_password_extras_password.setText(
            self.old_settings.get("auth_password"))

        if self.old_settings.get("bridges_enabled"):
            self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked)
            self.bridge_settings.show()

            bridges_type = self.old_settings.get("bridges_type")
            if bridges_type == "built-in":
                self.bridge_builtin_radio.setChecked(True)
                self.bridge_builtin_dropdown.show()
                self.bridge_moat_radio.setChecked(False)
                self.bridge_moat_textbox_options.hide()
                self.bridge_custom_radio.setChecked(False)
                self.bridge_custom_textbox_options.hide()

                bridges_builtin_pt = self.old_settings.get(
                    "bridges_builtin_pt")
                if bridges_builtin_pt == "obfs4":
                    self.bridge_builtin_dropdown.setCurrentText("obfs4")
                elif bridges_builtin_pt == "meek-azure":
                    self.bridge_builtin_dropdown.setCurrentText("meek-azure")
                else:
                    self.bridge_builtin_dropdown.setCurrentText("snowflake")

                self.bridge_moat_textbox_options.hide()
                self.bridge_custom_textbox_options.hide()

            elif bridges_type == "moat":
                self.bridge_builtin_radio.setChecked(False)
                self.bridge_builtin_dropdown.hide()
                self.bridge_moat_radio.setChecked(True)
                self.bridge_moat_textbox_options.show()
                self.bridge_custom_radio.setChecked(False)
                self.bridge_custom_textbox_options.hide()

            else:
                self.bridge_builtin_radio.setChecked(False)
                self.bridge_builtin_dropdown.hide()
                self.bridge_moat_radio.setChecked(False)
                self.bridge_moat_textbox_options.hide()
                self.bridge_custom_radio.setChecked(True)
                self.bridge_custom_textbox_options.show()

            bridges_moat = self.old_settings.get("bridges_moat")
            self.bridge_moat_textbox.document().setPlainText(bridges_moat)
            bridges_custom = self.old_settings.get("bridges_custom")
            self.bridge_custom_textbox.document().setPlainText(bridges_custom)

        else:
            self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
            self.bridge_settings.hide()
Пример #15
0
class TorSettingsTab(QtWidgets.QWidget):
    """
    Settings dialog.
    """

    close_this_tab = QtCore.Signal()
    tor_is_connected = QtCore.Signal()
    tor_is_disconnected = QtCore.Signal()

    def __init__(
        self,
        common,
        tab_id,
        are_tabs_active,
        status_bar,
        from_autoconnect=False,
        parent=None,
    ):
        super(TorSettingsTab, self).__init__()

        self.common = common
        self.common.log("TorSettingsTab", "__init__")

        self.status_bar = status_bar
        self.meek = Meek(common, get_tor_paths=self.common.gui.get_tor_paths)

        self.system = platform.system()
        self.tab_id = tab_id
        self.parent = parent
        self.from_autoconnect = from_autoconnect

        # 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

        (
            self.tor_path,
            self.tor_geo_ip_file_path,
            self.tor_geo_ipv6_file_path,
            self.obfs4proxy_file_path,
            self.snowflake_file_path,
            self.meek_client_file_path,
        ) = self.common.gui.get_tor_paths()

        bridges_label = QtWidgets.QLabel(
            strings._("gui_settings_tor_bridges_label"))
        bridges_label.setWordWrap(True)

        self.bridge_use_checkbox = QtWidgets.QCheckBox(
            strings._("gui_settings_bridge_use_checkbox"))
        self.bridge_use_checkbox.stateChanged.connect(
            self.bridge_use_checkbox_state_changed)

        # Built-in bridge
        self.bridge_builtin_radio = QtWidgets.QRadioButton(
            strings._("gui_settings_bridge_radio_builtin"))
        self.bridge_builtin_radio.toggled.connect(
            self.bridge_builtin_radio_toggled)
        self.bridge_builtin_dropdown = QtWidgets.QComboBox()
        self.bridge_builtin_dropdown.currentTextChanged.connect(
            self.bridge_builtin_dropdown_changed)
        if self.obfs4proxy_file_path and os.path.isfile(
                self.obfs4proxy_file_path):
            self.bridge_builtin_dropdown.addItem("obfs4")
            self.bridge_builtin_dropdown.addItem("meek-azure")
        if self.snowflake_file_path and os.path.isfile(
                self.snowflake_file_path):
            self.bridge_builtin_dropdown.addItem("snowflake")

        # Request a bridge from torproject.org (moat)
        self.bridge_moat_radio = QtWidgets.QRadioButton(
            strings._("gui_settings_bridge_moat_radio_option"))
        self.bridge_moat_radio.toggled.connect(self.bridge_moat_radio_toggled)
        self.bridge_moat_button = QtWidgets.QPushButton(
            strings._("gui_settings_bridge_moat_button"))
        self.bridge_moat_button.clicked.connect(
            self.bridge_moat_button_clicked)
        self.bridge_moat_textbox = QtWidgets.QPlainTextEdit()
        self.bridge_moat_textbox.setMinimumHeight(100)
        self.bridge_moat_textbox.setMaximumHeight(100)
        self.bridge_moat_textbox.setReadOnly(True)
        self.bridge_moat_textbox.setWordWrapMode(QtGui.QTextOption.NoWrap)
        bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout()
        bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_button)
        bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_textbox)
        self.bridge_moat_textbox_options = QtWidgets.QWidget()
        self.bridge_moat_textbox_options.setLayout(
            bridge_moat_textbox_options_layout)
        self.bridge_moat_textbox_options.hide()

        # Custom bridges radio and textbox
        self.bridge_custom_radio = QtWidgets.QRadioButton(
            strings._("gui_settings_bridge_custom_radio_option"))
        self.bridge_custom_radio.toggled.connect(
            self.bridge_custom_radio_toggled)
        self.bridge_custom_textbox = QtWidgets.QPlainTextEdit()
        self.bridge_custom_textbox.setMinimumHeight(100)
        self.bridge_custom_textbox.setMaximumHeight(100)
        self.bridge_custom_textbox.setPlaceholderText(
            strings._("gui_settings_bridge_custom_placeholder"))

        bridge_custom_textbox_options_layout = QtWidgets.QVBoxLayout()
        bridge_custom_textbox_options_layout.addWidget(
            self.bridge_custom_textbox)

        self.bridge_custom_textbox_options = QtWidgets.QWidget()
        self.bridge_custom_textbox_options.setLayout(
            bridge_custom_textbox_options_layout)
        self.bridge_custom_textbox_options.hide()

        # Bridge settings layout
        bridge_settings_layout = QtWidgets.QVBoxLayout()
        bridge_settings_layout.addWidget(self.bridge_builtin_radio)
        bridge_settings_layout.addWidget(self.bridge_builtin_dropdown)
        bridge_settings_layout.addWidget(self.bridge_moat_radio)
        bridge_settings_layout.addWidget(self.bridge_moat_textbox_options)
        bridge_settings_layout.addWidget(self.bridge_custom_radio)
        bridge_settings_layout.addWidget(self.bridge_custom_textbox_options)
        self.bridge_settings = QtWidgets.QWidget()
        self.bridge_settings.setLayout(bridge_settings_layout)

        # Bridges layout/widget
        bridges_layout = QtWidgets.QVBoxLayout()
        bridges_layout.addWidget(bridges_label)
        bridges_layout.addWidget(self.bridge_use_checkbox)
        bridges_layout.addWidget(self.bridge_settings)

        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
        self.authenticate_no_auth_checkbox = QtWidgets.QCheckBox(
            strings._("gui_settings_authenticate_no_auth_option"))
        self.authenticate_no_auth_checkbox.toggled.connect(
            self.authenticate_no_auth_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()

        # Group for Tor settings
        tor_settings_layout = QtWidgets.QVBoxLayout()
        tor_settings_layout.addWidget(self.connection_type_control_port_extras)
        tor_settings_layout.addWidget(self.connection_type_socket_file_extras)
        tor_settings_layout.addWidget(self.connection_type_socks)
        tor_settings_layout.addWidget(self.authenticate_no_auth_checkbox)
        tor_settings_layout.addWidget(self.authenticate_password_extras)
        self.tor_settings_group = QtWidgets.QGroupBox(
            strings._("gui_settings_controller_extras_label"))
        self.tor_settings_group.setLayout(tor_settings_layout)
        self.tor_settings_group.hide()

        # 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_layout.addStretch()
        connection_type_radio_group = QtWidgets.QGroupBox(
            strings._("gui_settings_connection_type_label"))
        connection_type_radio_group.setLayout(
            connection_type_radio_group_layout)

        # Quickstart settings
        self.autoconnect_checkbox = QtWidgets.QCheckBox(
            strings._("gui_enable_autoconnect_checkbox"))
        self.autoconnect_checkbox.toggled.connect(self.autoconnect_toggled)
        left_column_settings = QtWidgets.QVBoxLayout()
        connection_type_radio_group.setFixedHeight(300)
        left_column_settings.addWidget(connection_type_radio_group)
        left_column_settings.addSpacing(20)
        left_column_settings.addWidget(self.autoconnect_checkbox)
        left_column_settings.addStretch()
        left_column_settings.setContentsMargins(0, 0, 0, 0)
        left_column_setting_widget = QtWidgets.QWidget()
        left_column_setting_widget.setLayout(left_column_settings)

        # 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()

        # Connection type layout
        connection_type_layout = QtWidgets.QVBoxLayout()
        connection_type_layout.addWidget(self.tor_settings_group)
        connection_type_layout.addWidget(
            self.connection_type_bridges_radio_group)
        connection_type_layout.addStretch()

        # Settings are in columns
        columns_layout = QtWidgets.QHBoxLayout()
        columns_layout.addWidget(left_column_setting_widget)
        columns_layout.addSpacing(20)
        columns_layout.addLayout(connection_type_layout, stretch=1)
        columns_wrapper = QtWidgets.QWidget()
        columns_wrapper.setFixedHeight(400)
        columns_wrapper.setLayout(columns_layout)

        # Tor connection widget
        self.tor_con = TorConnectionWidget(self.common, self.status_bar)
        self.tor_con.success.connect(self.tor_con_success)
        self.tor_con.fail.connect(self.tor_con_fail)
        self.tor_con.hide()
        self.tor_con_type = None

        # Error label
        self.error_label = QtWidgets.QLabel()
        self.error_label.setStyleSheet(
            self.common.gui.css["tor_settings_error"])
        self.error_label.setWordWrap(True)

        # Buttons
        self.test_tor_button = QtWidgets.QPushButton(
            strings._("gui_settings_connection_type_test_button"))
        self.test_tor_button.clicked.connect(self.test_tor_clicked)
        self.save_button = QtWidgets.QPushButton(
            strings._("gui_settings_button_save"))
        self.save_button.clicked.connect(self.save_clicked)
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.addWidget(self.error_label, stretch=1)
        buttons_layout.addSpacing(20)
        buttons_layout.addWidget(self.test_tor_button)
        buttons_layout.addWidget(self.save_button)

        # Main layout
        main_layout = QtWidgets.QVBoxLayout()
        main_layout.addWidget(columns_wrapper)
        main_layout.addStretch()
        main_layout.addWidget(self.tor_con)
        main_layout.addStretch()
        main_layout.addLayout(buttons_layout)
        self.main_widget = QtWidgets.QWidget()
        self.main_widget.setLayout(main_layout)

        # Tabs are active label
        active_tabs_label = QtWidgets.QLabel(
            strings._("gui_settings_stop_active_tabs_label"))
        active_tabs_label.setAlignment(QtCore.Qt.AlignHCenter)

        # Active tabs layout
        active_tabs_layout = QtWidgets.QVBoxLayout()
        active_tabs_layout.addStretch()
        active_tabs_layout.addWidget(active_tabs_label)
        active_tabs_layout.addStretch()
        self.active_tabs_widget = QtWidgets.QWidget()
        self.active_tabs_widget.setLayout(active_tabs_layout)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.main_widget)
        layout.addWidget(self.active_tabs_widget)
        self.setLayout(layout)

        self.active_tabs_changed(are_tabs_active)
        self.reload_settings()

    def reload_settings(self):
        # Load settings, and fill them in
        self.old_settings = Settings(self.common)
        self.old_settings.load()

        # Check if autoconnect was enabled
        if self.old_settings.get("auto_connect"):
            self.autoconnect_checkbox.setCheckState(QtCore.Qt.Checked)

        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_checkbox.setCheckState(QtCore.Qt.Checked)
        else:
            self.authenticate_no_auth_checkbox.setChecked(QtCore.Qt.Unchecked)
        self.authenticate_password_extras_password.setText(
            self.old_settings.get("auth_password"))

        if self.old_settings.get("bridges_enabled"):
            self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked)
            self.bridge_settings.show()

            bridges_type = self.old_settings.get("bridges_type")
            if bridges_type == "built-in":
                self.bridge_builtin_radio.setChecked(True)
                self.bridge_builtin_dropdown.show()
                self.bridge_moat_radio.setChecked(False)
                self.bridge_moat_textbox_options.hide()
                self.bridge_custom_radio.setChecked(False)
                self.bridge_custom_textbox_options.hide()

                bridges_builtin_pt = self.old_settings.get(
                    "bridges_builtin_pt")
                if bridges_builtin_pt == "obfs4":
                    self.bridge_builtin_dropdown.setCurrentText("obfs4")
                elif bridges_builtin_pt == "meek-azure":
                    self.bridge_builtin_dropdown.setCurrentText("meek-azure")
                else:
                    self.bridge_builtin_dropdown.setCurrentText("snowflake")

                self.bridge_moat_textbox_options.hide()
                self.bridge_custom_textbox_options.hide()

            elif bridges_type == "moat":
                self.bridge_builtin_radio.setChecked(False)
                self.bridge_builtin_dropdown.hide()
                self.bridge_moat_radio.setChecked(True)
                self.bridge_moat_textbox_options.show()
                self.bridge_custom_radio.setChecked(False)
                self.bridge_custom_textbox_options.hide()

            else:
                self.bridge_builtin_radio.setChecked(False)
                self.bridge_builtin_dropdown.hide()
                self.bridge_moat_radio.setChecked(False)
                self.bridge_moat_textbox_options.hide()
                self.bridge_custom_radio.setChecked(True)
                self.bridge_custom_textbox_options.show()

            bridges_moat = self.old_settings.get("bridges_moat")
            self.bridge_moat_textbox.document().setPlainText(bridges_moat)
            bridges_custom = self.old_settings.get("bridges_custom")
            self.bridge_custom_textbox.document().setPlainText(bridges_custom)

        else:
            self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
            self.bridge_settings.hide()

    def autoconnect_toggled(self):
        """
        Auto connect checkbox clicked
        """
        self.common.log("TorSettingsTab", "autoconnect_checkbox_clicked")

    def active_tabs_changed(self, are_tabs_active):
        if are_tabs_active:
            self.main_widget.hide()
            self.active_tabs_widget.show()
        else:
            self.main_widget.show()
            self.active_tabs_widget.hide()

    def connection_type_bundled_toggled(self, checked):
        """
        Connection type bundled was toggled
        """
        self.common.log("TorSettingsTab", "connection_type_bundled_toggled")
        if checked:
            self.tor_settings_group.hide()
            self.connection_type_socks.hide()
            self.connection_type_bridges_radio_group.show()

    def bridge_use_checkbox_state_changed(self, state):
        """
        'Use a bridge' checkbox changed
        """
        if state == QtCore.Qt.Checked:
            self.bridge_settings.show()
            self.bridge_builtin_radio.click()
            self.bridge_builtin_dropdown.setCurrentText("obfs4")
        else:
            self.bridge_settings.hide()

    def bridge_builtin_radio_toggled(self, checked):
        """
        'Select a built-in bridge' radio button toggled
        """
        if checked:
            self.bridge_builtin_dropdown.show()
            self.bridge_custom_textbox_options.hide()
            self.bridge_moat_textbox_options.hide()

    def bridge_builtin_dropdown_changed(self, selection):
        """
        Build-in bridge selection changed
        """
        if selection == "meek-azure":
            # Alert the user about meek's costliness if it looks like they're turning it on
            if not self.old_settings.get("bridges_builtin_pt") == "meek-azure":
                Alert(
                    self.common,
                    strings._("gui_settings_meek_lite_expensive_warning"),
                    QtWidgets.QMessageBox.Warning,
                )

    def bridge_moat_radio_toggled(self, checked):
        """
        Moat (request bridge) bridges option was toggled. If checked, show moat bridge options.
        """
        if checked:
            self.bridge_builtin_dropdown.hide()
            self.bridge_custom_textbox_options.hide()
            self.bridge_moat_textbox_options.show()

    def bridge_moat_button_clicked(self):
        """
        Request new bridge button clicked
        """
        self.common.log("TorSettingsTab", "bridge_moat_button_clicked")

        moat_dialog = MoatDialog(self.common, self.meek)
        moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
        moat_dialog.exec_()

    def bridge_moat_got_bridges(self, bridges):
        """
        Got new bridges from moat
        """
        self.common.log("TorSettingsTab", "bridge_moat_got_bridges")
        self.bridge_moat_textbox.document().setPlainText(bridges)
        self.bridge_moat_textbox.show()

    def bridge_custom_radio_toggled(self, checked):
        """
        Custom bridges option was toggled. If checked, show custom bridge options.
        """
        if checked:
            self.bridge_builtin_dropdown.hide()
            self.bridge_moat_textbox_options.hide()
            self.bridge_custom_textbox_options.show()

    def connection_type_automatic_toggled(self, checked):
        """
        Connection type automatic was toggled. If checked, hide authentication fields.
        """
        self.common.log("TorSettingsTab", "connection_type_automatic_toggled")
        if checked:
            self.tor_settings_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("TorSettingsTab",
                        "connection_type_control_port_toggled")
        if checked:
            self.tor_settings_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("TorSettingsTab",
                        "connection_type_socket_file_toggled")
        if checked:
            self.tor_settings_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("TorSettingsTab", "authenticate_no_auth_toggled")
        if checked:
            self.authenticate_password_extras.hide()
        else:
            self.authenticate_password_extras.show()

    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("TorSettingsTab", "test_tor_clicked")

        self.error_label.setText("")

        settings = self.settings_from_fields()
        if not settings:
            return

        self.test_tor_button.hide()
        self.save_button.hide()

        self.test_onion = Onion(
            self.common,
            use_tmp_dir=True,
            get_tor_paths=self.common.gui.get_tor_paths,
        )

        self.tor_con_type = "test"
        self.tor_con.show()
        self.tor_con.start(settings, True, self.test_onion)

    def save_clicked(self):
        """
        Save button clicked. Save current settings to disk.
        """
        self.common.log("TorSettingsTab", "save_clicked")

        self.error_label.setText("")

        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:
            # 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 and not (
                    self.from_autoconnect
                    and not settings.get("auto_connect")):
                if self.common.gui.onion.is_authenticated():
                    self.common.log("TorSettingsTab", "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",
                            "bridges_enabled",
                            "bridges_type",
                            "bridges_builtin_pt",
                            "bridges_moat",
                            "bridges_custom",
                        ],
                    ):

                        reboot_onion = True

                else:
                    self.common.log("TorSettingsTab", "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:
                    # Tell the tabs that Tor is disconnected
                    self.tor_is_disconnected.emit()

                    # Reinitialize the Onion object
                    self.common.log("TorSettingsTab", "save_clicked",
                                    "rebooting the Onion")
                    self.common.gui.onion.cleanup()

                    self.test_tor_button.hide()
                    self.save_button.hide()

                    self.tor_con_type = "save"
                    self.tor_con.show()
                    self.tor_con.start(settings)
                else:
                    self.parent.close_this_tab.emit()
            else:
                self.parent.close_this_tab.emit()

    def tor_con_success(self):
        """
        Finished testing tor connection.
        """
        self.tor_con.hide()
        self.test_tor_button.show()
        self.save_button.show()

        if self.tor_con_type == "test":
            Alert(
                self.common,
                strings._("settings_test_success").format(
                    self.test_onion.tor_version,
                    self.test_onion.supports_ephemeral,
                    self.test_onion.supports_stealth,
                    self.test_onion.supports_v3_onions,
                ),
                title=strings._("gui_settings_connection_type_test_button"),
            )
            self.test_onion.cleanup()

        elif self.tor_con_type == "save":
            if (self.common.gui.onion.is_authenticated()
                    and not self.tor_con.wasCanceled()):
                # Tell the tabs that Tor is connected
                self.tor_is_connected.emit()
                # Close the tab
                self.parent.close_this_tab.emit()

        self.tor_con_type = None

    def tor_con_fail(self, msg):
        """
        Finished testing tor connection.
        """
        self.tor_con.hide()
        self.test_tor_button.show()
        self.save_button.show()

        self.error_label.setText(msg)

        if self.tor_con_type == "test":
            self.test_onion.cleanup()

        self.tor_con_type = None

    def settings_from_fields(self):
        """
        Return a Settings object that's full of values from the settings dialog.
        """
        self.common.log("TorSettingsTab", "settings_from_fields")
        settings = Settings(self.common)
        settings.load()  # To get the last update timestamp

        # autoconnect
        settings.set("auto_connect", self.autoconnect_checkbox.isChecked())

        # 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")

        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_checkbox.checkState(
        ) == QtCore.Qt.Checked:
            settings.set("auth_type", "no_auth")
        else:
            settings.set("auth_type", "password")

        settings.set("auth_password",
                     self.authenticate_password_extras_password.text())

        # Whether we use bridges
        if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked:
            settings.set("bridges_enabled", True)

            if self.bridge_builtin_radio.isChecked():
                settings.set("bridges_type", "built-in")

                selection = self.bridge_builtin_dropdown.currentText()
                settings.set("bridges_builtin_pt", selection)

            if self.bridge_moat_radio.isChecked():
                settings.set("bridges_type", "moat")
                moat_bridges = self.bridge_moat_textbox.toPlainText()
                if (self.connection_type_bundled_radio.isChecked()
                        and moat_bridges.strip() == ""):
                    self.error_label.setText(
                        strings._("gui_settings_moat_bridges_invalid"))
                    return False

                settings.set("bridges_moat", moat_bridges)

            if self.bridge_custom_radio.isChecked():
                settings.set("bridges_type", "custom")

                bridges = self.bridge_custom_textbox.toPlainText().split("\n")
                bridges_valid = self.common.check_bridges_valid(bridges)
                if bridges_valid:
                    new_bridges = "\n".join(bridges_valid) + "\n"
                    settings.set("bridges_custom", new_bridges)
                else:
                    self.error_label.setText(
                        strings._("gui_settings_tor_bridges_invalid"))
                    return False
        else:
            settings.set("bridges_enabled", False)

        return settings

    def closeEvent(self, e):
        self.common.log("TorSettingsTab", "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(
                    "TorSettingsTab",
                    "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 settings_have_changed(self):
        # Global settings have changed
        self.common.log("TorSettingsTab", "settings_have_changed")
    def settings_from_fields(self):
        """
        Return a Settings object that's full of values from the settings dialog.
        """
        self.common.log("TorSettingsTab", "settings_from_fields")
        settings = Settings(self.common)
        settings.load()  # To get the last update timestamp

        # 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")

        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_checkbox.checkState() == QtCore.Qt.Checked:
            settings.set("auth_type", "no_auth")
        else:
            settings.set("auth_type", "password")

        settings.set("auth_password", self.authenticate_password_extras_password.text())

        # Whether we use bridges
        if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked:
            settings.set("bridges_enabled", True)

            if self.bridge_builtin_radio.isChecked():
                settings.set("bridges_type", "built-in")

                selection = self.bridge_builtin_dropdown.currentText()
                settings.set("bridges_builtin_pt", selection)

            if self.bridge_moat_radio.isChecked():
                settings.set("bridges_type", "moat")
                moat_bridges = self.bridge_moat_textbox.toPlainText()
                if (
                    self.connection_type_bundled_radio.isChecked()
                    and moat_bridges.strip() == ""
                ):
                    self.error_label.setText(
                        strings._("gui_settings_moat_bridges_invalid")
                    )
                    return False

                settings.set("bridges_moat", moat_bridges)

            if self.bridge_custom_radio.isChecked():
                settings.set("bridges_type", "custom")

                new_bridges = []
                bridges = self.bridge_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=(.+)"
                        )
                        snowflake_pattern = re.compile(
                            "(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
                        )
                        if (
                            ipv4_pattern.match(bridge)
                            or ipv6_pattern.match(bridge)
                            or meek_lite_pattern.match(bridge)
                            or snowflake_pattern.match(bridge)
                        ):
                            new_bridges.append(bridge)
                            bridges_valid = True

                if bridges_valid:
                    new_bridges = "\n".join(new_bridges) + "\n"
                    settings.set("bridges_custom", new_bridges)
                else:
                    self.error_label.setText(
                        strings._("gui_settings_tor_bridges_invalid")
                    )
                    return False
        else:
            settings.set("bridges_enabled", False)

        return settings