### 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
+* Use new Tor Browser logo
+
+## 0.3.1
+
+* Ship with latest version of the Tor Browser Developers OpenPGP public key
+* Fix bug where TBL window stays open after Tor Browser is launched
+
## 0.3.0
* Switched from python2 to python3
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
# Tor Browser Launcher
+_**Are you getting an error?** Sometimes updates in Tor Browser itself will break Tor Browser Launcher. There's a good chance that the problem you're experiencing has already been fixed in the [newest version](https://github.com/micahflee/torbrowser-launcher/releases). Try installing from Flatpak (instructions below), or [build from source](/BUILD.md)._
+
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 package manager and it handles everything else:
* Downloads and installs the most recent version of Tor Browser in your language and for your computer's architecture, or launches Tor Browser if it's already installed (Tor Browser will automatically update itself)
![Tor Browser Launcher screenshot](/screenshot.png)
-# Installing in Ubuntu
+# 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 Flatpak:
+
+## Installing in any Linux distro using Flatpak
-If you want to always have the latest version of the `torbrowser-launcher` package before your distribution gets it, you can use my PPA:
+Install Flatpak using these [instructions](https://flatpak.org/setup/).
-```sh
-sudo add-apt-repository ppa:micahflee/ppa
-sudo apt-get update
-sudo apt-get install torbrowser-launcher
+Then install `torbrowser-launcher` like this:
+
+```
+flatpak install flathub com.github.micahflee.torbrowser-launcher -y
+```
+
+Run `torbrowser-launcher` either by using the GUI desktop launcher, or by running:
+
+```
+flatpak run com.github.micahflee.torbrowser-launcher
```
@{torbrowser_firefox_executable} = /home/*/.local/share/torbrowser/tbb/{i686,x86_64}/tor-browser_*/Browser/firefox.real
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.
# #include <abstractions/user-download>
# @{HOME}/ r,
+ # Audio support
+ /{,usr/}bin/pulseaudio Pixr,
+
#dbus,
network netlink raw,
network tcp,
- ptrace (trace) peer=torbrowser_plugin_container,
- signal (send) set=("term") peer=torbrowser_plugin_container,
+ ptrace (trace) peer=@{profile_name},
+ signal (receive, send) set=("term") peer=@{profile_name},
deny /etc/host.conf r,
deny /etc/hosts r,
deny /etc/group r,
deny /etc/mailcap r,
- deny /etc/machine-id r,
- deny /var/lib/dbus/machine-id r,
+ /etc/machine-id r,
+ /var/lib/dbus/machine-id r,
/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 @{PROC}/@{pid}/stat r,
owner @{torbrowser_home_dir}/*.so mr,
owner @{torbrowser_home_dir}/.cache/fontconfig/ rwk,
owner @{torbrowser_home_dir}/.cache/fontconfig/** rwkl,
- owner @{torbrowser_home_dir}/components/*.so mr,
- owner @{torbrowser_home_dir}/browser/components/*.so mr,
+ owner @{torbrowser_home_dir}/browser/** r,
+ owner @{torbrowser_home_dir}/{,browser/}components/*.so mr,
+ owner @{torbrowser_home_dir}/Downloads/ rwk,
+ owner @{torbrowser_home_dir}/Downloads/** rwk,
owner @{torbrowser_home_dir}/firefox rix,
- owner @{torbrowser_home_dir}/{,TorBrowser/UpdateInfo/}updates/[0-9]*/updater ix,
- owner @{torbrowser_home_dir}/{,TorBrowser/UpdateInfo/}updates/0/MozUpdater/bgupdate/updater ix,
+ owner @{torbrowser_home_dir}/{,TorBrowser/UpdateInfo/}updates/[0-9]*/* rw,
+ owner @{torbrowser_home_dir}/{,TorBrowser/UpdateInfo/}updates/[0-9]*/{,MozUpdater/bgupdate/}updater ix,
+ owner @{torbrowser_home_dir}/updater ix,
+ owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/.parentwritetest rw,
owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profiles.ini r,
- owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/ 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,
- # Web Content processes
- owner @{torbrowser_firefox_executable} px -> torbrowser_plugin_container,
+ # parent Firefox process when restarting after upgrade, Web Content processes
+ owner @{torbrowser_firefox_executable} pxmr -> torbrowser_firefox,
/etc/mailcap r,
/etc/mime.types r,
/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
# Required for multiprocess Firefox (aka Electrolysis, i.e. e10s)
owner /{dev,run}/shm/org.chromium.* rw,
+ owner /dev/shm/org.mozilla.ipc.[0-9]*.[0-9]* rw, # for Chromium IPC
# Deny access to DRM nodes, that's granted by the X abstraction, which is
# sourced by the gnome abstraction, that we include.
deny @{PROC}/@{pid}/net/route r,
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,
/etc/xfce4/defaults.list r,
/usr/share/xfce4/applications/ r,
+ # u2f (tested with Yubikey 4)
+ /sys/class/ r,
+ /sys/bus/ r,
+ /sys/class/hidraw/ r,
+ /run/udev/data/c24{7,9}:* r,
+ /dev/hidraw* rw,
+ # 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>
}
+++ /dev/null
-#include <tunables/global>
-#include <tunables/torbrowser>
-
-@{torbrowser_firefox_executable} = /home/*/.local/share/torbrowser/tbb/{i686,x86_64}/tor-browser_*/Browser/firefox.real
-
-profile torbrowser_plugin_container {
- #include <abstractions/gnome>
-
- # Uncomment the following lines if you want Tor Browser
- # to have direct access to your sound hardware. You will also
- # need to remove, further bellow:
- # - the "deny" word in the machine-id lines
- # - the rules that deny reading /etc/pulse/client.conf
- # and executing /usr/bin/pulseaudio
- # #include <abstractions/audio>
- # /etc/asound.conf r,
- # owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/tmp/mozilla-temp-* rw,
-
- signal (receive) set=("term") peer=torbrowser_firefox,
-
- deny /etc/host.conf r,
- deny /etc/hosts r,
- deny /etc/nsswitch.conf r,
- deny /etc/resolv.conf r,
- deny /etc/passwd r,
- deny /etc/group r,
- deny /etc/mailcap r,
-
- deny /etc/machine-id r,
- deny /var/lib/dbus/machine-id r,
-
- /etc/mime.types r,
- /usr/share/applications/gnome-mimeapps.list r,
-
- /dev/shm/ r,
-
- owner @{PROC}/@{pid}/environ r,
- owner @{PROC}/@{pid}/fd/ r,
- owner @{PROC}/@{pid}/mountinfo r,
- owner @{PROC}/@{pid}/stat r,
- owner @{PROC}/@{pid}/status r,
- owner @{PROC}/@{pid}/task/*/stat r,
- @{PROC}/sys/kernel/random/uuid r,
-
- owner @{torbrowser_home_dir}/*.dat r,
- owner @{torbrowser_home_dir}/*.manifest r,
- owner @{torbrowser_home_dir}/*.so mr,
- owner @{torbrowser_home_dir}/.cache/fontconfig/ rw,
- owner @{torbrowser_home_dir}/.cache/fontconfig/** rw,
- owner @{torbrowser_home_dir}/browser/** r,
- owner @{torbrowser_home_dir}/components/*.so mr,
- owner @{torbrowser_home_dir}/browser/components/*.so mr,
- owner @{torbrowser_home_dir}/defaults/pref/ r,
- owner @{torbrowser_home_dir}/defaults/pref/*.js r,
- owner @{torbrowser_home_dir}/dependentlibs.list r,
- owner @{torbrowser_home_dir}/fonts/ r,
- owner @{torbrowser_home_dir}/fonts/** r,
- owner @{torbrowser_home_dir}/omni.ja r,
- owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/extensions/*.xpi r,
- owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/startupCache/* r,
- owner @{torbrowser_home_dir}/TorBrowser/Data/Browser/profile.default/tmp/* rw,
- owner @{torbrowser_home_dir}/TorBrowser/Data/fontconfig/fonts.conf r,
- 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}/Downloads/ rwk,
- owner @{torbrowser_home_dir}/Downloads/** rwk,
-
- owner @{torbrowser_firefox_executable} ixmr -> torbrowser_plugin_container,
-
- /sys/devices/system/cpu/ r,
- /sys/devices/system/cpu/present r,
- /sys/devices/system/node/ r,
- /sys/devices/system/node/node[0-9]*/meminfo r,
- deny /sys/devices/virtual/block/*/uevent r,
-
- # Should use abstractions/gstreamer instead once merged upstream
- /etc/udev/udev.conf r,
- /run/udev/data/+pci:* r,
- /sys/devices/pci[0-9]*/**/uevent r,
- owner /{dev,run}/shm/shmfd-* rw,
-
- # Required for multiprocess Firefox (aka Electrolysis, i.e. e10s)
- owner /{dev,run}/shm/org.chromium.* rw,
-
- # Deny access to DRM nodes, that's granted by the X abstraction, which is
- # sourced by the gnome abstraction, that we include.
- deny /dev/dri/** rwklx,
-
- # Silence denial logs about permissions we don't need
- deny /dev/dri/ rwklx,
- deny @{PROC}/@{pid}/net/route r,
- deny /sys/devices/system/cpu/cpufreq/policy[0-9]*/cpuinfo_max_freq r,
- deny /sys/devices/system/cpu/*/cache/index[0-9]*/size r,
-
- # Silence denial logs about PulseAudio
- deny /etc/pulse/client.conf r,
- deny /usr/bin/pulseaudio x,
-
- #include <local/torbrowser.Browser.plugin-container>
-}
# 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
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-03-23 15:47-0700\n"
+"PO-Revision-Date: 2019-12-15 21:28+0100\n"
+"Last-Translator: René Mario Baumgartner <ned84@protonmail.com>\n"
+"Language-Team: \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"X-Poedit-Basepath: .\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: __init__.py:65 launcher.py:470
+msgid "Tor Browser Launcher"
+msgstr "Tor Browser Launcher"
+
+#: __init__.py:66
+msgid "By Micah Lee, licensed under MIT"
+msgstr "Von Micah Lee, lizensiert unter MIT"
+
+#: __init__.py:67
+#, python-brace-format
+msgid "version {0}"
+msgstr "Version {0}"
+
+#: common.py:100
+#, python-brace-format
+msgid "Error creating {0}"
+msgstr "Fehler erzeugt {0}"
+
+#: common.py:102 common.py:180
+#, python-brace-format
+msgid "{0} is not writable"
+msgstr "{0} ist nicht schreibbar"
+
+#: common.py:177
+#, python-brace-format
+msgid "Cannot create directory {0}"
+msgstr "Kann Verzeichnis {0} nicht erzeugen"
+
+#: common.py:187
+msgid "Creating GnuPG homedir"
+msgstr "Erzeuge GnuPG Verzeichnis"
+
+#: common.py:254
+#, python-format
+msgid "Could not import key with fingerprint: %s."
+msgstr "Konnte Schlüssel mit folgendem Fingerabdruck nicht importieren: %s."
+
+#: common.py:259
+msgid "Not all keys were imported successfully!"
+msgstr "Nicht alle Schlüssel konnten erfolgreich importiert werden!"
+
+#: launcher.py:83
+msgid "Downloading Tor Browser for the first time."
+msgstr "Lade Tor Browser das erste mal herunter."
+
+#: launcher.py:85
+msgid ""
+"Your version of Tor Browser is out-of-date. Downloading the newest version."
+msgstr ""
+"Ihre Version des Tor Browsers ist nicht mehr aktuell. Lade die neueste "
+"Version herunter."
+
+#: launcher.py:100
+msgid "Downloading over Tor"
+msgstr "Lade über Tor herunter"
+
+#: launcher.py:111
+msgid "Tor Browser"
+msgstr "Tor Browser"
+
+#: launcher.py:128
+msgid "Start"
+msgstr "Start"
+
+#: launcher.py:174
+msgid "Yes"
+msgstr "Ja"
+
+#: launcher.py:178
+msgid "Exit"
+msgstr "Exit"
+
+#: launcher.py:192 settings.py:136
+msgid "Cancel"
+msgstr "Abbruch"
+
+#: launcher.py:231 launcher.py:245 launcher.py:249 launcher.py:279
+#: launcher.py:281
+msgid "Downloading"
+msgstr "Herunterladen"
+
+#: launcher.py:238
+msgid "Latest version: {}"
+msgstr "Aktuelle Version: {}"
+
+#: launcher.py:241
+msgid "Error detecting Tor Browser version."
+msgstr "Fehler bei dem Versuch die Tor Browser Version zu finden."
+
+#: launcher.py:256 launcher.py:357
+msgid "Verifying Signature"
+msgstr "Prüfe Signatur"
+
+#: launcher.py:260
+msgid "Extracting"
+msgstr "Extrahieren"
+
+#: launcher.py:264
+msgid "Running"
+msgstr "Läuft"
+
+#: launcher.py:268
+msgid "Starting download over again"
+msgstr "Starte download nochmals"
+
+#: launcher.py:279 launcher.py:295
+msgid "(over Tor)"
+msgstr "(über Tor)"
+
+#: launcher.py:293
+msgid "Downloaded"
+msgstr "Heruntergeladen"
+
+#: launcher.py:393
+msgid "Installing"
+msgstr "Installiere"
+
+#: launcher.py:401
+#, python-brace-format
+msgid "Tor Browser Launcher doesn't understand the file format of {0}"
+msgstr "Tor Browser versteht folgendes Datei Format nicht: {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 ""
+"Die Version ihres Tor Browsers ist neuer als sie sein sollte, was auf eine "
+"Attacke schließen lassen könnte!"
+
+#: launcher.py:446
+msgid "Downloading Tor Browser over again."
+msgstr "Lade Tor Browser nochmals herunter."
+
+#: launcher.py:516 launcher.py:525 launcher.py:533
+msgid "Download Error:"
+msgstr "Fehler beim herunterladen:"
+
+#: launcher.py:517
+msgid "You are currently using a non-default mirror"
+msgstr "Sie verwenden zur Zeit einen nicht voreingestellten Mirror."
+
+#: launcher.py:518
+msgid "Would you like to switch back to the default?"
+msgstr "Wollen sie zur Voreinstellung wechseln?"
+
+#: launcher.py:527
+msgid "Would you like to try the English version of Tor Browser instead?"
+msgstr ""
+"Wollen sie die englische Version des Tor Browsers stattdessen versuchen?"
+
+#: launcher.py:548
+#, python-brace-format
+msgid ""
+"Invalid SSL certificate for:\n"
+"{0}\n"
+"\n"
+"You may be under attack."
+msgstr ""
+"Ungültiges SSL Zertifikat für:\n"
+"{0}\n"
+"\n"
+"Möglicherweise werden sie angegriffen."
+
+#: launcher.py:550
+msgid "Try the download again using Tor?"
+msgstr "Wollen sie das herunterladen nochmals über Tor versuchen?"
+
+#: 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 ""
+"Fehler beim starten des Downloads:\n"
+"\n"
+"{0}\n"
+"\n"
+"Versuche über Tor herunterzuladen. Sind sie sicher das Tor richtig "
+"konfiguriert und aktiv ist?"
+
+#: launcher.py:563
+#, python-brace-format
+msgid ""
+"Error starting download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Are you connected to the internet?"
+msgstr ""
+"Fehler beim starten des Downloads:\n"
+"\n"
+"{0}\n"
+"\n"
+"Sind sie mit dem Internet verbunden?"
+
+#: settings.py:46
+msgid "Tor Browser Launcher Settings"
+msgstr "Tor Browser Launcher Einstellungen"
+
+#: settings.py:50
+msgid "Download over system Tor"
+msgstr "Herunterladen über system Tor"
+
+#: settings.py:57
+msgid "Force downloading English version of Tor Browser"
+msgstr "Herunterladen der englischen Version des Tor Browsers erzwingen"
+
+#: settings.py:66
+msgid "Tor server"
+msgstr "Tor server"
+
+#: settings.py:82
+msgid "Status: Installed"
+msgstr "Status: Installiert"
+
+#: settings.py:84
+msgid "Status: Not Installed"
+msgstr "Status: Nicht installiert"
+
+#: settings.py:87
+msgid "Install Tor Browser"
+msgstr "Tor Browser installieren"
+
+#: settings.py:92
+msgid "Reinstall Tor Browser"
+msgstr "Tor Browser nochmals installieren"
+
+#: settings.py:115
+msgid "Mirror"
+msgstr "Mirror"
+
+#: settings.py:131
+msgid "Save && Exit"
+msgstr "Speichern && Exit"
#: __init__.py:66
msgid "By Micah Lee, licensed under MIT"
-msgstr "Par Micah Lee, sous license MIT"
+msgstr "Par Micah Lee, sous licence MIT"
#: __init__.py:67
#, python-brace-format
msgid "version {0}"
-msgstr ""
+msgstr "version {0}"
#: common.py:100
#, python-brace-format
#: common.py:187
msgid "Creating GnuPG homedir"
-msgstr "Creation du dossier GnuPG"
+msgstr "Création du dossier GnuPG"
#: common.py:254
#, python-format
msgid "Could not import key with fingerprint: %s."
-msgstr "Impossible d'importer la clé: %s"
+msgstr "Impossible d'importer la clé avec l'empreinte : %s."
#: common.py:259
msgid "Not all keys were imported successfully!"
-msgstr "Certaines clés n'ont pas pu être importées"
+msgstr "Certaines clés n'ont pas pu être importées !"
#: launcher.py:83
-#, fuzzy
msgid "Downloading Tor Browser for the first time."
-msgstr "Premier téléchargement et installation du Tor Browser"
+msgstr "Téléchargement du Navigateur Tor pour la première fois."
#: launcher.py:85
-#, fuzzy
msgid ""
"Your version of Tor Browser is out-of-date. Downloading the newest version."
msgstr ""
-"Votre version du Tor Browser est obsolète. Téléchargement et installation de "
-"la nouvelle version."
+"Votre version du Navigateur Tor est obsolète. Téléchargement de la nouvelle "
+"version."
#: launcher.py:100
msgid "Downloading over Tor"
#: launcher.py:111
msgid "Tor Browser"
-msgstr "Tor Browser"
+msgstr "Navigateur Tor"
#: launcher.py:128
msgid "Start"
#: launcher.py:174
msgid "Yes"
-msgstr ""
+msgstr "Oui"
#: launcher.py:178
msgid "Exit"
-msgstr ""
+msgstr "Quitter"
#: launcher.py:192 settings.py:136
msgid "Cancel"
#: launcher.py:238
msgid "Latest version: {}"
-msgstr "Dernière version: {}"
+msgstr "Dernière version : {}"
#: launcher.py:241
msgid "Error detecting Tor Browser version."
-msgstr "Impossible de détecter la version du Tor Browser."
+msgstr "Impossible de détecter la version du Navigateur Tor."
#: launcher.py:256 launcher.py:357
msgid "Verifying Signature"
#: launcher.py:279 launcher.py:295
msgid "(over Tor)"
-msgstr ""
+msgstr "(à travers Tor)"
#: launcher.py:293
msgid "Downloaded"
"The version of Tor Browser you have installed is earlier than it should be, "
"which could be a sign of an attack!"
msgstr ""
-"La version du Tor Browser installée est antérieure à l'actuelle, ce qui peut "
-"être la signature d'une attaque!"
+"La version du Navigateur Tor que vous avez installé est antérieure à ce "
+"qu'elle devrait, ce qui peut être le signe d'une attaque !"
#: launcher.py:446
-#, fuzzy
msgid "Downloading Tor Browser over again."
-msgstr "Nouveau téléchargement du Tor Browser Bundle."
+msgstr "Télécharger le Navigateur Tor à nouveau."
#: launcher.py:516 launcher.py:525 launcher.py:533
msgid "Download Error:"
-msgstr "Erreur de téléchargement:"
+msgstr "Erreur de téléchargement :"
#: launcher.py:517
msgid "You are currently using a non-default mirror"
-msgstr "Vous utilisez actuellement un miroir non-défaut"
+msgstr "Vous utilisez actuellement un miroir n'étant pas celui par défaut"
#: launcher.py:518
msgid "Would you like to switch back to the default?"
-msgstr "Voulez-vous revenir à la valeur par défaut?"
+msgstr "Voulez-vous revenir à la valeur par défaut ?"
#: launcher.py:527
msgid "Would you like to try the English version of Tor Browser instead?"
-msgstr "Voulez-vous essayer la version anglophone du Tor Browser à la place?"
+msgstr ""
+"Voulez-vous essayer la version anglophone du Navigateur Tor à la place ?"
#: launcher.py:548
#, python-brace-format
"\n"
"You may be under attack."
msgstr ""
+"Certificat SSL invalide pour :\n"
+"{0}\n"
+"\n"
+"Vous pourriez être attaqué."
#: launcher.py:550
msgid "Try the download again using Tor?"
-msgstr "Essayez de télécharger à nouveau à travers Tor?"
+msgstr "Essayer de télécharger à nouveau à travers Tor ?"
#: launcher.py:559
#, python-brace-format
"Trying to download over Tor. Are you sure Tor is configured correctly and "
"running?"
msgstr ""
+"Impossible de démarrer le téléchargement :\n"
+"\n"
+"{0}\n"
+"\n"
+"Essayez de télécharger à travers Tor. Êtes-vous sûr que Tor est configuré "
+"correctement et fonctionne ?"
#: launcher.py:563
#, python-brace-format
"\n"
"Are you connected to the internet?"
msgstr ""
-"Impossible de démarrer le téléchargement:\n"
+"Impossible de démarrer le téléchargement :\n"
"\n"
"{0}\n"
"\n"
-"Êtes-vous connecté à Internet?"
+"Êtes-vous connecté à Internet ?"
#: settings.py:46
msgid "Tor Browser Launcher Settings"
#: settings.py:57
msgid "Force downloading English version of Tor Browser"
-msgstr "Forcer le téléchargement de la version anglophone du Tor Browser"
+msgstr "Forcer le téléchargement de la version anglophone du Navigateur Tor"
#: settings.py:66
msgid "Tor server"
#: settings.py:82
msgid "Status: Installed"
-msgstr "Status: Installé"
+msgstr "Statut : Installé"
#: settings.py:84
msgid "Status: Not Installed"
-msgstr "Status: Pas installé"
+msgstr "Statut : Pas installé"
#: settings.py:87
msgid "Install Tor Browser"
-msgstr "Installer le Tor Browser"
+msgstr "Installer le Navigateur Tor"
#: settings.py:92
msgid "Reinstall Tor Browser"
-msgstr "Réinstaller le Tor Browser"
+msgstr "Réinstaller le Navigateur Tor"
#: settings.py:115
msgid "Mirror"
msgstr "Miroir"
#: settings.py:131
-#, fuzzy
msgid "Save && Exit"
msgstr "Enregistrer et quitter"
--- /dev/null
+# Croatian translation of Tor Browser Launcher.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Milo Ivir <mail@milotype.de>, 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Tor Browser Launcher\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-03-23 15:47-0700\n"
+"PO-Revision-Date: 2020-01-22 13:20+0100\n"
+"Last-Translator: Milo Ivir <mail@milotype.de>\n"
+"Language-Team: \n"
+"Language: hr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.12\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: __init__.py:65 launcher.py:470
+msgid "Tor Browser Launcher"
+msgstr "Pokretač Tor preglednika"
+
+#: __init__.py:66
+msgid "By Micah Lee, licensed under MIT"
+msgstr "Autor Micah Lee, MIT licenca"
+
+#: __init__.py:67
+#, python-brace-format
+msgid "version {0}"
+msgstr "verzija {0}"
+
+#: common.py:100
+#, python-brace-format
+msgid "Error creating {0}"
+msgstr "Greška prilikom stvaranja {0}"
+
+#: common.py:102 common.py:180
+#, python-brace-format
+msgid "{0} is not writable"
+msgstr "Nije moguće pisati u {0}"
+
+#: common.py:177
+#, python-brace-format
+msgid "Cannot create directory {0}"
+msgstr "Nije moguće stvoriti mapu {0}"
+
+#: common.py:187
+msgid "Creating GnuPG homedir"
+msgstr "Izrada početne mape za GnuPG"
+
+#: common.py:254
+#, python-format
+msgid "Could not import key with fingerprint: %s."
+msgstr "Nije bilo moguće uvesti ključ s otiskom prsta: %s."
+
+#: common.py:259
+msgid "Not all keys were imported successfully!"
+msgstr "Nisu svi ključevi uspješno uvezeni!"
+
+#: launcher.py:83
+msgid "Downloading Tor Browser for the first time."
+msgstr "Preuzimanje Tor preglednika po prvi put."
+
+#: launcher.py:85
+msgid ""
+"Your version of Tor Browser is out-of-date. Downloading the newest version."
+msgstr ""
+"Tvoja verzija Tor preglednika je zastarjela. Preuzima se najnovija verzija."
+
+#: launcher.py:100
+msgid "Downloading over Tor"
+msgstr "Preuzimanje preko Tora"
+
+#: launcher.py:111
+msgid "Tor Browser"
+msgstr "Tor preglednik"
+
+#: launcher.py:128
+msgid "Start"
+msgstr "Pokreni"
+
+#: launcher.py:174
+msgid "Yes"
+msgstr "Da"
+
+#: launcher.py:178
+msgid "Exit"
+msgstr "Zatvori"
+
+#: launcher.py:192 settings.py:136
+msgid "Cancel"
+msgstr "Odustani"
+
+#: launcher.py:231 launcher.py:245 launcher.py:249 launcher.py:279
+#: launcher.py:281
+msgid "Downloading"
+msgstr "Preuzimanje"
+
+#: launcher.py:238
+msgid "Latest version: {}"
+msgstr "Zadnja verzija: {}"
+
+#: launcher.py:241
+msgid "Error detecting Tor Browser version."
+msgstr "Greška prilikom otkrivanja verzije Tor preglednika."
+
+#: launcher.py:256 launcher.py:357
+msgid "Verifying Signature"
+msgstr "Potvrđivanje potpisa"
+
+#: launcher.py:260
+msgid "Extracting"
+msgstr "Otpakiravanje"
+
+#: launcher.py:264
+msgid "Running"
+msgstr "Izvršava se"
+
+#: launcher.py:268
+msgid "Starting download over again"
+msgstr "Pokreće se ponovno preuzimanje"
+
+#: launcher.py:279 launcher.py:295
+msgid "(over Tor)"
+msgstr "(preko Tora)"
+
+#: launcher.py:293
+msgid "Downloaded"
+msgstr "Preuzeto"
+
+#: launcher.py:393
+msgid "Installing"
+msgstr "Instaliranje"
+
+#: launcher.py:401
+#, python-brace-format
+msgid "Tor Browser Launcher doesn't understand the file format of {0}"
+msgstr "Pokretač Tor preglednika ne razumije format datoteke {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 ""
+"Tvoja verzija Tor preglednika je starija nego što bi trebala biti, što može "
+"biti znak napada!"
+
+#: launcher.py:446
+msgid "Downloading Tor Browser over again."
+msgstr "Ponovno preuzimanje Tor preglednika."
+
+#: launcher.py:516 launcher.py:525 launcher.py:533
+msgid "Download Error:"
+msgstr "Greška prilikom preuzimanja:"
+
+#: launcher.py:517
+msgid "You are currently using a non-default mirror"
+msgstr "Trenutačno koristiš nestandardno zrcalo"
+
+#: launcher.py:518
+msgid "Would you like to switch back to the default?"
+msgstr "Želiš li vratiti na standardno?"
+
+#: launcher.py:527
+msgid "Would you like to try the English version of Tor Browser instead?"
+msgstr "Želiš li umjesto toga isprobati englesku verziju Tor preglednika?"
+
+#: launcher.py:548
+#, python-brace-format
+msgid ""
+"Invalid SSL certificate for:\n"
+"{0}\n"
+"\n"
+"You may be under attack."
+msgstr ""
+"Neispravni SSL certifikat za:\n"
+"{0}\n"
+"\n"
+"Možda si napadnut/a."
+
+#: launcher.py:550
+msgid "Try the download again using Tor?"
+msgstr "Ponovo pokušati preuzeti pomoću Tora?"
+
+#: 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 ""
+"Greška prilikom pokretanja preuzimanja:\n"
+"\n"
+"{0}\n"
+"\n"
+"Pokušava se preuzeti preko Tora. Je li je Tor ispravno konfiguriran i "
+"pokrenut?"
+
+#: launcher.py:563
+#, python-brace-format
+msgid ""
+"Error starting download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Are you connected to the internet?"
+msgstr ""
+"Greška prilikom pokretanja preuzimanja:\n"
+"\n"
+"{0}\n"
+"\n"
+"Jesi li spojen/a na internet?"
+
+#: settings.py:46
+msgid "Tor Browser Launcher Settings"
+msgstr "Postavke pokretača Tor preglednika"
+
+#: settings.py:50
+msgid "Download over system Tor"
+msgstr "Preuzmi preko Tora sustava"
+
+#: settings.py:57
+msgid "Force downloading English version of Tor Browser"
+msgstr "Prisili preuzimanje engleske verzije Tor preglednika"
+
+#: settings.py:66
+msgid "Tor server"
+msgstr "Tor poslužitelj"
+
+#: settings.py:82
+msgid "Status: Installed"
+msgstr "Stanje: Instalirano"
+
+#: settings.py:84
+msgid "Status: Not Installed"
+msgstr "Stanje: Nije instalirano"
+
+#: settings.py:87
+msgid "Install Tor Browser"
+msgstr "Instaliraj Tor preglednik"
+
+#: settings.py:92
+msgid "Reinstall Tor Browser"
+msgstr "Ponovo instaliraj Tor preglednik"
+
+#: settings.py:115
+msgid "Mirror"
+msgstr "Zrcalo"
+
+#: settings.py:131
+msgid "Save && Exit"
+msgstr "Spremi i zatvori"
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+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-02-09 16:35-0300\n"
+"Language: pt_BR\n"
+"Language-Team: \n"
+"Last-Translator: João Vítor S. F. <jjoaovitor7@outlook.com.br>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: __init__.py:65 launcher.py:470
+msgid "Tor Browser Launcher"
+msgstr "Lançador do Navegador Tor"
+
+#: __init__.py:66
+msgid "By Micah Lee, licensed under MIT"
+msgstr "Por Micah Lee, licenciado sob o MIT"
+
+#: __init__.py:67
+#, python-brace-format
+msgid "version {0}"
+msgstr "versão {0}"
+
+#: common.py:100
+#, python-brace-format
+msgid "Error creating {0}"
+msgstr "Erro ao criar {0}"
+
+#: common.py:102 common.py:180
+#, python-brace-format
+msgid "{0} is not writable"
+msgstr "{0} não é gravável"
+
+#: common.py:177
+#, python-brace-format
+msgid "Cannot create directory {0}"
+msgstr "Não é possível criar o diretório {0}"
+
+#: common.py:187
+msgid "Creating GnuPG homedir"
+msgstr "Criando o diretório inicial do GnuPG"
+
+#: common.py:254
+#, python-format
+msgid "Could not import key with fingerprint: %s."
+msgstr "Não foi possível importar a chave com a impressão digital: %s."
+
+#: common.py:259
+msgid "Not all keys were imported successfully!"
+msgstr "Nem todas as chaves foram importadas com sucesso!"
+
+#: launcher.py:83
+#, fuzzy
+msgid "Downloading Tor Browser for the first time."
+msgstr "Baixando o Navegador Tor pela primeira vez."
+
+#: launcher.py:85
+#, fuzzy
+msgid "Your version of Tor Browser is out-of-date. Downloading the newest version."
+msgstr "Sua versão do Navegador Tor está desatualizada. Baixando a versão mais recente."
+
+#: launcher.py:100
+msgid "Downloading over Tor"
+msgstr "Baixando pelo Tor"
+
+#: launcher.py:111
+msgid "Tor Browser"
+msgstr "Navegador Tor"
+
+#: launcher.py:128
+msgid "Start"
+msgstr "Iniciar"
+
+#: launcher.py:174
+msgid "Yes"
+msgstr "Sim"
+
+#: launcher.py:178
+msgid "Exit"
+msgstr "Sair"
+
+#: launcher.py:192 settings.py:136
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: launcher.py:231 launcher.py:245 launcher.py:249 launcher.py:279
+#: launcher.py:281
+msgid "Downloading"
+msgstr "Baixando"
+
+#: launcher.py:238
+msgid "Latest version: {}"
+msgstr "Versão mais recente: {}"
+
+#: launcher.py:241
+msgid "Error detecting Tor Browser version."
+msgstr "Erro ao detectar a versão do Navegador Tor."
+
+#: launcher.py:256 launcher.py:357
+msgid "Verifying Signature"
+msgstr "Verificando Assinatura"
+
+#: launcher.py:260
+msgid "Extracting"
+msgstr "Extraindo"
+
+#: launcher.py:264
+msgid "Running"
+msgstr "Rodando"
+
+#: launcher.py:268
+msgid "Starting download over again"
+msgstr "Iniciando o download novamente"
+
+#: launcher.py:279 launcher.py:295
+msgid "(over Tor)"
+msgstr "(pelo Tor)"
+
+#: launcher.py:293
+msgid "Downloaded"
+msgstr "Baixado"
+
+#: launcher.py:393
+msgid "Installing"
+msgstr "Instalando"
+
+#: launcher.py:401
+#, python-brace-format
+msgid "Tor Browser Launcher doesn't understand the file format of {0}"
+msgstr "O Lançador do Navegador Tor não entende o formato do arquivo de {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 ""
+"A versão do Navegador Tor que você instalou é anterior ao que deveria ser,
+"o que poderia ser um sinal de ataque!"
+
+#: launcher.py:446
+#, fuzzy
+msgid "Downloading Tor Browser over again."
+msgstr "Baixando o Navegador Tor novamente.."
+
+#: launcher.py:516 launcher.py:525 launcher.py:533
+msgid "Download Error:"
+msgstr "Erro de Download:"
+
+#: launcher.py:517
+msgid "You are currently using a non-default mirror"
+msgstr "No momento você está usando espelho não-padrão"
+
+#: launcher.py:518
+msgid "Would you like to switch back to the default?"
+msgstr "Você gostaria de retornar para o padrão?"
+
+#: launcher.py:527
+msgid "Would you like to try the English version of Tor Browser instead?"
+msgstr "Você gostaria de experimentar a versão em inglês do Navegador Tor?"
+
+#: launcher.py:548
+#, python-brace-format
+msgid ""
+"Invalid SSL certificate for:\n"
+"{0}\n"
+"\n"
+"You may be under attack."
+msgstr ""
+"Certificado SSL inválido para:\n"
+"{0}\n"
+"\n"
+"Você pode estar sob ataque."
+
+#: launcher.py:550
+msgid "Try the download again using Tor?"
+msgstr "Tentar o download novamente usando o 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 ""
+"Erro ao iniciar o download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Tentando fazer o download pelo Tor. Você tem certeza de que o Tor está configurado corretamente e "
+"e funcionando?"
+
+#: launcher.py:563
+#, python-brace-format
+msgid ""
+"Error starting download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Are you connected to the internet?"
+msgstr ""
+"Erro ao iniciar o download:\n"
+"\n"
+"{0}\n"
+"\n"
+"Você está conectado à internet?"
+
+#: settings.py:46
+msgid "Tor Browser Launcher Settings"
+msgstr "Configurações do Lançador do Navegador Tor"
+
+#: settings.py:50
+msgid "Download over system Tor"
+msgstr "Download pelo sistema Tor"
+
+#: settings.py:57
+msgid "Force downloading English version of Tor Browser"
+msgstr "Forçar o download da versão em Inglês do Navegador Tor"
+
+#: settings.py:66
+msgid "Tor server"
+msgstr "Servidor Tor"
+
+#: settings.py:82
+msgid "Status: Installed"
+msgstr "Estado: Instalado"
+
+#: settings.py:84
+msgid "Status: Not Installed"
+msgstr "Estado: Não Instalado"
+
+#: settings.py:87
+msgid "Install Tor Browser"
+msgstr "Instalar o Navegador Tor"
+
+#: settings.py:92
+msgid "Reinstall Tor Browser"
+msgstr "Reinstalar o Navegador Tor"
+
+#: settings.py:115
+msgid "Mirror"
+msgstr "Espelho"
+
+#: settings.py:131
+#, fuzzy
+msgid "Save && Exit"
+msgstr "Salvar && Sair"
+
+#~ msgid ""
+#~ "The python-txsocksx package is missing, downloads will not happen over tor"
+#~ msgstr
+#~ "O pacote python-txsocksx está faltando, os downloads não acontecerão pelo tor"
+
+#~ msgid "DNS Lookup Error"
+#~ msgstr "Erro de Pesquisa de DNS"
+
+#~ msgid ""
+#~ "The SSL certificate served by https://www.torproject.org is invalid! You "
+#~ "may be under attack."
+#~ msgstr ""
+#~ "O certificado SSL fornecido por https://www.torproject.org é inválido! Você "
+#~ "pode estar sob ataque."
+
+#~ msgid "Error connecting to Tor at {0}"
+#~ msgstr "Erro ao conectar ao Tor em {0}"
+
+#~ msgid ""
+#~ "SIGNATURE VERIFICATION FAILED!\n"
+#~ "\n"
+#~ "You might be under attack, or there might just be a networking problem. "
+#~ "Click Start try the download again."
+#~ msgstr ""
+#~ "FALHA NA VERIFICAÇÃO DA ASSINATURA!\n"
+#~ "\n"
+#~ "Você pode estar sob ataque, ou pode haver apenas um problema de rede. "
+#~ "Clique em Iniciar para tentar baixar novamente."
+
+#~ msgid ""
+#~ "The python-pygame package is missing, the modem sound is unavailable."
+#~ msgstr ""
+#~ "O pacote python-pygame está faltando, o som do modem não está disponível."
+
+#~ msgid ""
+#~ "This option is only available when using a system wide Tor installation."
+#~ msgstr ""
+#~ "Esta opção está apenas disponível ao usar uma instalação Tor do sistema."
+
+#~ msgid "This option requires the python-txsocksx package."
+#~ msgstr "Esta opção precisa do pacote python-txsocksx"
+
+#~ msgid "Play modem sound, because Tor is slow :]"
+#~ msgstr "Reproduzir som do modem, porque o Tor é lento :]"
+
+#~ msgid "This option requires python-pygame to be installed"
+#~ msgstr "Esta opção precisa do pacote python-pygame"
--- /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 = []
for root, dirs, files in os.walk(SHARE):
- datafiles.append((os.path.join(sys.prefix, root),
- [os.path.join(root, f) for f in files]))
+ if files:
+ 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.Browser.plugin-container',
- 'apparmor/torbrowser.Tor.tor']),
- ('/etc/apparmor.d/local/', [
- 'apparmor/local/torbrowser.Browser.firefox',
- 'apparmor/local/torbrowser.Browser.plugin-container',
- '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 += [('/usr/share/locale/', create_mo_files())]
+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[hr]=Tor preglednik – postavke pokretanja
Name[hu]=Tor-böngésző indító beállításai
Name[nl]=Tor Browser Launcher Instellingen
+Name[pt_BR]=Navegador Tor (Configurações do Lançador)
Name[ru]=Tor Browser (настройки запуска)
GenericName=Tor Browser Launcher Settings
+GenericName[hr]=Tor preglednik – postavke pokretanja
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[hr]=Postavke za pokretanje Tor preglednika
Comment[hu]=Tor-böngésző indító beállításai
Comment[nl]=Tor Browser Launcher Instellingen
+Comment[pt_BR]=Navegador Tor (Configurações do Lançador)
Comment[ru]=Tor Browser (настройки запуска)
Exec=torbrowser-launcher --settings
Terminal=false
[Desktop Entry]
Name=Tor Browser
+Name[cs]=Tor Browser
+Name[hr]=Tor preglednik
Name[hu]=Tor-böngésző
+Name[pt_BR]=Navegador Tor
+GenericName=Tor Browser
+GenericName[hu]=Tor böngésző indító
+Comment=Launch Tor Browser
+Comment[cs]=Spustit Tor Browser
+Name[hu]=Tor-böngésző
+Name[pt_BR]=Navegador Tor
GenericName=Tor browser
+GenericName[hr]=Tor preglednik
GenericName[hu]=Tor böngésző indító
Comment=Launch Tor Browser
+Comment[hr]=Pokreni Tor preglednika
Comment[hu]=Tor böngésző indító
+Comment[pt_BR]=Navegador Tor
Exec=torbrowser-launcher %u
Terminal=false
Type=Application
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2014 Micah Lee <micah@micahflee.com> -->
-<component>
- <id>org.torproject.torbrowser.desktop</id>
+<component type="desktop-application">
+ <id>torbrowser.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<name>Tor Browser Launcher</name>
</screenshots>
<url type="homepage">https://github.com/micahflee/torbrowser-launcher</url>
<update_contact>micah@micahflee.com</update_contact>
+ <content_rating type="oars-1.1"/>
</component>
+++ /dev/null
------BEGIN CERTIFICATE-----
-MIIFizCCA3OgAwIBAgIJAK9zyLTPn4CPMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV
-BAYTAk5PMQ0wCwYDVQQIDARPc2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5u
-ZXQgQ0ExHjAcBgNVBAMMFXNrcy1rZXlzZXJ2ZXJzLm5ldCBDQTAeFw0xMjEwMDkw
-MDMzMzdaFw0yMjEwMDcwMDMzMzdaMFwxCzAJBgNVBAYTAk5PMQ0wCwYDVQQIDARP
-c2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5uZXQgQ0ExHjAcBgNVBAMMFXNr
-cy1rZXlzZXJ2ZXJzLm5ldCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
-ggIBANdsWy4PXWNUCkS3L//nrd0GqN3dVwoBGZ6w94Tw2jPDPifegwxQozFXkG6I
-6A4TK1CJLXPvfz0UP0aBYyPmTNadDinaB9T4jIwd4rnxl+59GiEmqkN3IfPsv5Jj
-MkKUmJnvOT0DEVlEaO1UZIwx5WpfprB3mR81/qm4XkAgmYrmgnLXd/pJDAMk7y1F
-45b5zWofiD5l677lplcIPRbFhpJ6kDTODXh/XEdtF71EAeaOdEGOvyGDmCO0GWqS
-FDkMMPTlieLA/0rgFTcz4xwUYj/cD5e0ZBuSkYsYFAU3hd1cGfBue0cPZaQH2HYx
-Qk4zXD8S3F4690fRhr+tki5gyG6JDR67aKp3BIGLqm7f45WkX1hYp+YXywmEziM4
-aSbGYhx8hoFGfq9UcfPEvp2aoc8u5sdqjDslhyUzM1v3m3ZGbhwEOnVjljY6JJLx
-MxagxnZZSAY424ZZ3t71E/Mn27dm2w+xFRuoy8JEjv1d+BT3eChM5KaNwrj0IO/y
-u8kFIgWYA1vZ/15qMT+tyJTfyrNVV/7Df7TNeWyNqjJ5rBmt0M6NpHG7CrUSkBy9
-p8JhimgjP5r0FlEkgg+lyD+V79H98gQfVgP3pbJICz0SpBQf2F/2tyS4rLm+49rP
-fcOajiXEuyhpcmzgusAj/1FjrtlynH1r9mnNaX4e+rLWzvU5AgMBAAGjUDBOMB0G
-A1UdDgQWBBTkwyoJFGfYTVISTpM8E+igjdq28zAfBgNVHSMEGDAWgBTkwyoJFGfY
-TVISTpM8E+igjdq28zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAR
-OXnYwu3g1ZjHyley3fZI5aLPsaE17cOImVTehC8DcIphm2HOMR/hYTTL+V0G4P+u
-gH+6xeRLKSHMHZTtSBIa6GDL03434y9CBuwGvAFCMU2GV8w92/Z7apkAhdLToZA/
-X/iWP2jeaVJhxgEcH8uPrnSlqoPBcKC9PrgUzQYfSZJkLmB+3jEa3HKruy1abJP5
-gAdQvwvcPpvYRnIzUc9fZODsVmlHVFBCl2dlu/iHh2h4GmL4Da2rRkUMlbVTdioB
-UYIvMycdOkpH5wJftzw7cpjsudGas0PARDXCFfGyKhwBRFY7Xp7lbjtU5Rz0Gc04
-lPrhDf0pFE98Aw4jJRpFeWMjpXUEaG1cq7D641RpgcMfPFvOHY47rvDTS7XJOaUT
-BwRjmDt896s6vMDcaG/uXJbQjuzmmx3W2Idyh3s5SI0GTHb0IwMKYb4eBUIpQOnB
-cE77VnCYqKvN1NVYAqhWjXbY7XasZvszCRcOG+W3FqNaHOK/n/0ueb0uijdLan+U
-f4p1bjbAox8eAOQS/8a3bzkJzdyBNUKGx1BIK2IBL9bn/HravSDOiNRSnZ/R3l9G
-ZauX0tu7IIDlRCILXSyeazu0aj/vdT3YFQXPcvt5Fkf5wiNTo53f72/jYEJd6qph
-WrpoKqrwGwTpRUCMhYIUt65hsTxCiJJ5nKe39h46sg==
------END CERTIFICATE-----
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
-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
-=/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",
+ }
+
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
+ def get_env(var_name, default_value):
+ value = os.getenv(var_name)
+ if not value:
+ value = default_value
+ return value
# 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}/.config/torbrowser'.format(homedir)
- tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
- tbb_local = '{0}/.local/share/torbrowser'.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'),
- 'keyserver_ca': os.path.join(SHARE, 'sks-keyservers.netCA.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://hkps.pool.sks-keyservers.net',
- '--keyserver-options', 'ca-cert-file=' + self.paths['keyserver_ca']
- + ',include-revoked,no-honor-keyserver-url,no-honor-pka-record',
- '--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()