]> git.lizzy.rs Git - torbrowser-launcher.git/blobdiff - torbrowser_launcher/launcher.py
Make downloading over Tor work
[torbrowser-launcher.git] / torbrowser_launcher / launcher.py
index 8b0b55e1f799d9ebddfa15c251b8dfa1a5eff27e..959a2aca66a22b95f3406859689735b3683e83bd 100644 (file)
@@ -37,8 +37,8 @@ import threading
 import re
 import unicodedata
 import requests
+import socks
 import gpg
-import OpenSSL
 import xml.etree.ElementTree as ET
 
 from PyQt5 import QtCore, QtWidgets, QtGui
@@ -81,16 +81,6 @@ class Launcher(QtWidgets.QMainWindow):
 
         # If Tor Browser is not installed, detect latest version, download, and install
         if not self.common.settings['installed'] or not self.check_min_version():
-            # If downloading over Tor, include txsocksx
-            if self.common.settings['download_over_tor']:
-                try:
-                    import txsocksx
-                    print(_('Downloading over Tor'))
-                except ImportError:
-                    Alert(self.common, _("The python-txsocksx package is missing, downloads will not happen over tor"))
-                    self.common.settings['download_over_tor'] = False
-                    self.common.save_settings()
-
             # Different message if downloading for the first time, or because your installed version is too low
             download_message = ""
             if not self.common.settings['installed']:
@@ -109,6 +99,9 @@ class Launcher(QtWidgets.QMainWindow):
                           'extract',
                           'run'])
 
+            if self.common.settings['download_over_tor']:
+                print(_('Downloading over Tor'))
+
         else:
             # Tor Browser is already installed, so run
             self.run(False)
@@ -283,12 +276,10 @@ class Launcher(QtWidgets.QMainWindow):
         # Initialize the progress bar
         self.progress_bar.setValue(0)
         self.progress_bar.setMaximum(100)
-        self.progress_bar.setFormat(_('Downloading') + ' {0}, %p%'.format(name))
-
         if self.common.settings['download_over_tor']:
-            # TODO: make requests work over SOCKS5 proxy
-            # this is the proxy to use: self.common.settings['tor_socks_address']
-            pass
+            self.progress_bar.setFormat(_('Downloading') + ' {0} '.format(name) + _('(over Tor)') + ', %p%')
+        else:
+            self.progress_bar.setFormat(_('Downloading') + ' {0}, %p%'.format(name))
 
         def progress_update(total_bytes, bytes_so_far):
             percent = float(bytes_so_far) / float(total_bytes)
@@ -300,8 +291,9 @@ class Launcher(QtWidgets.QMainWindow):
                     amount /= float(size)
                     break
 
