[Pkg-privacy-commits] [onionshare] 144/256: Created update_checker module, and logic to load http://elx57ue5uyfplgva.onion/latest-version.txt (this is the OnionShare website's onion site) to check for updates. Also added UX in the settings dialog to force checking for updates. Does not actually do so automatically yet.

Ulrike Uhlig ulrike at moszumanska.debian.org
Fri May 26 12:53:29 UTC 2017


This is an automated email from the git hooks/post-receive script.

ulrike pushed a commit to branch master
in repository onionshare.

commit a116d3ae60542566e7e1aaa40a7059968deb51f4
Author: Micah Lee <micah at micahflee.com>
Date:   Sat Apr 15 18:04:05 2017 -0700

    Created update_checker module, and logic to load http://elx57ue5uyfplgva.onion/latest-version.txt (this is the OnionShare website's onion site) to check for updates. Also added UX in the settings dialog to force checking for updates. Does not actually do so automatically yet.
---
 install/requirements-windows.txt  |   1 +
 install/requirements.txt          |   1 +
 onionshare_gui/settings_dialog.py |  92 ++++++++++++++++++++---------
 onionshare_gui/update_checker.py  | 120 ++++++++++++++++++++++++++++++++++++++
 share/locale/en.json              |   9 ++-
 5 files changed, 195 insertions(+), 28 deletions(-)

diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt
index 37b77e0..9c08e12 100644
--- a/install/requirements-windows.txt
+++ b/install/requirements-windows.txt
@@ -8,6 +8,7 @@ pefile==2016.3.28
 PyInstaller==3.2.1
 pypiwin32==219
 PyQt5==5.8
+PySocks==1.6.7
 sip==4.19.1
 stem==1.5.4
 Werkzeug==0.11.15
diff --git a/install/requirements.txt b/install/requirements.txt
index bfc7f93..a046504 100644
--- a/install/requirements.txt
+++ b/install/requirements.txt
@@ -5,6 +5,7 @@ Jinja2==2.9.5
 MarkupSafe==0.23
 PyInstaller==3.2.1
 PyQt5==5.7.1
+PySocks==1.6.7
 sip==4.19
 stem==1.5.4
 Werkzeug==0.11.15
diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py
index e14749e..1194383 100644
--- a/onionshare_gui/settings_dialog.py
+++ b/onionshare_gui/settings_dialog.py
@@ -25,6 +25,7 @@ from onionshare.settings import Settings
 from onionshare.onion import *
 
 from .alert import Alert
+from .update_checker import *
 
 class SettingsDialog(QtWidgets.QDialog):
     """
@@ -247,12 +248,7 @@ class SettingsDialog(QtWidgets.QDialog):
             self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked)
 
         autoupdate_timestamp = settings.get('autoupdate_timestamp')
-        if autoupdate_timestamp:
-            dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
-            last_checked = dt.strftime('%B %d, %Y %H:%M')
-        else:
-            last_checked = strings._('gui_settings_autoupdate_timestamp_never', True)
-        self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked))
+        self._update_autoupdate_timestamp(autoupdate_timestamp)
 
         connection_type = settings.get('connection_type')
         if connection_type == 'bundled':
@@ -346,27 +342,12 @@ class SettingsDialog(QtWidgets.QDialog):
         """
         settings = self.settings_from_fields()
 
-        def bundled_setup():
-            self.tor_status.show()
-            self.connection_type_test_button.setEnabled(False)
-            self.save_button.setEnabled(False)
-            self.cancel_button.setEnabled(False)
-
-        def bundled_cleanup():
-            self.tor_status.hide()
-            self.connection_type_test_button.setEnabled(True)
-            self.save_button.setEnabled(True)
-            self.cancel_button.setEnabled(True)
-
         try:
             # Show Tor connection status if connection type is bundled tor
             if settings.get('connection_type') == 'bundled':
