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