-            message = _('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units))
-            print(message, end='\r')
+            message = _('Downloaded') + (' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units))
+            if self.common.settings['download_over_tor']:
+                message += ' ' + _('(over Tor)')
 
             self.progress_bar.setMaximum(total_bytes)
             self.progress_bar.setValue(bytes_so_far)
@@ -316,29 +308,28 @@ class Launcher(QtWidgets.QMainWindow):
             self.set_state(gui, message, [], False)
             self.update()
 
-        t = DownloadThread(mirror_url, path)
+        t = DownloadThread(self.common, mirror_url, path)
         t.progress_update.connect(progress_update)
         t.download_complete.connect(download_complete)
         t.download_error.connect(download_error)
         t.start()
-
         time.sleep(0.2)
 
-    def try_default_mirror(self, widget, data=None):
+    def try_default_mirror(self):
         # change mirror to default and relaunch TBL
         self.common.settings['mirror'] = self.common.default_mirror
         self.common.save_settings()
         subprocess.Popen([self.common.paths['tbl_bin']])
         self.close()
 
-    def try_forcing_english(self, widget, data=None):
+    def try_forcing_english(self):
         # change force english to true and relaunch TBL
         self.common.settings['force_en-US'] = True
         self.common.save_settings()
         subprocess.Popen([self.common.paths['tbl_bin']])
         self.close()
 
-    def try_tor(self, widget, data=None):
+    def try_tor(self):
         # set download_over_tor to true and relaunch TBL
         self.common.settings['download_over_tor'] = True
         self.common.save_settings()
@@ -362,13 +353,14 @@ class Launcher(QtWidgets.QMainWindow):
     def verify(self):
         self.progress_bar.setValue(0)
         self.progress_bar.setMaximum(0)
-        self.progress_bar.setFormat(_('Verifying Signature'))
         self.progress_bar.show()
 
-        def gui_raise_sigerror(self, sigerror='MissingErr'):
-            """
-            :type sigerror: str
-            """
+        self.label.setText(_('Verifying Signature'))
+
+        def success():
+            self.run_task()
+
+        def error(message):
             sigerror = 'SIGNATURE VERIFICATION FAILED!\n\nError Code: {0}\n\nYou might be under attack, there might' \
                        ' be a network\nproblem, or you may be missing a recently added\nTor Browser verification key.' \
                        '\nClick Start to refresh the keyring and try again. If the message persists report the above' \
@@ -377,54 +369,31 @@ class Launcher(QtWidgets.QMainWindow):
             self.set_state('task', sigerror, ['start_over'], False)
             self.update()
 
-        with gpg.Context() as c:
-            c.set_engine_info(gpg.constants.protocol.OpenPGP, home_dir=self.common.paths['gnupg_homedir'])
-
-            sig = gpg.Data(file=self.common.paths['sig_file'])
-            signed = gpg.Data(file=self.common.paths['tarball_file'])
-
-            try:
-                c.verify(signature=sig, signed_data=signed)
-            except gpg.errors.BadSignatures as e:
-                result = str(e).split(": ")
-                if result[1] == 'Bad signature':
-                    gui_raise_sigerror(self, str(e))
-                elif result[1] == 'No public key':
-                    self.common.refresh_keyring(result[0])
-                    gui_raise_sigerror(self, str(e))
-            else:
-                self.run_task()
+        t = VerifyThread(self.common)
+        t.error.connect(error)
+        t.success.connect(success)
+        t.start()
+        time.sleep(0.2)
 
     def extract(self):
-        # initialize the progress bar
         self.progress_bar.setValue(0)
         self.progress_bar.setMaximum(0)
-        self.progress_bar.setFormat(_('Installing'))
         self.progress_bar.show()
 
-        extracted = False
-        try:
-            if self.common.paths['tarball_file'][-2:] == 'xz':
-                # if tarball is .tar.xz
-                xz = lzma.LZMAFile(self.common.paths['tarball_file'])
-                tf = tarfile.open(fileobj=xz)
-                tf.extractall(self.common.paths['tbb']['dir'])
-                extracted = True
-            else:
-                # if tarball is .tar.gz
-                if tarfile.is_tarfile(self.common.paths['tarball_file']):
-                    tf = tarfile.open(self.common.paths['tarball_file'])
-                    tf.extractall(self.common.paths['tbb']['dir'])
-                    extracted = True
-        except:
-            pass
+        self.label.setText(_('Installing'))
+
+        def success():
+            self.run_task()
 
-        if not extracted:
+        def error(message):
             self.set_state('task', _("Tor Browser Launcher doesn't understand the file format of {0}".format(self.common.paths['tarball_file'])), ['start_over'], False)
             self.update()
-            return
 
-        self.run_task()
+        t = ExtractThread(self.common)
+        t.error.connect(error)
+        t.success.connect(success)
+        t.start()
+        time.sleep(0.2)
 
     def check_min_version(self):
         installed_version = None
@@ -502,28 +471,41 @@ class DownloadThread(QtCore.QThread):
     download_complete = QtCore.pyqtSignal()
     download_error = QtCore.pyqtSignal(str, str)
 
-    def __init__(self, url, path):
+    def __init__(self, common, url, path):
         super(DownloadThread, self).__init__()
+        self.common = common
         self.url = url
         self.path = path
 
+        # Use tor socks5 proxy, if enabled
+        if self.common.settings['download_over_tor']:
+            socks5_address = 'socks5://{}'.format(self.common.settings['tor_socks_address'])
+            self.proxies = {
+                'https': socks5_address,
+                'http': socks5_address
+            }
+        else:
+            self.proxies = None
+
     def run(self):
         with open(self.path, "wb") as f:
             try:
                 # Start the request
-                r = requests.get(self.url, headers={'User-Agent': 'torbrowser-launcher'}, stream=True)
+                r = requests.get(self.url,
+                                 headers={'User-Agent': 'torbrowser-launcher'},
+                                 stream=True, proxies=self.proxies)
 
                 # If status code isn't 200, something went wrong
                 if r.status_code != 200:
                     # Should we use the default mirror?
-                    if common.settings['mirror'] != common.default_mirror:
+                    if self.common.settings['mirror'] != self.common.default_mirror:
                         message = (_("Download Error:") +
                                    " {0}\n\n" + _("You are currently using a non-default mirror") +
-                                   ":\n{1}\n\n" + _("Would you like to switch back to the default?")).format(r.status_code, common.settings['mirror'])
+                                   ":\n{1}\n\n" + _("Would you like to switch back to the default?")).format(r.status_code, self.common.settings['mirror'])
                         self.download_error.emit('error_try_default_mirror', message)
 
                     # Should we switch to English?
-                    elif common.language != 'en-US' and not common.settings['force_en-US']:
+                    elif self.common.language != 'en-US' and not self.common.settings['force_en-US']:
                         message = (_("Download Error:") +
                                    " {0}\n\n" + _("Would you like to try the English version of Tor Browser instead?")).format(r.status_code)
                         self.download_error.emit('error_try_forcing_english', message)
@@ -543,6 +525,15 @@ class DownloadThread(QtCore.QThread):
                     f.write(data)
                     self.progress_update.emit(total_bytes, bytes_so_far)
 
+            except requests.exceptions.SSLError:
+                if not self.common.settings['download_over_tor']:
+                    message = _('Invalid SSL certificate for:\n{0}\n\nYou may be under attack.').format(self.url.decode()) + "\n\n" + _('Try the download again using Tor?')
+                    self.download_error.emit('error_try_tor', message)
+                else:
+                    message = _('Invalid SSL certificate for:\n{0}\n\nYou may be under attack.'.format(self.url.decode()))
+                    self.download_error.emit('error', message)
+                return
+
             except requests.exceptions.ConnectionError:
                 # Connection error
                 message = _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(self.url.decode())
@@ -550,5 +541,68 @@ class DownloadThread(QtCore.QThread):
                 # TODO: check for SSL error, also check if connecting over Tor if there's a socks5 error
                 return
 
-        print('')
         self.download_complete.emit()
+
+
+class VerifyThread(QtCore.QThread):
+    """
+    Verify the signature in a separate thread
+    """
+    success = QtCore.pyqtSignal()
+    error = QtCore.pyqtSignal(str)
+
+    def __init__(self, common):
+        super(VerifyThread, self).__init__()
+        self.common = common
+
+    def run(self):
+        with gpg.Context() as c:
+            c.set_engine_info(gpg.constants.protocol.OpenPGP, home_dir=self.common.paths['gnupg_homedir'])
+
+            sig = gpg.Data(file=self.common.paths['sig_file'])
+            signed = gpg.Data(file=self.common.paths['tarball_file'])
+
+            try:
+                c.verify(signature=sig, signed_data=signed)
+            except gpg.errors.BadSignatures as e:
+                result = str(e).split(": ")
+                if result[1] == 'No public key':
+                    self.common.refresh_keyring(result[0])
+                self.error.emit(str(e))
+            else:
+                self.success.emit()
+
+
+class ExtractThread(QtCore.QThread):
+    """
+    Extract the tarball in a separate thread
+    """
+    success = QtCore.pyqtSignal()
+    error = QtCore.pyqtSignal()
+
+    def __init__(self, common):
+        super(ExtractThread, self).__init__()
+        self.common = common
+
+    def run(self):
+        extracted = False
+        try:
+            if self.common.paths['tarball_file'][-2:] == 'xz':
+                # if tarball is .tar.xz
+                xz = lzma.LZMAFile(self.common.paths['tarball_file'])
+                tf = tarfile.open(fileobj=xz)
+                tf.extractall(self.common.paths['tbb']['dir'])
+                extracted = True
+            else:
+                # if tarball is .tar.gz
+                if tarfile.is_tarfile(self.common.paths['tarball_file']):
+                    tf = tarfile.open(self.common.paths['tarball_file'])
+                    tf.extractall(self.common.paths['tbb']['dir'])
+                    extracted = True
+        except:
+            pass
+
+        if extracted:
+            self.success.emit()
+        else:
+            self.error.emit()