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