-                bundled_setup()
-                def bundled_tor_func(message):
-                    self.tor_status.setText('<strong>{}</strong><br>{}'.format(strings._('connecting_to_tor', True), message))
-                    self.qtapp.processEvents()
-                    if 'Done' in message:
-                        bundled_cleanup()
+                self.tor_status.show()
+                self._disable_buttons()
+                bundled_tor_func = self._bundled_tor_func
             else:
                 bundled_tor_func = None
 
@@ -381,13 +362,44 @@ class SettingsDialog(QtWidgets.QDialog):
         except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
             Alert(e.args[0], QtWidgets.QMessageBox.Warning)
             if settings.get('connection_type') == 'bundled':
-                bundled_cleanup()
+                self.tor_status.hide()
+                self._enable_buttons()
 
     def check_for_updates(self):
         """
         Check for Updates button clicked. Manually force an update check.
         """
-        pass
+        settings = Settings()
+        settings.load()
+
+        # Show Tor connection status if connection type is bundled tor
+        if settings.get('connection_type') == 'bundled':
+            self.tor_status.show()
+            self._disable_buttons()
+            bundled_tor_func = self._bundled_tor_func
+        else:
+            bundled_tor_func = None
+
+        # Check for updates
+        try:
+            if not check_for_updates(force=True, bundled_tor_func=bundled_tor_func):
+                Alert(strings._('update_not_available', True))
+        except UpdateCheckerTorError:
+            Alert(strings._('update_error_tor', True), QtWidgets.QMessageBox.Warning)
+        except UpdateCheckerSOCKSHTTPError:
+            Alert(strings._('update_error_sockshttp', True), QtWidgets.QMessageBox.Warning)
+        except UpdateCheckerInvalidLatestVersion as e:
+            Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning)
+
+        # Clean up afterwards
+        if settings.get('connection_type') == 'bundled':
+            self.tor_status.hide()
+            self._enable_buttons()
+
+        # Update the last checked label
+        settings.load()
+        autoupdate_timestamp = settings.get('autoupdate_timestamp')
+        self._update_autoupdate_timestamp(autoupdate_timestamp)
 
     def save_clicked(self):
         """
@@ -408,6 +420,7 @@ class SettingsDialog(QtWidgets.QDialog):
         Return a Settings object that's full of values from the settings dialog.
         """
         settings = Settings()
+        settings.load() # To get the last update timestamp
 
         settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
         settings.set('use_stealth', self.stealth_checkbox.isChecked())
@@ -436,3 +449,30 @@ class SettingsDialog(QtWidgets.QDialog):
         settings.set('auth_password', self.authenticate_password_extras_password.text())
 
         return settings
