### Debian, Ubuntu, Linux Mint, etc.
```sh
-sudo apt install build-essential dh-python python3-all python3-stdeb python3-pyqt5 python3-gpg python3-requests python3-socks gnupg2 tor
+sudo apt install build-essential dh-python python3-all python3-stdeb python3-pyqt5 python3-gpg python3-requests python3-socks python3-packaging gnupg2 tor
./build_deb.sh
sudo dpkg -i deb_dist/torbrowser-launcher_*.deb
```
### Red Hat, Fedora, CentOS, etc.
```sh
-sudo dnf install rpm-build python3-qt5 python3-gpg python3-requests python3-pysocks gnupg2 tor
+sudo dnf install rpm-build python3-qt5 python3-gpg python3-requests python3-pysocks python3-packaging gnupg2 tor
./build_rpm.sh
sudo yum install dist/torbrowser-launcher-*.rpm
```
# Tor Browser Launcher Changelog
+## 0.3.3
+
+* Switch to Web Key Directory to refresh signing key from torproject.org, because everything is broken
+* Use proper version comparison now that Tor Browser 10.0 is out
+* Fix DNS leak when downloading over Tor
+* Various bug fixes, as well as AppData and AppArmor fixes
+
## 0.3.2
* Switch to keys.openpgp.org when refreshing signing key, because SKS keyservers are broken
Tor Browser Launcher
https://github.com/micahflee/torbrowser-launcher/
-Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
+Copyright (c) 2013-2021 Micah Lee <micah@micahflee.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
# Installing
-You can install `torbrowser-launcher` from your operating system's package manager, but it might be out-of-date and have issues working. If you want to make sure you always have the latest version, use one of the methods below to install:
+You can install `torbrowser-launcher` from your operating system's package manager, but it might be out-of-date and have issues working. If you want to make sure you always have the latest version, use Flatpak:
## Installing in any Linux distro using Flatpak
```
flatpak run com.github.micahflee.torbrowser-launcher
```
-
-## Installing from the PPA
-
-If you use Ubuntu or one of its derivatives:
-
-```sh
-sudo add-apt-repository ppa:micahflee/ppa
-sudo apt update
-sudo apt install torbrowser-launcher
-```
-
-Run `torbrowser-launcher` either by using the GUI desktop launcher, or by running:
-
-```
-torbrowser-launcher
-```
\ No newline at end of file
profile torbrowser_firefox @{torbrowser_firefox_executable} {
#include <abstractions/audio>
#include <abstractions/gnome>
+ #include <abstractions/ibus>
+ #include if exists <abstractions/vulkan>
# Uncomment the following lines if you want to give the Tor Browser read-write
# access to most of your personal files.
/dev/ r,
/dev/shm/ r,
+ owner @{PROC}/@{pid}/cgroup r,
owner @{PROC}/@{pid}/environ r,
owner @{PROC}/@{pid}/fd/ r,
owner @{PROC}/@{pid}/mountinfo r,
owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profiles.ini r,
owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/{,**} rwk,
owner @{torbrowser_home_dir}/TorBrowser/Data/fontconfig/fonts.conf r,
+ owner @{torbrowser_home_dir}/fonts/* l,
owner @{torbrowser_home_dir}/TorBrowser/Tor/tor px,
owner @{torbrowser_home_dir}/TorBrowser/Tor/ r,
owner @{torbrowser_home_dir}/TorBrowser/Tor/*.so mr,
owner @{torbrowser_home_dir}/TorBrowser/Tor/*.so.* mr,
+ owner @{torbrowser_home_dir}/TorBrowser/Tor/libstdc++/*.so mr,
+ owner @{torbrowser_home_dir}/TorBrowser/Tor/libstdc++/*.so.* mr,
# parent Firefox process when restarting after upgrade, Web Content processes
owner @{torbrowser_firefox_executable} pxmr -> torbrowser_firefox,
/sys/devices/system/cpu/present r,
/sys/devices/system/node/ r,
/sys/devices/system/node/node[0-9]*/meminfo r,
+ /sys/fs/cgroup/cpu,cpuacct/{,user.slice/}cpu.cfs_quota_us r,
deny /sys/devices/virtual/block/*/uevent r,
# Should use abstractions/gstreamer instead once merged upstream
deny /sys/devices/system/cpu/cpufreq/policy[0-9]*/cpuinfo_max_freq r,
deny /sys/devices/system/cpu/*/cache/index[0-9]*/size r,
deny /run/user/[0-9]*/dconf/user rw,
+ deny /usr/bin/lsb_release x,
# Silence denial logs about PulseAudio
deny /etc/pulse/client.conf r,
# Yubikey NEO also needs this:
/sys/devices/**/hidraw/hidraw*/uevent r,
+ # Needed for Firefox sandboxing via unprivileged user namespaces
+ capability sys_admin,
+ capability sys_chroot,
+ owner @{PROC}/@{pid}/{gid,uid}_map w,
+ owner @{PROC}/@{pid}/setgroups w,
+
#include <local/torbrowser.Browser.firefox>
}
# Support some of the included pluggable transports
owner @{torbrowser_home_dir}/TorBrowser/Tor/PluggableTransports/** rix,
@{PROC}/sys/net/core/somaxconn r,
+ #include <abstractions/ssl_certs>
# Silence file_inherit logs
deny @{torbrowser_home_dir}/{browser/,}omni.ja r,
deny @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/.parentlock rw,
deny @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/extensions/*.xpi r,
deny @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/startupCache/* r,
+ # Silence logs from included pluggable transports
+ deny /etc/hosts r,
+ deny /etc/services r,
@{PROC}/sys/kernel/random/uuid r,
/sys/devices/system/cpu/ r,
rm -r build dist
# build binary package
-python3 setup.py bdist_rpm --requires="python3-qt5, python3-gpg, python3-requests, python3-pysocks, gnupg2"
+python3 setup.py bdist_rpm --requires="python3-qt5, python3-gpg, python3-requests, python3-pysocks, python3-packaging, gnupg2"
# install it
echo ""
--- /dev/null
+# Chinese translations for PACKAGE package
+# PACKAGE 套件的正體中文翻譯.
+# Copyright (C) 2020 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Koala Yeung <koalay@gmail.com>, 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-03-23 15:47-0700\n"
+"PO-Revision-Date: 2020-04-22 23:31+0800\n"
+"Last-Translator: Koala Yeung <koalay@gmail.com>\n"
+"Language-Team: Chinese (traditional)\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.3\n"
+
+#: __init__.py:65 launcher.py:470
+msgid "Tor Browser Launcher"
+msgstr "Tor 瀏覽器啟動程式"
+
+#: __init__.py:66
+msgid "By Micah Lee, licensed under MIT"
+msgstr "由 Micah Lee 編寫,以 MIT 特許授權發佈"
+
+#: __init__.py:67
+#, python-brace-format
+msgid "version {0}"
+msgstr "{0} 版"
+
+#: common.py:100
+#, python-brace-format
+msgid "Error creating {0}"
+msgstr "生成 {0} 時發生問題"
+
+#: common.py:102 common.py:180
+#, python-brace-format
+msgid "{0} is not writable"
+msgstr "無法寫入 {0}"
+
+#: common.py:177
+#, python-brace-format
+msgid "Cannot create directory {0}"
+msgstr "無法生成 {0} 目錄"
+
+#: common.py:187
+msgid "Creating GnuPG homedir"
+msgstr "生成 GnuPG 目錄"
+
+#: common.py:254
+#, python-format
+msgid "Could not import key with fingerprint: %s."
+msgstr "無法以指紋匯入加密金鑰︰%s."
+
+#: common.py:259
+msgid "Not all keys were imported successfully!"
+msgstr "只有部份加密金鑰加入成功!"
+
+#: launcher.py:83
+msgid "Downloading Tor Browser for the first time."
+msgstr "第一次下載 Tor 瀏覽器。"
+
+#: launcher.py:85
+msgid ""
+"Your version of Tor Browser is out-of-date. Downloading the newest version."
+msgstr "你目前的 Tor 瀏覽器不是最新的,正下載最新版本。"
+
+#: launcher.py:100
+msgid "Downloading over Tor"
+msgstr "透過 Tor 網絡下載"
+
+#: launcher.py:111
+msgid "Tor Browser"
+msgstr "Tor 瀏覽器"
+
+#: launcher.py:128
+msgid "Start"
+msgstr "開始"
+
+#: launcher.py:174
+msgid "Yes"
+msgstr "是"
+
+#: launcher.py:178
+msgid "Exit"
+msgstr "離開"
+
+#: launcher.py:192 settings.py:136
+msgid "Cancel"
+msgstr "取消"
+
+#: launcher.py:231 launcher.py:245 launcher.py:249 launcher.py:279
+#: launcher.py:281
+msgid "Downloading"
+msgstr "下載中"
+
+#: launcher.py:238
+msgid "Latest version: {}"
+msgstr "最新版本︰{}"
+
+#: launcher.py:241
+msgid "Error detecting Tor Browser version."
+msgstr "偵測 Tor 瀏覽器版本時發生錯誤。"
+
+#: launcher.py:256 launcher.py:357
+msgid "Verifying Signature"
+msgstr "正在檢查數位簽章"
+
+#: launcher.py:260
+msgid "Extracting"
+msgstr "正在解開"
+
+#: launcher.py:264
+msgid "Running"
+msgstr "執行中"
+
+#: launcher.py:268
+msgid "Starting download over again"
+msgstr "從新開始下載"
+
+#: launcher.py:279 launcher.py:295
+msgid "(over Tor)"
+msgstr "(透過 Tor 網絡)"
+
+#: launcher.py:293
+msgid "Downloaded"
+msgstr "已下載"
+
+#: launcher.py:393
+msgid "Installing"
+msgstr "正在安裝"
+
+#: launcher.py:401
+#, python-brace-format
+msgid "Tor Browser Launcher doesn't understand the file format of {0}"
+msgstr "Tor 瀏覽器啟動程式不了解 {0} 的檔案格式"
+
+#: launcher.py:427
+msgid ""
+"The version of Tor Browser you have installed is earlier than it should be, "
+"which could be a sign of an attack!"
+msgstr "你已安裝的 Tor 瀏覽器版本比預期的版本早期,這表示你的電腦可能被入侵!"
+
+#: launcher.py:446
+msgid "Downloading Tor Browser over again."
+msgstr "重新下載 Tor 瀏覽器。"
+
+#: launcher.py:516 launcher.py:525 launcher.py:533
+msgid "Download Error:"
+msgstr "下載錯誤:"
+
+#: launcher.py:517
+msgid "You are currently using a non-default mirror"
+msgstr "你目前正在使用非預設的鏡像"
+
+#: launcher.py:518
+msgid "Would you like to switch back to the default?"
+msgstr "你想改為使用預設的嗎?"
+
+#: launcher.py:527
+msgid "Would you like to try the English version of Tor Browser instead?"
+msgstr "你要試試改為下載英文版 Tor 瀏覽器嗎?"
+
+#: launcher.py:548
+#, python-brace-format
+msgid ""
+"Invalid SSL certificate for:\n"
+"{0}\n"
+"\n"
+"You may be under attack."
+msgstr ""
+"以下網址的 SSL 證書不正確︰\n"
+"{0}\n"
+"\n"
+"你可能正被入侵。"
+
+#: launcher.py:550
+msgid "Try the download again using Tor?"
+msgstr "要試試改用 Tor 網絡重新下載嗎?"
+
+#: launcher.py:559
+#, python-brace-format
+msgid ""
+"Error starting download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Trying to download over Tor. Are you sure Tor is configured correctly and "
+"running?"
+msgstr ""
+"無法開始下載︰\n"
+"\n"
+"{0}\n"
+"\n"
+"請試試經由 Tor 網絡下載。你肯定你的 Tor 有正確被下載和執行嗎?"
+
+#: launcher.py:563
+#, python-brace-format
+msgid ""
+"Error starting download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Are you connected to the internet?"
+msgstr ""
+"無法開始下載︰\n"
+"\n"
+"{0}\n"
+"\n"
+"請問你是否有連上互聯網?"
+
+#: settings.py:46
+msgid "Tor Browser Launcher Settings"
+msgstr "Tor 瀏覽器啟動程式設定"
+
+#: settings.py:50
+msgid "Download over system Tor"
+msgstr "透過系統的 Tor 網絡下載"
+
+#: settings.py:57
+msgid "Force downloading English version of Tor Browser"
+msgstr "強制下載英文版本的 Tor 瀏覽器"
+
+#: settings.py:66
+msgid "Tor server"
+msgstr "Tor 伺服器"
+
+#: settings.py:82
+msgid "Status: Installed"
+msgstr "狀態︰已安裝"
+
+#: settings.py:84
+msgid "Status: Not Installed"
+msgstr "狀態︰未安裝"
+
+#: settings.py:87
+msgid "Install Tor Browser"
+msgstr "安裝 Tor 瀏覽器"
+
+#: settings.py:92
+msgid "Reinstall Tor Browser"
+msgstr "重新安裝 Tor 瀏覽器"
+
+#: settings.py:115
+msgid "Mirror"
+msgstr "鏡像"
+
+#: settings.py:131
+msgid "Save && Exit"
+msgstr "儲存及離開"
import os
import sys
-import platform
+import distro
import subprocess
from distutils.core import setup
-SHARE = 'share'
+SHARE = "share"
# detect linux distribution
-distro = platform.dist()[0]
+distro = distro.linux_distribution()[0]
def file_list(path):
files = []
for filename in os.listdir(path):
- if os.path.isfile(path+'/'+filename):
- files.append(path+'/'+filename)
+ if os.path.isfile(path + "/" + filename):
+ files.append(path + "/" + filename)
return files
def create_mo_files():
- po_dir = 'po/'
+ po_dir = "po/"
if not os.path.exists(po_dir):
return []
- domain = 'torbrowser-launcher'
+ domain = "torbrowser-launcher"
mo_files = []
- po_files = [f
- for f in next(os.walk(po_dir))[2]
- if os.path.splitext(f)[1] == '.po']
+ po_files = sorted(
+ [f for f in next(os.walk(po_dir))[2] if os.path.splitext(f)[1] == ".po"]
+ )
for po_file in po_files:
filename, extension = os.path.splitext(po_file)
- mo_file = domain + '.mo'
- mo_dir = 'share/locale/' + filename + '/LC_MESSAGES/'
- subprocess.call('mkdir -p ' + mo_dir, shell=True)
- msgfmt_cmd = 'msgfmt {} -o {}'.format(po_dir + po_file, mo_dir + mo_file)
+ mo_file = domain + ".mo"
+ mo_dir = "share/locale/" + filename + "/LC_MESSAGES/"
+ subprocess.call("mkdir -p " + mo_dir, shell=True)
+ msgfmt_cmd = "msgfmt {} -o {}".format(po_dir + po_file, mo_dir + mo_file)
subprocess.call(msgfmt_cmd, shell=True)
mo_files.append(mo_dir + mo_file)
return mo_files
-with open(os.path.join(SHARE, 'torbrowser-launcher/version')) as buf:
+with open(os.path.join(SHARE, "torbrowser-launcher/version")) as buf:
version = buf.read().strip()
datafiles = []
datafiles.append((root, [os.path.join(root, f) for f in files]))
# disable shipping apparmor profiles until they work in ubuntu (#128)
-if distro != 'Ubuntu':
- if not hasattr(sys, 'real_prefix'):
+if distro != "Ubuntu":
+ if not hasattr(sys, "real_prefix"):
# we're not in a virtualenv, so we can probably write to /etc
datafiles += [
- ('/etc/apparmor.d/', [
- 'apparmor/torbrowser.Browser.firefox',
- 'apparmor/torbrowser.Tor.tor']),
- ('/etc/apparmor.d/local/', [
- 'apparmor/local/torbrowser.Browser.firefox',
- 'apparmor/local/torbrowser.Tor.tor']),
- ('/etc/apparmor.d/tunables/', ['apparmor/tunables/torbrowser'])
+ (
+ "/etc/apparmor.d/",
+ ["apparmor/torbrowser.Browser.firefox", "apparmor/torbrowser.Tor.tor"],
+ ),
+ (
+ "/etc/apparmor.d/local/",
+ [
+ "apparmor/local/torbrowser.Browser.firefox",
+ "apparmor/local/torbrowser.Tor.tor",
+ ],
+ ),
+ ("/etc/apparmor.d/tunables/", ["apparmor/tunables/torbrowser"]),
]
datafiles += [(os.path.dirname(f), [f]) for f in create_mo_files()]
setup(
- name='torbrowser-launcher',
+ name="torbrowser-launcher",
version=version,
- author='Micah Lee',
- author_email='micah@micahflee.com',
- url='https://www.github.com/micahflee/torbrowser-launcher',
- platforms=['GNU/Linux'],
- license='MIT',
- description='A program to help you securely download and run Tor Browser',
+ author="Micah Lee",
+ author_email="micah@micahflee.com",
+ url="https://www.github.com/micahflee/torbrowser-launcher",
+ platforms=["GNU/Linux"],
+ license="MIT",
+ description="A program to help you securely download and run Tor Browser",
long_description="""
Tor Browser Launcher is intended to make Tor Browser easier to install and use
for GNU/Linux users. You install torbrowser-launcher from your distribution's
directory, and launch it. When you run it after that it will just launch Tor
Browser.
""",
- packages=['torbrowser_launcher'],
- scripts=['torbrowser-launcher'],
- data_files=datafiles
+ packages=["torbrowser_launcher"],
+ scripts=["torbrowser-launcher"],
+ data_files=datafiles,
)
[Desktop Entry]
Name=Tor Browser Launcher Settings
+Name[cs]=Tor Browser Launcher nastavení
Name[fr]=Tor Browser Launcher configurations
Name[hu]=Tor-böngésző indító beállításai
Name[nl]=Tor Browser Launcher Instellingen
GenericName=Tor Browser Launcher Settings
GenericName[hu]=Tor-böngésző indító beállításai
Comment=Tor Browser Launcher Settings
+Comment[cs]=Tor Browser Launcher nastavení
Comment[fr]=Tor Browser Launcher configurations
Comment[hu]=Tor-böngésző indító beállításai
Comment[nl]=Tor Browser Launcher Instellingen
[Desktop Entry]
Name=Tor Browser
+Name[cs]=Tor Browser
Name[hu]=Tor-böngésző
Name[pt_BR]=Navegador Tor
-GenericName=Tor browser
+GenericName=Tor Browser
GenericName[hu]=Tor böngésző indító
Comment=Launch Tor Browser
+Comment[cs]=Spustit Tor Browser
Comment[hu]=Tor böngésző indító
Comment[pt_BR]=Navegador Tor
Exec=torbrowser-launcher %u
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
-ZXJAdG9ycHJvamVjdC5vcmc+iQI9BBMBCgAnAhsBBQsJCAcDBRUKCQgLBRYCAwEA
-Ah4BAheABQJV3aJFBQkKtPQDAAoJEE4sboeTKYKQARMQALQ4cgtqlAL9tUofNq2k
-/MqWaUX/sAGipZKPpHKvSBUgbmhwPsHrqXD8BxK2/pzvcCko48cHgR60lumNZl2Y
-h72j3P8azVfmNxAwfjXL8Qg+62DRg7w+jEpFMmkuoV7hIN3rhCxdOnBg6tP7V3Vv
-xwRYrEg7U5q+JAzH8a0moL38TlMrJckA0Osh6KSdp5lTiwnQMYv1jmMAGzIko6j5
-I10FQxEopPlTZ+MWr2YZeOTZ023GjudA6f+ZTnRMDV/IMFVxWdDVshFiBrzI5ZBy
-MSjJ+deIBtFbY36mvQxFYG9u/fZ8RkYjnLpah23T2qB/mWdOHzt87vUqcA3qajN6
-HkdDCDNi5TjiOq6ePcnW9GWP9lWwDB+acO5VrpFQd4fuLItWFGCl4R4PzAuHKbaz
-EvE1RROVuHTd1+BptEYQkW4NHmdZThyS4bSwBNmU8IYi76JP302E4JJBeWwbHA6i
-lWTEMiaGIp4uJmYXhdDnEzUqg72eeJHPw9aWgb3jQZ3AEkQFkJlFp4lFnTpHcFWU
-k8yeUljM0uV1oLuvax/8V58DOMSN9+DVBdnb6JpLluId2oH9BY7vMdY/f2EdAysJ
-PfP/N9Pg0sWUJ7zNfiHAQW6hnWZuCBpJuVgvkUeW/Hvv9MUmNgtGviescMtZUU2K
-FRGeU4APEk8CjqJN5T+qSyzKuQINBFSOr/sBEADKozhKT/c1dbHuIf4H3kigdq6V
-svNGlDKJQakbTJuMKxVRc4nu4j2MUhgawlzvNQWiUEf5CC5X/BqU5wdL1ybhhFdx
-sXgkCLeFpxim1d+FIf0vBv9XdB+Z5Dv4w70Cemw4qM2HiXyaKltwEyc0U7ZN8w+P
-Wmp56M+9yDgYwWn8vi7GtbAEugaF9c0jvlmK5C0l6XKULMr+CstYRdMyC1A6yhe3
-avWu7uUQXmwPLUj3mwzyZSYU0sT9Kw2LmJ+wOVJZSgxIfGFv9CRAzrxl4IZn22s8
-FYonxU/9Dy7vd2RB2E9zRx/hnf9ksvThcga9bCV9jEa00rLV1MTI2iqsLdo/hOhF
-MYDF/kT0lSakck1ROsnUhImMqbXHXbQXmqTErblWZbHSupdx+iM2OuFQhnhcMl2N
-Rx1DNCqZNZ4h5vO/2yfGZjkJig1bAKZY9JB6FrX98Yg1bS1ViTME1U3yAmQexaOX
-645oluq/ZFG4CJt2uizbe/Xr+h+7k20Y/goMO3Qb28j/gzrcoUVmIEtttBQFBUb4
-y8/UdEPKw19yWFyMJtBRKDAFb6fwTx/60DGaX/uI/mh2bt1nCyH1uOTpO7vAveLx
-RnMvTZNVeY59SbhWvyg9+LxJV5DOGhYN/rMwJkSiDFKxKAZtZZsBu5zToUiZ/04Y
-sBDYVqEBDJd6tW3UFwARAQABiQREBBgBCgAPAhsCBQJV3aG7BQkFEViwAinBXSAE
-GQEKAAYFAlSOr/sACgkQcBetzvZcIDbJ6hAAxbiuUJdQgLdALz0vN8lJBDhx48Yd
-qSvOn2AMADj6+ETvC3oVhp1V4viCI4BQyjuXuG0Av2sV5ue3OKmeCFEPniHA3+s7
-jt0kmuhnDIpXX8kHF0wVtrCI4oPyJknUz7C7DslsIqa6tXzwsgEWKV8zWuE4r+K0
-8qT9GDc4T0p46ybQBJnAZv19JR9/XJ/zzcGocgboxp7iU58GHAjM1yTD/g549uR7
-55j38sBHZGEpRUBP47cyyF/aXErHRXGbroGUJcmHpj502UYlkycdS3epxRnuZBj9
-G2ownfAGyWcI5rSGB4XBbjdMq3mtMwzMK3lWgR+s9aIkrXo7PNQrKwt3Nb5GI/lW
-eSCPGDV8TMQp7/Gvrz5EFmi7dGss4L6ZeRGsooS5nTA4GaQUeIaXvNJc7/gUVRqk
-1/N4OviyfzJZMBtA8z/57fgxTORpQq4pwdbGNyYV84x+YOTW+1ye65c5neBo5/W+
-IZ0ja6Vx2shix0EfXzoYYX0W0rBh1CzDGIho5ec4IDBN+sPyXO1WkMgZYFo1T3jR
-x1k46qlc8Ic1+CbyNkf+Zms7ZRSqU4Zpbx6EUgyEgTOTQdkcI7QMJVn0Rd/Z1+/X
-qseks7QP9/r3cw0sUVhhI5OA/toFjDGtsnScvSDKbEK+zNbN3f2N9zpmFV46fcnL
-gthgqzIDBPeZRcYJEE4sboeTKYKQaUkQAKmIUb9kJAJEQ1RvgKoFI5yORKWGERlz
-2TObvPGm3ZS1UHh9q1h/iwy//DIrJ35iVr/AYtYoHIdFvJ8Q1HbYyI5xnGi6VcCK
-OA2V++ky56/49jtSB70g4j3RCBYEhbDwOQjAjlEpre61ppN8GMTBkgYQVn2YDY4+
-tXuWePlrUnbGPAS5i3V0qN7bq+ZrMbdfvAPzk73AP8svOAenDrz8AJBC5JcguPwM
-BrkU7nIOvLSAPetAyMySCBeOnSdYLXe+mFqJxR6CMTYUV/8HcvuwmtJ8oZVyLrzs
-ji/+/it1V0v5VHRUAOgV9Lu1jhdNPOhrOwywCxdPRqprtcOOLutwj81RRnzuHzQj
-n/LxSi1KH3A6ZCu0Bb1+t1UwF8VDZJKvtCinKck4lUM7Zr9gj5kFLvRv1tTXsSw2
-sSMGykPhU/nUlSjRklE96w4A7/W8nHeD0Ic/ROLbhvk0cS4sxao85+wM5Pn09L25
-JkQPvNw5sr8sdfC46RG9aVqEvJi1R7+JnzcziuDbgH1FsU/1r0d8+2l874cVpTa3
-y2DkqeF7LLzxQaVEuajQeclIikSXeIjE/aLhhu15DZVKssnyfWhH3QoqtMgfHaiG
-/WhdDlMfhovcErVqjW6F59ZOjuqUq03zhLf7ikIAJVUZ+6O050R3xYzpKLPTNhdA
-EWGhnEdUCckLuQINBFSOsFYBEADB79YviDFjY6mwjFD0uw105uCRsNRfPIH92B1c
-Ehe3WX1HR0uH2+uX1bOAD2nymakK2pdKowyLCNqGYLnnduPyAxfSSGFl/ipLOVmx
-yT95OxpOvMA3BgZxjEqGirzkLXPy9/+wB54cZxb7bA/GsnbVHwTSAQYjcwF7H6T/
-1n/rRles/u1Pgk+8BN1AqLdhK2/1HeT1UqvBjB2UfiKOOV2w2RBwrZloLN5fs/M4
-eC5xX9xz/lLgzZdjwpsQKpfllziNzWweuNDvuGOLCMbdPAt8Msyrhh+pCJUb1xr2
-O5AviTX5/KDBtZJynju1n93Q87lCvULwhsTZts8jm7887ffpQr4yZ3aTRFBA5XvG
-6N8Wo0AXsZughWCN5bmYxGyyjCNhWNFpd0dmxJgpNxvp5UJZnP/ubO6pmGUqP2Z7
-4KFA01DIb1cqYE5MnSuPs6ELsneqhR23ilmj6+nmSI3TuSd7eb/tlmxYwKl1v/Pa
-x7/WlWmYli+w5LJqwYL8ViB64k3cKbQ3zWHhTpyAkJAh6lKFkNloK1g7rqF7PrdG
-zp2ujSRykjWZ9huJh2aovLVL9tiJpGGiFVQ5pKBnInWz79epw756FOpttn4vQiqK
-qBVTSsMi0c+7fVagjNmdk6qnOTxr8N6H9XIaiDldfAA/35NZB7XynJ/S/QPV/m17
-hVIPnQARAQABiQREBBgBCgAPAhsCBQJV3aJrBQkFEVkQAinBXSAEGQEKAAYFAlSO
-sFYACgkQLhrGjtQIFOCSoxAAl61KCTOIrJBf7UQnOu25f0FlcuEQ0JVYXAn51AJ7
-iK++n9xI7mKqw+nPEGnG0xc4IE8LbWF8dPTH9wN/PE/42R1f/R5Dxt3MaYKTZqv2
-IkIcQlb3bx6PyBpKUvVDLeH+4RY7C+3ZVtee8rPDkPHIEUkNAPxRHjL8xOrp4u3G
-GeJXy5K0vPeqNJWsov7goOwSkYc+OGvZWFqUPnewRQvq5jBJO54VoFHi42/qC9tj
-LFbp20ROHJRCmL8k+tweIprZsybHRokDyv/scwnMGKrUTiPa+3To0hyiOVNLNT/z
-7122Yqc8oTRcUzy/HzkVvAyUlunYXdOAfoqc+GTBDJMsDTllPMOhHr1bbOw5OTL7
-mrj2bEjUiS8lWZgfS5Y1zW2ATBrFwuXtTNPUb6OV1XSo/D4GyfYqQlm3H7tRgUYj
-3/3KxB+Tl0gVp80Jbfjc7i/+upEDblgip0Pyu79ufEJXh2GOfxO0p+MUYjE2uU3P
-p5mJhgcldMDYwZkcV1qbWeA2OHKZdU3w5e4lhK4qnPmTpkNVIouYZDekr5vGuLvj
-koPPGZZ6wxELUEUYNnXSpxreO1qhG5OvSf9XVlbk126nXxqlJ7lu8GdBSpTDmpA0
-IqYC13MooAvw4wFBkHfUbzCqEQPrYCST9K+awwnYvgmHFMo0X8Lg6lMnATc9R/P5
-UyQJEE4sboeTKYKQUoEP+gL9exaJP2o238YgQnmmW5bL7x8z+1XDHWv4Hgv95+lW
-WSuUcaY1Wm/jnoeNx2by17iT/7SyD4LMZ/iIeOOgztQJ0GbHQgT2HL7cmDYAb2L8
-hYNuFgdsa7IDElDX8W3XFDIDNwvAlv7MSFVDBXVDry8ZIohttmU3AMoVRQn+9ZBk
-YeDbBnlJR0xiHw6sq5P+m+yIvzrwa4+8L4z026mhaZDmD6qi5L+j7ckOp/mxe27s
-AMPD0x1WvxPGNjqMGQcqU00K8IaD6Y8JNtmYwpwfyMqdNgnZ8a4GUCjAnnws3a6C
-SpDBEBYtD0INg9HtgQecxh6Ts4yCs+d0HfSE0XAMOMJhR40fKb69VIXSaW4ShJ+H
-eyVuYnlGg0+Ufdc86e+3d+OU4FOgIbUSFA+qQLpHjl4FaxtnVRFs3EijK4CbkJAB
-rKF0if6CrDxB3TdW2aoBuAJq95SN1PhdIM5EHZFkb9wV9/FQCbHxMUcKnVA4pU+D
-Hd+0aWUyEzFxoso1eEIGlMvuOltz9lIyZCDS4EEsgTkPYRPIn3PRKBAfUpQj2U7k
-P+GfZ3t2fbNvRyN1WiqeBKwYJcSJGosGe/dNVLoEcCp9KJzk0vc+UU/j5AaZe7FP
-QQrlyK+uV4ZCblKnNOpD2LLU6DAk6qHeLlzFfhE9ywdqqBmseVjajG8KdwRvxGMM
-uQINBFSOsOoBEADknavzdE/HW/D07rX5AUVbZHVab4bppClm4waDeAsAPAVWUgH3
-YDQhO+Lcj/pOz2vNNnkAVBOn9I+Kw/BO6IRjutb6N2SyCDDxgkDi7bzk4TSlPGQZ
-wj3VG5OprGz5p34fSoc998xpI6M8697i5EQyqoMArttrIMTDGvks7NsgaXo2m+Jk
-kYJTYBIJtbC8aCFAV9g87leo4ePid4wgkjklA0pfBxdxC2gXmzaNlXoBnbzqkzij
-y2afvHf//Hs4v1utsFIcSZbSbBT0SsN6xZCHhoElKIOaKF/1fVfWDtFLA5gjt1YM
-ubOG8nLYArhG8WtmT2m8K0YKWl1IDhrv2uTwztb3nrAiYSmwLh78IJ7Wjg6WOj45
-pzQP/UM6yoVKQGKeI3kgDcbXdrSxTPp/x8CuJ/MsZwXDNnanNlnm32Qxzczcqghr
-/twroE6Lqlve3ICT20uyJBDAFZts8oDHs8d8t6e21QudNksZBrJy3MPR/qmwUoNN
-b/IYilbSP+4xUUMQB0pqUDrZPmO4wKcaMddtwRjrSSo7MZFKNRhJPqyKQpLp/g5N
-dzZWMOAzbSCj0AZ4MfjgTYvVy0tLOWbJXzsJOH8U3SZZjQBi5l77FcqDgLCaPire
-xx+SiiiS+2FKxO2uufBv+mbomMvV21iONLS6SqwKxaHUzjfVuvgHqhtW0QARAQAB
-iQQ+BBgBCgAJBQJUjrDqAhsCAikJEE4sboeTKYKQwV0gBBkBCgAGBQJUjrDqAAoJ
-EC0ACYhYmDmjtlEP/3JlkkpH24Ej8AE+XBZwt4qanBb2Pi/vzg3Ke9t7pyMH7JWk
-g7LFGSpQTIG4/HOfRGahG68SQNKyPS+MUwhtqQrrEfOGnmXf+TS2F1TqKVGIsFpN
-iNSi5zW4Q2GBlhwDTsPGOsnAAjlsRjkgwwegq6UEDJYTz3X1tkbY2iFH4oSr/hdp
-gOeI4Z2rHQmgJNGhpHSZidOP9gYZMFqQXqRaZevljMHtaANJTQoP3l4aMVvAU+RM
-SusNJc6dv+D/cGY1bQGKTjlPUnR4+4NV6R+r2r2qUJXM/DX8Pgcl+mSyK9WGkKTr
-Dq/xAXI8sNNiwj4j/rSKPwcOKPI5vPjiLf6p6xKpcASwRZUDuTYrMug6+7sqhAC7
-+zBmxMSZt945wksh0eaMkQxSOAWIqdj3RWeVgFCmb5aufpYfmHdwX/LgtpYzGQ+h
-HeqwKdNDODFi581Jqz4P+Cc1nO0vleXQWRIRFiXrD1PE+8a/LrRu29i8RV2M2Uau
-Y0sq+tljzGyx85UUyHc7AXTbAZyS4KOTwmx8CEMRl3iaB1nfmEx7TDkQRd7gqQvw
-IByoqFOedYqXMdXvGhcqbJDMWWUR5ZUzm8CnsXjvHNn09VfRr0JxJ+e9pdEKWPc8
-93bgfN7PNK6R55ejY0aMBGz7qgSqSyMcN2u1qN9fakUA2DhZvU16ztAg2fDGO/sP
-/A0ovCkg0TcazG4TBNUfwnEjaAUK0DqBkiS3QYVvUEhIzD/uoQE5Hp2xpq1cZdZi
-Asx7ql4K/iVvdg2z8EfM+gNZOUjvFdcTnrF+84LdrMuUb3zWXvZpommtAsXmOmBt
-DWqhdvjgOb76R14d78BjnDePNa0y3SheJUIc76c3R5WFE2MLX9zCMADiXc7GQfox
-iaYUYel0vpkwAbQIoFxQD/dNbEX4i/uNWaNZ0LZu7IwVYR1RCEjmxWS03V8L05+b
-n8Pt65mOV+Z/ANkCtVfY+MtA2BZNT+HVvTqGp1lP5C0cqjw7D46/BMtFHmchLq+B
-1wXRbnMtRZ9vxLVBU2FmjzqslZdPyJSk5XxmNOeupf5+PJMDsfz+DxyFa9+rROlx
-z0QdXeroun6c45thYAu6HY52toA/SIRwHi34xtcL11x3BQnmR3X9QdJ4nIr8WABm
-9DXPCApfPVjHyqfvyzMw9PAJc89ujH5oJb+sycdidG/KbjBLU4jOBEqw1azMXsIm
-2eHCPXtYPOllxj5SGdAKYnBZa+vW0yD8yGDZmMFqmm9raptDEFUaTQ1jCJFif690
-05jTfhar/NuxaO6kfM4z1YvA8qCphc34MmtmaBO6HeWxoO5XDO1IcxD38GVDKimm
-sJa586stbBiyYtEGRK1IR6N7VPbB2KgLVBDb7OCwu3RBiQIfBCgBCgAJBQJV3aDt
-Ah0CAAoJEE4sboeTKYKQld0QAIsyTVuD3SmvdBIgFS+t7U5c+Pi+GoTxTV2HgeVl
-7f9n2UvewfJK97hT+xpxo+6I4+P/vcer01ohYLh8N2RqSUZFnqB0S4HvngMZfQF1
-7TA3XiZNIc6ndeDY9t3kZrLKKngLUJtc1z75pgtMm35uwsZwCHb2TQiE7eCdIOmz
-Ksnq0duedPmWOpgJTf8euSTX5ht1yd16OlighsvIWjo2lq3EzPQUdcDPNuuq2giW
-pI2Vy4TiyVjEQL3GCMKQnSigE3vgHKA0+TfIECZoAyY10X7LRiz5CZJrvYZqPQHy
-462YbakRnsZmj56m9nkEr5c7gA2brsvRsiMefrfOauzh7O+6cmAZvPLKNi5CW8IS
-+dVzSVus+o9Y1/bm2y4GZiZQJMfJzZlTFj3sSh69mhPpx5EZrkvXtkyCu6OXLrZb
-dDLiEYwc+Ui6wepu0x+eN/2Jvof71sY10hhTnjH1cenvM6TQ7uFpkT5kfEJR165z
-oZVrsBWN5CBmhFrViMWpSs9V8mYo2lp2fgCGwJ3y4VT043MGpMthJtDQ0Kgi8JOI
-8LllAcs8FbqzSZMAMJYV1Yd3AMlfH4sA16KSOEc8q4HLlvsi8grF0xdBYKeys2JH
-y6OTPThpSrz1by5gs7vqEYn9hBlQXRo+/uuaVeBN/2E3c7MPBHWAWSin3rcQE2PW
-SXATuQINBFe9hGABEACrWZ8UwcxWOOB9vscJl0FP6r3bITq3zItycPFGzqaMmQLP
-+kYJ5eVLp8rskul0IkmgcxWqGeek7hBZ1b7vZjD8Cq1UpUbLQHh1zPdZ3eT91uoj
-O2feJmeIkTc/fHuaQfOsO4LfkU/YjSyIbXWfe8VURcNUIt+nBaPNHaFxCb9T3kvu
-URu0uYoxyu5fYM1ln9MnrFPLjYWHnteBT448go3//vPrvqNk4jNUlWIhf4/SBjF+
-xWlqOJ+NzR0+DmCcfAYR7MPjQzH5SvilGBiylKQS7N8ljZZzWAk9Eg83NHXQ8EGH
-S7AGwpZM1XsD3zVdOmyIoO4bkpuyR5SGGLOHhil3DETmcUjNkXMGxf/kwu9p+bLO
-rdaP6q/ui+Cj1yycjJyAVs5yw4kPv3Hzi4eNRhdm31/hbaKi2mSrBcj+32eKOEQ3
-1tvKwRAq4HkOnH0sJ+fzg7lM897Hruce+Y8l+VbwzCQ43UjUBMtvCDbTtl4YxRcs
-eMGAmOf0ReW1p7+jkYVQXqAHQAUy9E6XXuCzC/jjtGuqj9in71fGv3OxTzPZkWrx
-vAhoDorQZiMmlKYxg9PSoGKp//e+8aTW9/mEuGoJyFhjaeHzMc0IlgZHql2xhBct
-EvYJl8ddrcZl3k7ssXmMVJvbAqCJuUZ8aBaIcYauZbTcAdfwHMv0Z+jDSmsR0wAR
-AQABiQREBBgBCgAPBQJXvYRgAhsCBQkDwmcAAikJEE4sboeTKYKQwV0gBBkBCgAG
-BQJXvYRgAAoJENFIP6bDwHE2+rkP/1HMxGVRc7DUsSAc3w+2oB3lEXvmfYkx9UJ6
-QD9eHY0Va2I17U85liD3S5kAkTm+tHzXj7UKu5odQe4QkvFuZ2ON2WNdQUfftkvi
-48+BvpfKeHQpbFu34DgSmRAi6DVR1HzoXFt+tu0H4qk1i1v23a4UGfgowABklvM+
-t7fcuvyENqbyMaEz34t+zauS9bVK41M1hPB6HOmx1zduObRTqUtaaQ2AqptAvV2d
-t0I7J90QJ77R0+KT5wTesGn945W3R+CG+Ks0QyOJLyEHgoNmmdgiJcatYoUf6+ZU
-9FHn/kfbDqVX2o+b9LgkUGdq+mNWnwnwsul5XQeCwMMse8mrRPeYT4MaLHFFBtxe
-QuVBXM9cErHg/HUdCvij+4ALmOiqhlGOYkDfGXy8YhSvPZZd46vaosX4ewd236fr
-JdGngdyKvvVjXmzdOlFX54NDXT6JW5I+SVbmJ/cTSz4NmXUfYbTnfiLHgM/J5v8g
-nSmQ4PeFALBoub7+UpXd3J40Fnv23WpKZbRB+II3UaFF8udIK4x/iScBYy42MEAy
-Ejyv8HwYBgXzqMN1XIm7DJRfqcIti7HvGqX4Wg/hcqxvxRBbD7n9JGpGD7AON4Kq
-rkxIFgsIH/Ai54qx3sJ0JdwPG9rz3qzlrPFkOA65whrbt1N1d84ksBIB4NRaTpeQ
-PZ4dRBdv/B0QAMbh55yI6gr038yqf6wCJ4k9Lg6QXHAGPKqEJvXxh0MzOhjW4unw
-Z1AaEJUoka0QCZaXgxaUnnQB21MhiBjPpGDxfHg+zygt49mJ1PJ6s1LJXwjajiMa
-tKbTYtNe98+TRrq4ZZKEG8xB9+BhpVzr3f11X5znnfqXAy1ojXv45c8NkswdtaAQ
-bvYQpkZvgN2KcVnVkFoGAipSLZxFvAEeyDk48tUdUPX4Tr5u8qA6/x6d50RTLYv2
-ahEPWSn61/1v+UD5//tU4VnfYw0x9mE7I1v5TNXTwVQY9ewaadYU2hlNYVluiCwm
-qRcynX3AQhujqWNh9oR65LCCdSQJWsEeuvS7QUl80IRlPyD13lADiyb4Sp56yhAk
-MfMt7AHlIf2d+qph9B2dtH1WZ3SuVyOZSqJma5V5xTXN3xdyh68fIZT66yEhBvak
-SVtuFjpxy6NRlcD6aMK6BPZf2CTufN6i0bzcML57RiXc1SJvV24hpxE8vT9x1i37
-0KbxHiQLGOZNkU2k3tqSnogN+u7rmADBnLYN+C54G4E2I3nCn0G1bhdAzWL+IZLb
-j+tzg4dWuyX0HLRbAdRGIgrcsdFSBoi8MM/1Qc0v8IJAYk0CjGgiVxJsBxtomQpX
-cgioZe0PjCuMvJEr5bEAFDAypwr2nRs6dkoZloOu8lTa9ZTbQp4DW63YuQINBFsJ
-0HQBEACvDEVMY7L72RRrd3UnJyNtHL5S+CzN0XnrHnaqxRDnGT/KuZFcYrt59c0+
-uAV0c2thImD7bcpm4eespy5P0scXDNanRoZ9asHQhGr//02CSuHhNGUT23Ti+mGP
-YZHwT2VDjQQ/mGJ6IbDlJwGETWkbEo5QMLGqA2riTEjhpexWptBNhg78wGHghIxP
-UmBcFtYxhprxCowFfc6o3jMB9AOzVmLk6PVV5gssUTz4QCsP917DLidVjpy/az8+
-MqNmUJci39+NiBSqb0C8Ph9zakWKSWvD+W4tXCmBhz/e1GRRyzrua876MhZk1PNh
-9KHNgUAyGnGc8JlVcMjNMDoOHr3UV5r/9ir7bsMaLBefJF2U7QkO5olkVlDxjF+D
-0e9SO9CgQQ3C1mTKbZlvBMvqBOw4zlDL++7BngWXRf6QVtDsuKoDRf7V//zZKCNn
-SgQbhEDIpEC4H7hKBhf+c7nliXS4xAxEnmQfZ14FhGgA8Ih/p83LlGIrwmEdsrM0
-aUU79hsHFKGG09Qm/C90Mu+8attQYjsbOTlspX1EtMi2TfVzj/6sGuTAz5qUtZAM
-NyHAX/TAPNuTxcV0GP5r/a73eYJoqfr38AU/Bd2b180BwFgRdG10zjZqfwsbo4xN
-T5CQUPpjoOcf8MmBFcPh5H2YEiMH1RRSfjF5CmHl11R4OfpbmwARAQABiQRyBBgB
-CgAmFiEE724obdqF6ipLp95oTixuh5MpgpAFAlsJ0HQCGwIFCQRTbAACQAkQTixu
-h5MpgpDBdCAEGQEKAB0WIQQRB3W10QH7NrxskRvrd0SR2f8G4gUCWwnQdAAKCRDr
-d0SR2f8G4te9D/9Eb4NLgXax7Jyjo36sD2epOCNVsMdraZK7G+MClVcaEU2RTohd
-QxN94GdQ5H9Y3M38HaQWUGwu9IJGi89lqW2wUbYOkSGUDud6bDPsJmETtQuD/gcj
-CfW3/FdvKcyk7AVd41NwAqdrjVn7e8CM1Kv+gBx3+kYFb4pIJQvkXFMS2zh3V8y6
-YDPCLc6LQ91AavoOu7pt1RtRwt/9UWgPrar64P7noRI4Ygu+d12IM9lZB5UVVaDO
-+CqFwtxysQocAK6U+ZQH6sFML3L7zojy+GbAYr5eOdrUDj33n/VwYFey0kJ0tVsv
-MdC/p3ApT/DohHQtw6WUoGSnhSdVfsbIrPVdAc7Pz7+FR/rO7Kb5tQJewcJDV0JM
-Z9nLGsLNZSLku3HwerG3ZDTRsQHkEjnwizgliVNNZ4svzVUUIhBI24QkjB8I9QBH
-+gfrBoIZo+QRHwnXwdu6OHgq7/8UAia4u4NBZWBcqZ2sXP5R6IzUGWQgBwNwL/0Q
-nGxKAUNSQXUdn/aFEPOmSbR1W1ClOJN/Y0gP2/HqkkEAesv9nZDXVtlZ14jPwIvG
-o9RSUE1+Cvo+aQhsv/gEduNan3iJq9ybTGr9kq5H7g+9nVD2yqMBXiqtzNKd/ddq
-XqB/Nj1Zy4LA+7oUU2G4S1Dnn2BvCb6KL54UB+l/UbzBEN2mcr0X4JDckYHzD/9H
-WhUtdEQWHKhLfFZG5GBUKxcMJd7TPZtfq9GbFfpHd5uo1MY/WQx9Lj8e2ITu1NJ7
-7Ui1ga4r9JTqjJFKmXTVkgpGTDpekyVtIU5Bgha/MmT98nE4IT/4TxSY/wETQybU
-KHZVll32cEUg2SKNg8OyCEQ2Aby8adexZzJBSCx6CtgmmTeQfpxPGfdUdZ6yfdm1
-zcFiB293dLftNU1xAXTpO5BzW2mwcMP2yHYOg+Ko/yy+Bfh9okf+gTKQNIFnaXCA
-hBJkJ0JH9IHxyp95QGbKl9RbPr1MD0gl3ZVlJFD7iDKKXp1PD0zNH+YwE5G2FFXH
-y+Obcqp4T6Fdw80/gPtSrUd7xpFcRxk6wnrrBZgM5A0lk7lob/C0DcwK7Umpt/XC
-oq2XyDxxya7y/NN32eNPAeLbuejUmWk/x6jBfsFYHoHuAJhlp0ZWjrbl8CyCpqbf
-KCqFMwoOiZFwAGMwK7dbp6EWpmwdTzbmxchGRpQ3gFrVlQ+BkRZwE+lKU4gSXoZ1
-MUBDU9Sti1p4hF6FGzS7ZaPh8gR1Iz0owuICIxi0Ot/nHdq/IZIE94eM5Hp8vdKT
-QoCZxYp5+lE3EqJk4VFolyZqjnitQ5k6jVn4npj8BXSl0gKK+QRez0t7MUQsrH/S
-UvM92/Fxje8URq2rWj9hFgw/amjBbzAaDgIpKe59uQ==
-=/faz
+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
+=ODNf
-----END PGP PUBLIC KEY BLOCK-----
[DEFAULT]
Package3: torbrowser-launcher
-Depends3: python3-pyqt5, python3-gpg, python3-requests, python3-socks, gnupg2
-Build-Depends: dh-python, python3-pyqt5, python3-gpg, python3-requests, python3-socks, gnupg2
+Depends3: python3-pyqt5, python3-gpg, python3-requests, python3-socks, python3-packaging, gnupg2
+Build-Depends: dh-python, python3-pyqt5, python3-gpg, python3-requests, python3-socks, python3-packaging, gnupg2
Recommends: tor
Suite: bionic
Tor Browser Launcher
https://github.com/micahflee/torbrowser-launcher/
-Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
+Copyright (c) 2013-2021 Micah Lee <micah@micahflee.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
"""
Qt's QApplication class. It has been overridden to support threads.
"""
+
def __init__(self):
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtWidgets.QApplication.__init__(self, sys.argv)
def main():
# Parse arguments
parser = argparse.ArgumentParser()
- parser.add_argument('--settings', action='store_true', dest='settings', help='Open Tor Browser Launcher settings')
- parser.add_argument('url', nargs='*', help='URL to load')
+ parser.add_argument(
+ "--settings",
+ action="store_true",
+ dest="settings",
+ help="Open Tor Browser Launcher settings",
+ )
+ parser.add_argument("url", nargs="*", help="URL to load")
args = parser.parse_args()
settings = bool(args.settings)
url_list = args.url
# Load the version and print the banner
- with open(os.path.join(SHARE, 'version')) as buf:
+ with open(os.path.join(SHARE, "version")) as buf:
tor_browser_launcher_version = buf.read().strip()
- print(_('Tor Browser Launcher'))
- print(_('By Micah Lee, licensed under MIT'))
- print(_('version {0}').format(tor_browser_launcher_version))
- print('https://github.com/micahflee/torbrowser-launcher')
+ print(_("Tor Browser Launcher"))
+ print(_("By Micah Lee, licensed under MIT"))
+ print(_("version {0}").format(tor_browser_launcher_version))
+ print("https://github.com/micahflee/torbrowser-launcher")
common = Common(tor_browser_launcher_version)
app = Application()
window_size = gui.size()
gui.move(
(desktop.width() - window_size.width()) / 2,
- (desktop.height() - window_size.height()) / 2
+ (desktop.height() - window_size.height()) / 2,
)
gui.show()
Tor Browser Launcher
https://github.com/micahflee/torbrowser-launcher/
-Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
+Copyright (c) 2013-2021 Micah Lee <micah@micahflee.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
import gettext
import gpg
-SHARE = os.getenv('TBL_SHARE', sys.prefix + '/share') + '/torbrowser-launcher'
+SHARE = os.getenv("TBL_SHARE", sys.prefix + "/share") + "/torbrowser-launcher"
-gettext.install('torbrowser-launcher')
+gettext.install("torbrowser-launcher")
# We're looking for output which:
#
# 2. The second must be an integer between [0, 15], inclusive
# 3. The third must be an uppercased hex-encoded 160-bit fingerprint
gnupg_import_ok_pattern = re.compile(
- b"(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})")
+ b"(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})"
+)
class Common(object):
self.tbl_version = tbl_version
# initialize the app
- self.default_mirror = 'https://dist.torproject.org/'
+ self.default_mirror = "https://dist.torproject.org/"
self.discover_arch_lang()
self.build_paths()
- for d in self.paths['dirs']:
- self.mkdir(self.paths['dirs'][d])
+ for d in self.paths["dirs"]:
+ self.mkdir(self.paths["dirs"][d])
self.load_mirrors()
self.load_settings()
- self.mkdir(self.paths['download_dir'])
- self.mkdir(self.paths['tbb']['dir'])
+ self.mkdir(self.paths["download_dir"])
+ self.mkdir(self.paths["tbb"]["dir"])
self.init_gnupg()
# discover the architecture and language
def discover_arch_lang(self):
# figure out the architecture
- self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
+ self.architecture = "x86_64" if "64" in platform.architecture()[0] else "i686"
# figure out the language
- available_languages = ['ar', 'ca', 'da', 'de', 'en-US', 'es-ES', 'fa', 'fr', 'ga-IE', 'he', 'id', 'is', 'it', 'ja', 'ko', 'nb-NO', 'nl', 'pl', 'pt-BR', 'ru', 'sv-SE', 'tr', 'vi', 'zh-CN', 'zh-TW']
+ available_languages = [
+ "ar",
+ "ca",
+ "cs",
+ "da",
+ "de",
+ "el",
+ "en-US",
+ "es-AR",
+ "es-ES",
+ "fa",
+ "fr",
+ "ga-IE",
+ "he",
+ "hu",
+ "id",
+ "is",
+ "it",
+ "ja",
+ "ka",
+ "ko",
+ "lt",
+ "mk",
+ "ms",
+ "my",
+ "nb-NO",
+ "nl",
+ "pl",
+ "pt-BR",
+ "ro",
+ "ru",
+ "sv-SE",
+ "th",
+ "tr",
+ "vi",
+ "zh-CN",
+ "zh-TW",
+ ]
# a list of manually configured language fallback overriding
language_overrides = {
- 'zh-HK': 'zh-TW',
+ "zh-HK": "zh-TW",
}
default_locale = locale.getlocale()[0]
if default_locale is None:
- self.language = 'en-US'
+ self.language = "en-US"
else:
- self.language = default_locale.replace('_', '-')
+ self.language = default_locale.replace("_", "-")
if self.language in language_overrides:
self.language = language_overrides[self.language]
if self.language not in available_languages:
- self.language = self.language.split('-')[0]
+ self.language = self.language.split("-")[0]
if self.language not in available_languages:
for l in available_languages:
if l[0:2] == self.language:
self.language = l
# if language isn't available, default to english
if self.language not in available_languages:
- self.language = 'en-US'
+ self.language = "en-US"
# get value of environment variable, if it is not set return the default value
@staticmethod
# build all relevant paths
def build_paths(self, tbb_version=None):
- homedir = os.getenv('HOME')
+ homedir = os.getenv("HOME")
if not homedir:
- homedir = '/tmp/.torbrowser-'+os.getenv('USER')
+ homedir = "/tmp/.torbrowser-" + os.getenv("USER")
if not os.path.exists(homedir):
try:
os.mkdir(homedir, 0o700)
except:
- self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
- if not os.access(homedir, os.W_OK):
- self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
-
- tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
- tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
- tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
- old_tbb_data = '{0}/.torbrowser'.format(homedir)
+ self.set_gui(
+ "error", _("Error creating {0}").format(homedir), [], False
+ )
+
+ tbb_config = "{0}/torbrowser".format(
+ self.get_env("XDG_CONFIG_HOME", "{0}/.config".format(homedir))
+ )
+ tbb_cache = "{0}/torbrowser".format(
+ self.get_env("XDG_CACHE_HOME", "{0}/.cache".format(homedir))
+ )
+ tbb_local = "{0}/torbrowser".format(
+ self.get_env("XDG_DATA_HOME", "{0}/.local/share".format(homedir))
+ )
+ old_tbb_data = "{0}/.torbrowser".format(homedir)
if tbb_version:
# tarball filename
- if self.architecture == 'x86_64':
- arch = 'linux64'
+ if self.architecture == "x86_64":
+ arch = "linux64"
else:
- arch = 'linux32'
+ arch = "linux32"
- if hasattr(self, 'settings') and self.settings['force_en-US']:
- language = 'en-US'
+ if hasattr(self, "settings") and self.settings["force_en-US"]:
+ language = "en-US"
else:
language = self.language
- tarball_filename = 'tor-browser-' + arch + '-' + tbb_version + '_' + language + '.tar.xz'
+ tarball_filename = (
+ "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
+ )
# tarball
- self.paths['tarball_url'] = '{0}torbrowser/' + tbb_version + '/' + tarball_filename
- self.paths['tarball_file'] = tbb_cache + '/download/' + tarball_filename
- self.paths['tarball_filename'] = tarball_filename
+ self.paths["tarball_url"] = (
+ "{0}torbrowser/" + tbb_version + "/" + tarball_filename
+ )
+ self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
+ self.paths["tarball_filename"] = tarball_filename
# sig
- self.paths['sig_url'] = '{0}torbrowser/' + tbb_version + '/' + tarball_filename + '.asc'
- self.paths['sig_file'] = tbb_cache + '/download/' + tarball_filename + '.asc'
- self.paths['sig_filename'] = tarball_filename + '.asc'
+ self.paths["sig_url"] = (
+ "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
+ )
+ self.paths["sig_file"] = (
+ tbb_cache + "/download/" + tarball_filename + ".asc"
+ )
+ self.paths["sig_filename"] = tarball_filename + ".asc"
else:
self.paths = {
- 'dirs': {
- 'config': tbb_config,
- 'cache': tbb_cache,
- 'local': tbb_local,
+ "dirs": {
+ "config": tbb_config,
+ "cache": tbb_cache,
+ "local": tbb_local,
},
- 'old_data_dir': old_tbb_data,
- 'tbl_bin': sys.argv[0],
- 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'),
- 'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
- 'signing_keys': {
- 'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
+ "old_data_dir": old_tbb_data,
+ "tbl_bin": sys.argv[0],
+ "icon_file": os.path.join(
+ os.path.dirname(SHARE), "pixmaps/torbrowser.png"
+ ),
+ "torproject_pem": os.path.join(SHARE, "torproject.pem"),
+ "signing_keys": {
+ "tor_browser_developers": os.path.join(
+ SHARE, "tor-browser-developers.asc"
+ )
},
- 'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
- tbb_config + '/mirrors.txt'],
- 'download_dir': tbb_cache + '/download',
- 'gnupg_homedir': tbb_local + '/gnupg_homedir',
- 'settings_file': tbb_config + '/settings.json',
- 'settings_file_pickle': tbb_config + '/settings',
- 'version_check_url': 'https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US',
- 'version_check_file': tbb_cache + '/download/release.xml',
- 'tbb': {
- 'changelog': tbb_local + '/tbb/' + self.architecture + '/tor-browser_' +
- self.language + '/Browser/TorBrowser/Docs/ChangeLog.txt',
- 'dir': tbb_local + '/tbb/' + self.architecture,
- 'dir_tbb': tbb_local + '/tbb/' + self.architecture + '/tor-browser_' + self.language,
- 'start': tbb_local + '/tbb/' + self.architecture + '/tor-browser_' +
- self.language + '/start-tor-browser.desktop'
+ "mirrors_txt": [
+ os.path.join(SHARE, "mirrors.txt"),
+ tbb_config + "/mirrors.txt",
+ ],
+ "download_dir": tbb_cache + "/download",
+ "gnupg_homedir": tbb_local + "/gnupg_homedir",
+ "settings_file": tbb_config + "/settings.json",
+ "settings_file_pickle": tbb_config + "/settings",
+ "version_check_url": "https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US",
+ "version_check_file": tbb_cache + "/download/release.xml",
+ "tbb": {
+ "changelog": tbb_local
+ + "/tbb/"
+ + self.architecture
+ + "/tor-browser_"
+ + self.language
+ + "/Browser/TorBrowser/Docs/ChangeLog.txt",
+ "dir": tbb_local + "/tbb/" + self.architecture,
+ "dir_tbb": tbb_local
+ + "/tbb/"
+ + self.architecture
+ + "/tor-browser_"
+ + self.language,
+ "start": tbb_local
+ + "/tbb/"
+ + self.architecture
+ + "/tor-browser_"
+ + self.language
+ + "/start-tor-browser.desktop",
},
}
# Add the expected fingerprint for imported keys:
self.fingerprints = {
- 'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
+ "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
}
# create a directory
# if gnupg_homedir isn't set up, set it up
def init_gnupg(self):
- if not os.path.exists(self.paths['gnupg_homedir']):
- print(_('Creating GnuPG homedir'), self.paths['gnupg_homedir'])
- self.mkdir(self.paths['gnupg_homedir'])
+ if not os.path.exists(self.paths["gnupg_homedir"]):
+ print(_("Creating GnuPG homedir"), self.paths["gnupg_homedir"])
+ self.mkdir(self.paths["gnupg_homedir"])
self.import_keys()
def refresh_keyring(self, fingerprint=None):
if fingerprint is not None:
- print('Refreshing local keyring... Missing key: ' + fingerprint)
+ print("Refreshing local keyring... Missing key: " + fingerprint)
else:
- print('Refreshing local keyring...')
-
- p = subprocess.Popen(['/usr/bin/gpg2', '--status-fd', '2',
- '--homedir', self.paths['gnupg_homedir'],
- '--keyserver', 'hkps://keys.openpgp.org',
- '--refresh-keys'], stderr=subprocess.PIPE)
+ print("Refreshing local keyring...")
+
+ # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
+ p = subprocess.Popen(
+ [
+ "gpg2",
+ "--status-fd",
+ "2",
+ "--homedir",
+ self.paths["gnupg_homedir"],
+ "--auto-key-locate",
+ "nodefault,wkd",
+ "--locate-keys",
+ "torbrowser@torproject.org",
+ ],
+ stderr=subprocess.PIPE,
+ )
p.wait()
for output in p.stderr.readlines():
match = gnupg_import_ok_pattern.match(output)
- if match and match.group(2) == 'IMPORT_OK':
+ if match and match.group(2) == "IMPORT_OK":
fingerprint = str(match.group(4))
- if match.group(3) == '0':
- print('Keyring refreshed successfully...')
- print(' No key updates for key: ' + fingerprint)
- elif match.group(3) == '4':
- print('Keyring refreshed successfully...')
- print(' New signatures for key: ' + fingerprint)
+ if match.group(3) == "0":
+ print("Keyring refreshed successfully...")
+ print(" No key updates for key: " + fingerprint)
+ elif match.group(3) == "4":
+ print("Keyring refreshed successfully...")
+ print(" New signatures for key: " + fingerprint)
else:
- print('Keyring refreshed successfully...')
+ print("Keyring refreshed successfully...")
def import_key_and_check_status(self, key):
"""Import a GnuPG key and check that the operation was successful.
previously and hasn't changed). ``False`` otherwise.
"""
with gpg.Context() as c:
- c.set_engine_info(gpg.constants.protocol.OpenPGP, home_dir=self.paths['gnupg_homedir'])
+ c.set_engine_info(
+ gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
+ )
- impkey = self.paths['signing_keys'][key]
+ impkey = self.paths["signing_keys"][key]
try:
c.op_import(gpg.Data(file=impkey))
except:
:returns: ``True`` if all keys were successfully imported; ``False``
otherwise.
"""
- keys = ['tor_browser_developers', ]
+ keys = [
+ "tor_browser_developers",
+ ]
all_imports_succeeded = True
for key in keys:
imported = self.import_key_and_check_status(key)
if not imported:
- print(_('Could not import key with fingerprint: %s.'
- % self.fingerprints[key]))
+ print(
+ _(
+ "Could not import key with fingerprint: %s."
+ % self.fingerprints[key]
+ )
+ )
all_imports_succeeded = False
if not all_imports_succeeded:
- print(_('Not all keys were imported successfully!'))
+ print(_("Not all keys were imported successfully!"))
return all_imports_succeeded
# load mirrors
def load_mirrors(self):
self.mirrors = []
- for srcfile in self.paths['mirrors_txt']:
+ for srcfile in self.paths["mirrors_txt"]:
if not os.path.exists(srcfile):
continue
- for mirror in open(srcfile, 'r').readlines():
+ for mirror in open(srcfile, "r").readlines():
if mirror.strip() not in self.mirrors:
self.mirrors.append(mirror.strip())
# load settings
def load_settings(self):
default_settings = {
- 'tbl_version': self.tbl_version,
- 'installed': False,
- 'download_over_tor': False,
- 'tor_socks_address': '127.0.0.1:9050',
- 'mirror': self.default_mirror,
- 'force_en-US': False,
+ "tbl_version": self.tbl_version,
+ "installed": False,
+ "download_over_tor": False,
+ "tor_socks_address": "127.0.0.1:9050",
+ "mirror": self.default_mirror,
+ "force_en-US": False,
}
- if os.path.isfile(self.paths['settings_file']):
- settings = json.load(open(self.paths['settings_file']))
+ if os.path.isfile(self.paths["settings_file"]):
+ settings = json.load(open(self.paths["settings_file"]))
resave = False
# detect installed
- settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
+ settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
# make sure settings file is up-to-date
for setting in default_settings:
resave = True
# make sure tor_socks_address doesn't start with 'tcp:'
- if settings['tor_socks_address'].startswith('tcp:'):
- settings['tor_socks_address'] = settings['tor_socks_address'][4:]
+ if settings["tor_socks_address"].startswith("tcp:"):
+ settings["tor_socks_address"] = settings["tor_socks_address"][4:]
resave = True
# make sure the version is current
- if settings['tbl_version'] != self.tbl_version:
- settings['tbl_version'] = self.tbl_version
+ if settings["tbl_version"] != self.tbl_version:
+ settings["tbl_version"] = self.tbl_version
resave = True
self.settings = settings
self.save_settings()
# if settings file is still using old pickle format, convert to json
- elif os.path.isfile(self.paths['settings_file_pickle']):
- self.settings = pickle.load(open(self.paths['settings_file_pickle']))
+ elif os.path.isfile(self.paths["settings_file_pickle"]):
+ self.settings = pickle.load(open(self.paths["settings_file_pickle"]))
self.save_settings()
- os.remove(self.paths['settings_file_pickle'])
+ os.remove(self.paths["settings_file_pickle"])
self.load_settings()
else:
# save settings
def save_settings(self):
- json.dump(self.settings, open(self.paths['settings_file'], 'w'))
+ json.dump(self.settings, open(self.paths["settings_file"], "w"))
return True
Tor Browser Launcher
https://github.com/micahflee/torbrowser-launcher/
-Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
+Copyright (c) 2013-2021 Micah Lee <micah@micahflee.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
import gpg
import shutil
import xml.etree.ElementTree as ET
+from packaging import version
from PyQt5 import QtCore, QtWidgets, QtGui
"""
Launcher window.
"""
+
def __init__(self, common, app, url_list):
super(Launcher, self).__init__()
self.common = common
self.force_redownload = False
# This is the current version of Tor Browser, which should get updated with every release
- self.min_version = '7.5.2'
+ self.min_version = "7.5.2"
# Init launcher
- self.set_state(None, '', [])
+ self.set_state(None, "", [])
self.launch_gui = True
# 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 not self.common.settings["installed"] or not self.check_min_version():
# Different message if downloading for the first time, or because your installed version is too low
download_message = ""
- if not self.common.settings['installed']:
+ if not self.common.settings["installed"]:
download_message = _("Downloading Tor Browser for the first time.")
elif not self.check_min_version():
- download_message = _("Your version of Tor Browser is out-of-date. "
- "Downloading the newest version.")
+ download_message = _(
+ "Your version of Tor Browser is out-of-date. "
+ "Downloading the newest version."
+ )
# Download and install
print(download_message)
- self.set_state('task', download_message,
- ['download_version_check',
- 'set_version',
- 'download_sig',
- 'download_tarball',
- 'verify',
- 'extract',
- 'run'])
-
- if self.common.settings['download_over_tor']:
- print(_('Downloading over Tor'))
+ self.set_state(
+ "task",
+ download_message,
+ [
+ "download_version_check",
+ "set_version",
+ "download_sig",
+ "download_tarball",
+ "verify",
+ "extract",
+ "run",
+ ],
+ )
+
+ if self.common.settings["download_over_tor"]:
+ print(_("Downloading over Tor"))
else:
# Tor Browser is already installed, so run
launch_message = "Launching Tor Browser."
print(launch_message)
- self.set_state('task', launch_message, ['run'])
+ self.set_state("task", launch_message, ["run"])
# Build the rest of the UI
# Set up the window
self.setWindowTitle(_("Tor Browser"))
- self.setWindowIcon(QtGui.QIcon(self.common.paths['icon_file']))
+ self.setWindowIcon(QtGui.QIcon(self.common.paths["icon_file"]))
# Label
self.label = QtWidgets.QLabel()
# Buttons
self.yes_button = QtWidgets.QPushButton()
- self.yes_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton))
+ self.yes_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+ )
self.yes_button.clicked.connect(self.yes_clicked)
- self.start_button = QtWidgets.QPushButton(_('Start'))
- self.start_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton))
+ self.start_button = QtWidgets.QPushButton(_("Start"))
+ self.start_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+ )
self.start_button.clicked.connect(self.start)
self.cancel_button = QtWidgets.QPushButton()
- self.cancel_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogCancelButton))
+ self.cancel_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
+ )
self.cancel_button.clicked.connect(self.close)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addStretch()
self.yes_button.hide()
self.start_button.hide()
- if 'error' in self.gui:
+ if "error" in self.gui:
# Label
self.label.setText(self.gui_message)
# Yes button
- if self.gui != 'error':
- self.yes_button.setText(_('Yes'))
+ if self.gui != "error":
+ self.yes_button.setText(_("Yes"))
self.yes_button.show()
# Exit button
- self.cancel_button.setText(_('Exit'))
+ self.cancel_button.setText(_("Exit"))
- elif self.gui == 'task':
+ elif self.gui == "task":
# Label
self.label.setText(self.gui_message)
self.start_button.show()
# Cancel button
- self.cancel_button.setText(_('Cancel'))
+ self.cancel_button.setText(_("Cancel"))
# Resize the window
self.adjustSize()
# Yes button clicked, based on the state decide what to do
def yes_clicked(self):
- if self.gui == 'error_try_stable':
+ if self.gui == "error_try_stable":
self.try_stable()
- elif self.gui == 'error_try_default_mirror':
+ elif self.gui == "error_try_default_mirror":
self.try_default_mirror()
- elif self.gui == 'error_try_forcing_english':
+ elif self.gui == "error_try_forcing_english":
self.try_forcing_english()
- elif self.gui == 'error_try_tor':
+ elif self.gui == "error_try_tor":
self.try_tor()
# Start button clicked, begin tasks
# Get ready for the next task
self.gui_task_i += 1
- if task == 'download_version_check':
- print(_('Downloading'), self.common.paths['version_check_url'])
- self.download('version check', self.common.paths['version_check_url'], self.common.paths['version_check_file'])
+ if task == "download_version_check":
+ print(_("Downloading"), self.common.paths["version_check_url"])
+ self.download(
+ "version check",
+ self.common.paths["version_check_url"],
+ self.common.paths["version_check_file"],
+ )
- if task == 'set_version':
+ if task == "set_version":
version = self.get_stable_version()
if version:
self.common.build_paths(self.get_stable_version())
- print(_('Latest version: {}').format(version))
+ print(_("Latest version: {}").format(version))
self.run_task()
else:
- self.set_state('error', _("Error detecting Tor Browser version."), [], False)
+ self.set_state(
+ "error", _("Error detecting Tor Browser version."), [], False
+ )
self.update()
- elif task == 'download_sig':
- print(_('Downloading'), self.common.paths['sig_url'].format(self.common.settings['mirror']))
- self.download('signature', self.common.paths['sig_url'], self.common.paths['sig_file'])
+ elif task == "download_sig":
+ print(
+ _("Downloading"),
+ self.common.paths["sig_url"].format(self.common.settings["mirror"]),
+ )
+ self.download(
+ "signature", self.common.paths["sig_url"], self.common.paths["sig_file"]
+ )
- elif task == 'download_tarball':
- print(_('Downloading'), self.common.paths['tarball_url'].format(self.common.settings['mirror']))
- if not self.force_redownload and os.path.exists(self.common.paths['tarball_file']):
+ elif task == "download_tarball":
+ print(
+ _("Downloading"),
+ self.common.paths["tarball_url"].format(self.common.settings["mirror"]),
+ )
+ if not self.force_redownload and os.path.exists(
+ self.common.paths["tarball_file"]
+ ):
self.run_task()
else:
- self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
-
- elif task == 'verify':
- print(_('Verifying Signature'))
+ self.download(
+ "tarball",
+ self.common.paths["tarball_url"],
+ self.common.paths["tarball_file"],
+ )
+
+ elif task == "verify":
+ print(_("Verifying Signature"))
self.verify()
- elif task == 'extract':
- print(_('Extracting'), self.common.paths['tarball_filename'])
+ elif task == "extract":
+ print(_("Extracting"), self.common.paths["tarball_filename"])
self.extract()
- elif task == 'run':
- print(_('Running'), self.common.paths['tbb']['start'])
+ elif task == "run":
+ print(_("Running"), self.common.paths["tbb"]["start"])
self.run()
- elif task == 'start_over':
- print(_('Starting download over again'))
+ elif task == "start_over":
+ print(_("Starting download over again"))
self.start_over()
def download(self, name, url, path):
# Download from the selected mirror
- mirror_url = url.format(self.common.settings['mirror']).encode()
+ mirror_url = url.format(self.common.settings["mirror"]).encode()
# Initialize the progress bar
self.progress_bar.setValue(0)
self.progress_bar.setMaximum(100)
- if self.common.settings['download_over_tor']:
- self.progress_bar.setFormat(_('Downloading') + ' {0} '.format(name) + _('(over Tor)') + ', %p%')
+ if self.common.settings["download_over_tor"]:
+ self.progress_bar.setFormat(
+ _("Downloading") + " {0} ".format(name) + _("(over Tor)") + ", %p%"
+ )
else:
- self.progress_bar.setFormat(_('Downloading') + ' {0}, %p%'.format(name))
+ 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)
amount /= float(size)
break
- message = _('Downloaded') + (' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units))
- if self.common.settings['download_over_tor']:
- message += ' ' + _('(over Tor)')
+ 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)
def try_default_mirror(self):
# change mirror to default and relaunch TBL
- self.common.settings['mirror'] = self.common.default_mirror
+ self.common.settings["mirror"] = self.common.default_mirror
self.common.save_settings()
- subprocess.Popen([self.common.paths['tbl_bin']])
+ subprocess.Popen([self.common.paths["tbl_bin"]])
self.close()
def try_forcing_english(self):
# change force english to true and relaunch TBL
- self.common.settings['force_en-US'] = True
+ self.common.settings["force_en-US"] = True
self.common.save_settings()
- subprocess.Popen([self.common.paths['tbl_bin']])
+ subprocess.Popen([self.common.paths["tbl_bin"]])
self.close()
def try_tor(self):
# set download_over_tor to true and relaunch TBL
- self.common.settings['download_over_tor'] = True
+ self.common.settings["download_over_tor"] = True
self.common.save_settings()
- subprocess.Popen([self.common.paths['tbl_bin']])
+ subprocess.Popen([self.common.paths["tbl_bin"]])
self.close()
def get_stable_version(self):
- tree = ET.parse(self.common.paths['version_check_file'])
+ tree = ET.parse(self.common.paths["version_check_file"])
for up in tree.getroot():
- if up.tag == 'update' and up.attrib['appVersion']:
- version = str(up.attrib['appVersion'])
+ if up.tag == "update" and up.attrib["appVersion"]:
+ version = str(up.attrib["appVersion"])
# make sure the version does not contain directory traversal attempts
# e.g. "5.5.3", "6.0a", "6.0a-hardened" are valid but "../../../../.." is invalid
- if not re.match(r'^[a-z0-9\.\-]+$', version):
+ if not re.match(r"^[a-z0-9\.\-]+$", version):
return None
return version
self.progress_bar.setMaximum(0)
self.progress_bar.show()
- self.label.setText(_('Verifying Signature'))
+ self.label.setText(_("Verifying Signature"))
def success():
self.run_task()
def error(message):
# Make backup of tarball and sig
- backup_tarball_filename = self.common.paths['tarball_file'] + '.verification_failed'
- backup_sig_filename = self.common.paths['sig_file'] + '.verification_failed'
- shutil.copyfile(self.common.paths['tarball_file'], backup_tarball_filename)
- shutil.copyfile(self.common.paths['sig_file'], backup_sig_filename)
-
- sigerror = 'SIGNATURE VERIFICATION FAILED!\n\n' \
- 'Error Code: {0}\n\n' \
- 'You might be under attack, there might be a network problem, or you may be missing a ' \
- 'recently added Tor Browser verification key.\n\n' \
- 'A copy of the Tor Browser files you downloaded have been saved here:\n' \
- '{1}\n{2}\n\n' \
- 'Click Start to refresh the keyring and try again. If the message persists report the above ' \
- 'error code here:\nhttps://github.com/micahflee/torbrowser-launcher/issues'
- sigerror = sigerror.format(message, backup_tarball_filename, backup_sig_filename)
-
- self.set_state('task', sigerror, ['start_over'], False)
+ backup_tarball_filename = (
+ self.common.paths["tarball_file"] + ".verification_failed"
+ )
+ backup_sig_filename = self.common.paths["sig_file"] + ".verification_failed"
+ shutil.copyfile(self.common.paths["tarball_file"], backup_tarball_filename)
+ shutil.copyfile(self.common.paths["sig_file"], backup_sig_filename)
+
+ sigerror = (
+ "SIGNATURE VERIFICATION FAILED!\n\n"
+ "Error Code: {0}\n\n"
+ "You might be under attack, there might be a network problem, or you may be missing a "
+ "recently added Tor Browser verification key.\n\n"
+ "A copy of the Tor Browser files you downloaded have been saved here:\n"
+ "{1}\n{2}\n\n"
+ "Click Start to refresh the keyring and try again. If the message persists report the above "
+ "error code here:\nhttps://github.com/micahflee/torbrowser-launcher/issues"
+ )
+ sigerror = sigerror.format(
+ message, backup_tarball_filename, backup_sig_filename
+ )
+
+ self.set_state("task", sigerror, ["start_over"], False)
self.update()
t = VerifyThread(self.common)
self.progress_bar.setMaximum(0)
self.progress_bar.show()
- self.label.setText(_('Installing'))
+ self.label.setText(_("Installing"))
def success():
self.run_task()
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
+ "task",
+ _(
+ "Tor Browser Launcher doesn't understand the file format of {0}".format(
+ self.common.paths["tarball_file"]
+ )
+ ),
+ ["start_over"],
+ False,
)
self.update()
def check_min_version(self):
installed_version = None
- for line in open(self.common.paths['tbb']['changelog'],'rb').readlines():
- if line.startswith(b'Tor Browser '):
+ for line in open(self.common.paths["tbb"]["changelog"], "rb").readlines():
+ if line.startswith(b"Tor Browser "):
installed_version = line.split()[2].decode()
break
- if self.min_version <= installed_version:
+ if version.parse(self.min_version) <= version.parse(installed_version):
return True
return False
def run(self):
# Don't run if it isn't at least the minimum version
if not self.check_min_version():
- message = _("The version of Tor Browser you have installed is earlier than it should be, which could be a "
- "sign of an attack!")
+ message = _(
+ "The version of Tor Browser you have installed is earlier than it should be, which could be a "
+ "sign of an attack!"
+ )
print(message)
Alert(self.common, message)
return
# Run Tor Browser
- subprocess.call([self.common.paths['tbb']['start']], cwd=self.common.paths['tbb']['dir_tbb'])
+ subprocess.call(
+ [self.common.paths["tbb"]["start"]], cwd=self.common.paths["tbb"]["dir_tbb"]
+ )
sys.exit(0)
# Start over and download TBB again
def start_over(self):
self.force_redownload = True # Overwrite any existing file
self.label.setText(_("Downloading Tor Browser over again."))
- self.gui_tasks = ['download_tarball', 'verify', 'extract', 'run']
+ self.gui_tasks = ["download_tarball", "verify", "extract", "run"]
self.gui_task_i = 0
self.start(None)
def closeEvent(self, event):
# Clear the download cache
try:
- os.remove(self.common.paths['version_check_file'])
- os.remove(self.common.paths['sig_file'])
- os.remove(self.common.paths['tarball_file'])
+ os.remove(self.common.paths["version_check_file"])
+ os.remove(self.common.paths["sig_file"])
+ os.remove(self.common.paths["tarball_file"])
except:
pass
"""
An alert box dialog.
"""
- def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
+
+ def __init__(
+ self,
+ common,
+ message,
+ icon=QtWidgets.QMessageBox.NoIcon,
+ buttons=QtWidgets.QMessageBox.Ok,
+ autostart=True,
+ ):
super(Alert, self).__init__(None)
self.setWindowTitle(_("Tor Browser Launcher"))
- self.setWindowIcon(QtGui.QIcon(common.paths['icon_file']))
+ self.setWindowIcon(QtGui.QIcon(common.paths["icon_file"]))
self.setText(message)
self.setIcon(icon)
self.setStandardButtons(buttons)
"""
Download a file in a separate thread.
"""
+
progress_update = QtCore.pyqtSignal(int, int)
download_complete = QtCore.pyqtSignal()
download_error = QtCore.pyqtSignal(str, str)
# Use tor socks5 proxy, if enabled
if self.common.settings['download_over_tor']:
- socks5_address = 'socks5://{}'.format(self.common.settings['tor_socks_address'])
+ socks5_address = 'socks5h://{}'.format(self.common.settings['tor_socks_address'])
self.proxies = {
'https': socks5_address,
'http': socks5_address
with open(self.path, "wb") as f:
try:
# Start the request
- r = requests.get(self.url,
- headers={'User-Agent': 'torbrowser-launcher'},
- stream=True, proxies=self.proxies)
+ 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 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, self.common.settings['mirror']
- )
- self.download_error.emit('error_try_default_mirror', message)
+ 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, self.common.settings["mirror"])
+ self.download_error.emit("error_try_default_mirror", message)
# Should we switch to English?
- 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)
+ 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)
else:
message = (_("Download Error:") + " {0}").format(r.status_code)
- self.download_error.emit('error', message)
+ self.download_error.emit("error", message)
r.close()
return
# Start streaming the download
- total_bytes = int(r.headers.get('content-length'))
+ total_bytes = int(r.headers.get("content-length"))
bytes_so_far = 0
for data in r.iter_content(chunk_size=4096):
bytes_so_far += len(data)
self.progress_update.emit(total_bytes, bytes_so_far)
except requests.exceptions.SSLError:
- message = _('Invalid SSL certificate for:\n{0}\n\nYou may be under attack.').format(self.url.decode())
- if not self.common.settings['download_over_tor']:
- message += "\n\n" + _('Try the download again using Tor?')
- self.download_error.emit('error_try_tor', message)
+ message = _(
+ "Invalid SSL certificate for:\n{0}\n\nYou may be under attack."
+ ).format(self.url.decode())
+ if not self.common.settings["download_over_tor"]:
+ message += "\n\n" + _("Try the download again using Tor?")
+ self.download_error.emit("error_try_tor", message)
else:
- self.download_error.emit('error', message)
+ self.download_error.emit("error", message)
return
except requests.exceptions.ConnectionError:
# Connection error
- if self.common.settings['download_over_tor']:
- message = _("Error starting download:\n\n{0}\n\nTrying to download over Tor. "
- "Are you sure Tor is configured correctly and running?").format(self.url.decode())
- self.download_error.emit('error', message)
+ if self.common.settings["download_over_tor"]:
+ message = _(
+ "Error starting download:\n\n{0}\n\nTrying to download over Tor. "
+ "Are you sure Tor is configured correctly and running?"
+ ).format(self.url.decode())
+ self.download_error.emit("error", message)
else:
- message = _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(
- self.url.decode()
- )
- self.download_error.emit('error', message)
+ message = _(
+ "Error starting download:\n\n{0}\n\nAre you connected to the internet?"
+ ).format(self.url.decode())
+ self.download_error.emit("error", message)
return
"""
Verify the signature in a separate thread
"""
+
success = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str)
def run(self):
def verify(second_try=False):
with gpg.Context() as c:
- c.set_engine_info(gpg.constants.protocol.OpenPGP, home_dir=self.common.paths['gnupg_homedir'])
+ 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'])
+ 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)
"""
Extract the tarball in a separate thread
"""
+
success = QtCore.pyqtSignal()
error = QtCore.pyqtSignal()
def run(self):
extracted = False
try:
- if self.common.paths['tarball_file'][-2:] == 'xz':
+ if self.common.paths["tarball_file"][-2:] == "xz":
# if tarball is .tar.xz
- xz = lzma.LZMAFile(self.common.paths['tarball_file'])
+ xz = lzma.LZMAFile(self.common.paths["tarball_file"])
tf = tarfile.open(fileobj=xz)
- tf.extractall(self.common.paths['tbb']['dir'])
+ 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'])
+ 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
Tor Browser Launcher
https://github.com/micahflee/torbrowser-launcher/
-Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
+Copyright (c) 2013-2021 Micah Lee <micah@micahflee.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
"""
Settings window.
"""
+
def __init__(self, common, app):
super(Settings, self).__init__()
# Set up the window
self.setWindowTitle(_("Tor Browser Launcher Settings"))
- self.setWindowIcon(QtGui.QIcon(self.common.paths['icon_file']))
+ self.setWindowIcon(QtGui.QIcon(self.common.paths["icon_file"]))
# Download over system tor
self.tor_download_checkbox = QtWidgets.QCheckBox(_("Download over system Tor"))
- if self.common.settings['download_over_tor']:
+ if self.common.settings["download_over_tor"]:
self.tor_download_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.tor_download_checkbox.setCheckState(QtCore.Qt.Unchecked)
# Force en-US, only display if language isn't already en-US
- self.force_en_checkbox = QtWidgets.QCheckBox(_("Force downloading English version of Tor Browser"))
- if self.common.settings['force_en-US']:
+ self.force_en_checkbox = QtWidgets.QCheckBox(
+ _("Force downloading English version of Tor Browser")
+ )
+ if self.common.settings["force_en-US"]:
self.force_en_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.force_en_checkbox.setCheckState(QtCore.Qt.Unchecked)
- if self.common.language == 'en-US':
+ if self.common.language == "en-US":
self.force_en_checkbox.hide()
# Tor SOCKS address
- tor_addr_label = QtWidgets.QLabel(_('Tor server'))
+ tor_addr_label = QtWidgets.QLabel(_("Tor server"))
self.tor_addr = QtWidgets.QLineEdit()
- self.tor_addr.setText(self.common.settings['tor_socks_address'])
+ self.tor_addr.setText(self.common.settings["tor_socks_address"])
tor_addr_layout = QtWidgets.QHBoxLayout()
tor_addr_layout.addWidget(tor_addr_label)
tor_addr_layout.addWidget(self.tor_addr)
# Status
status_label = QtWidgets.QLabel()
- if(self.common.settings['installed']):
- status_label.setText(_('Status: Installed'))
+ if self.common.settings["installed"]:
+ status_label.setText(_("Status: Installed"))
else:
- status_label.setText(_('Status: Not Installed'))
+ status_label.setText(_("Status: Not Installed"))
# Install button
install_button = QtWidgets.QPushButton(_("Install Tor Browser"))
- install_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton))
+ install_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+ )
install_button.clicked.connect(self.install)
# Reinstall buttons
reinstall_button = QtWidgets.QPushButton(_("Reinstall Tor Browser"))
- reinstall_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton))
+ reinstall_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+ )
reinstall_button.clicked.connect(self.reinstall)
- if(self.common.settings['installed']):
+ if self.common.settings["installed"]:
install_button.hide()
reinstall_button.show()
else:
top_layout.addLayout(status_layout)
# Mirror
- mirror_label = QtWidgets.QLabel(_('Mirror'))
+ mirror_label = QtWidgets.QLabel(_("Mirror"))
self.mirror = QtWidgets.QComboBox()
for mirror in self.common.mirrors:
self.mirror.addItem(mirror)
- if self.common.settings['mirror'] in self.common.mirrors:
- self.mirror.setCurrentIndex(self.mirror.findText(self.common.settings['mirror']))
+ if self.common.settings["mirror"] in self.common.mirrors:
+ self.mirror.setCurrentIndex(
+ self.mirror.findText(self.common.settings["mirror"])
+ )
else:
self.mirror.setCurrentIndex(0)
# Save & Exit button
self.save_exit_button = QtWidgets.QPushButton(_("Save && Exit"))
- self.save_exit_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton))
+ self.save_exit_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
+ )
self.save_exit_button.clicked.connect(self.save_exit)
# Cancel button
self.cancel_button = QtWidgets.QPushButton(_("Cancel"))
- self.cancel_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogCancelButton))
+ self.cancel_button.setIcon(
+ self.style().standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
+ )
self.cancel_button.clicked.connect(self.close)
# Buttons layout
# Install
def install(self):
self.save()
- subprocess.Popen([self.common.paths['tbl_bin']])
+ subprocess.Popen([self.common.paths["tbl_bin"]])
self.close()
# Reinstall
def reinstall(self):
self.save()
- shutil.rmtree(self.common.paths['tbb']['dir'])
- subprocess.Popen([self.common.paths['tbl_bin']])
+ shutil.rmtree(self.common.paths["tbb"]["dir"])
+ subprocess.Popen([self.common.paths["tbl_bin"]])
self.close()
# Save & Exit
# Save settings
def save(self):
# Checkbox options
- self.common.settings['download_over_tor'] = self.tor_download_checkbox.isChecked()
- self.common.settings['force_en-US'] = self.force_en_checkbox.isChecked()
- self.common.settings['tor_socks_address'] = self.tor_addr.text()
+ self.common.settings[
+ "download_over_tor"
+ ] = self.tor_download_checkbox.isChecked()
+ self.common.settings["force_en-US"] = self.force_en_checkbox.isChecked()
+ self.common.settings["tor_socks_address"] = self.tor_addr.text()
# Figure out the selected mirror
- self.common.settings['mirror'] = self.mirror.currentText()
+ self.common.settings["mirror"] = self.mirror.currentText()
# Save them
self.common.save_settings()