+
+    def _update_autoupdate_timestamp(self, autoupdate_timestamp):
+        if autoupdate_timestamp:
+            dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
+            last_checked = dt.strftime('%B %d, %Y %H:%M')
+        else:
+            last_checked = strings._('gui_settings_autoupdate_timestamp_never', True)
+        self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked))
+
+    def _bundled_tor_func(self, message):
+        self.tor_status.setText('<strong>{}</strong><br>{}'.format(strings._('connecting_to_tor', True), message))
+        self.qtapp.processEvents()
+        if 'Done' in message:
+            self.tor_status.hide()
+            self._enable_buttons()
+
+    def _disable_buttons(self):
+        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.check_for_updates_button.setEnabled(True)
+        self.connection_type_test_button.setEnabled(True)
+        self.save_button.setEnabled(True)
+        self.cancel_button.setEnabled(True)
diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py
new file mode 100644
index 0000000..d180e18
--- /dev/null
+++ b/onionshare_gui/update_checker.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2017 Micah Lee <micah at micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+import datetime, time, socks, socket, re, platform
+
+from . import strings, helpers
+from onionshare.settings import Settings
+from onionshare.onion import Onion
+from .alert import Alert
+
+class UpdateCheckerTorError(Exception):
+    """
+    Error checking for updates because of some Tor connection issue.
+    """
+    pass
+
+class UpdateCheckerSOCKSHTTPError(Exception):
+    """
+    Error checking for updates because of some SOCKS proxy or HTTP request issue.
+    """
+    pass
+
+class UpdateCheckerInvalidLatestVersion(Exception):
+    """
+    Successfully downloaded the latest version, but it doesn't appear to be a
+    valid version string.
+    """
+    def __init__(self, latest_version):
+        self.latest_version = latest_version
+
+def check_for_updates(force=False, bundled_tor_func=None):
+    """
+    Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest
+    version of OnionShare is. If the latest version is newer than the
+    installed version, alert the user.
+
+    Only check at most once per day, unless force is True.
+    """
+    # Load the settings
+    settings = Settings()
+    settings.load()
+
+    # See if it's been 1 day since the last check, and if so set force to True
+    if not force:
+        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:
+                force = True
+        else:
+            force = True
+
+    # Check for updates
+    if force:
+        # Create an Onion object, for checking for updates over tor
+        try:
+            onion = Onion(settings=settings, bundled_tor_func=bundled_tor_func)
+        except:
+            raise UpdateCheckerTorError
+
+        # Download the latest-version file over Tor
+        #try:
+        (socks_address, socks_port) = onion.get_tor_socks_port()
+        socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
+
+        s = socks.socksocket()
+        s.connect(('elx57ue5uyfplgva.onion', 80))
+
+        http_request = 'GET /latest-version.txt HTTP/1.0\r\n'
+        http_request += 'Host: elx57ue5uyfplgva.onion\r\n'
+        http_request += 'User-Agent: OnionShare {}, {}\r\n'.format(helpers.get_version(), platform.system())
+        http_request += '\r\n'
+        s.sendall(http_request.encode('utf-8'))
+
+        http_response = s.recv(1024)
+        latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8')
+
+        # Clean up from Onion
+        onion.cleanup()
+        #except:
+        #    raise UpdateCheckerSOCKSHTTPError
+
+        # Validate that latest_version looks like a version string
+        # This regex is: 1-3 dot-separated numeric components
+        version_re = r"^(\d+\.)?(\d+\.)?(\d+)$"
+        if not re.match(version_re, latest_version):
+            raise UpdateCheckerInvalidLatestVersion
+
+        # Update the last checked timestamp (dropping the seconds and milliseconds)
+        timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp()
+        settings.set('autoupdate_timestamp', timestamp)
+        settings.save()
+
+        # Do we need to update?
+        update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version)
+        installed_version = helpers.get_version()
+        if installed_version < latest_version:
+            Alert(strings._("update_available", True).format(update_url, installed_version, latest_version))
+            return True
+
+        return False
diff --git a/share/locale/en.json b/share/locale/en.json
index b245ec2..618bba1 100644
--- a/share/locale/en.json
+++ b/share/locale/en.json
@@ -59,7 +59,7 @@
     "gui_settings_stealth_label": "Stealth (advanced)",
     "gui_settings_stealth_option": "Create stealth onion services",
     "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to it.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.",
-    "gui_settings_autoupdate_label": "Automatic updates",
+    "gui_settings_autoupdate_label": "Check for updates",
     "gui_settings_autoupdate_option": "Notify me when updates are available",
     "gui_settings_autoupdate_timestamp": "Last checked: {}",
     "gui_settings_autoupdate_timestamp_never": "Never",
@@ -95,5 +95,10 @@
     "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Make sure you're online and your clock is accurate, then try again.",
     "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
     "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.",
-    "connecting_to_tor": "Connecting to the Tor network"
+    "connecting_to_tor": "Connecting to the Tor network",
+    "update_available": "There is an OnionShare update available. <a href='{}'>Click here</a> to download it.<br><br>Installed version: {}<br>Latest version: {}",
+    "update_error_tor": "Error checking for updates: Can't connect to Tor.\nCheck your Tor connection settings.",
+    "update_error_sockshttp": "Error checking for updates: Connected to Tor, but can't load the update HTTP request.",
+    "update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.",
+    "update_not_available": "You are running the latest version of OnionShare."
 }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/onionshare.git



More information about the Pkg-privacy-commits mailing list