]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser_launcher/common.py
Update available languages for torbrowser
[torbrowser-launcher.git] / torbrowser_launcher / common.py
1 """
2 Tor Browser Launcher
3 https://github.com/micahflee/torbrowser-launcher/
4
5 Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
6
7 Permission is hereby granted, free of charge, to any person
8 obtaining a copy of this software and associated documentation
9 files (the "Software"), to deal in the Software without
10 restriction, including without limitation the rights to use,
11 copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following
14 conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 OTHER DEALINGS IN THE SOFTWARE.
27 """
28
29 import os
30 import sys
31 import platform
32 import subprocess
33 import locale
34 import pickle
35 import json
36 import re
37 import gettext
38 import gpg
39
40 SHARE = os.getenv("TBL_SHARE", sys.prefix + "/share") + "/torbrowser-launcher"
41
42 gettext.install("torbrowser-launcher")
43
44 # We're looking for output which:
45 #
46 #  1. The first portion must be `[GNUPG:] IMPORT_OK`
47 #  2. The second must be an integer between [0, 15], inclusive
48 #  3. The third must be an uppercased hex-encoded 160-bit fingerprint
49 gnupg_import_ok_pattern = re.compile(
50     b"(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})"
51 )
52
53
54 class Common(object):
55     def __init__(self, tbl_version):
56         self.tbl_version = tbl_version
57
58         # initialize the app
59         self.default_mirror = "https://dist.torproject.org/"
60         self.discover_arch_lang()
61         self.build_paths()
62         for d in self.paths["dirs"]:
63             self.mkdir(self.paths["dirs"][d])
64         self.load_mirrors()
65         self.load_settings()
66         self.mkdir(self.paths["download_dir"])
67         self.mkdir(self.paths["tbb"]["dir"])
68         self.init_gnupg()
69
70     # discover the architecture and language
71     def discover_arch_lang(self):
72         # figure out the architecture
73         self.architecture = "x86_64" if "64" in platform.architecture()[0] else "i686"
74
75         # figure out the language
76         available_languages = [
77             "ar",
78             "ca",
79             "cs",
80             "da",
81             "de",
82             "el",
83             "en-US",
84             "es-AR",
85             "es-ES",
86             "fa",
87             "fr",
88             "ga-IE",
89             "he",
90             "hu",
91             "id",
92             "is",
93             "it",
94             "ja",
95             "ka",
96             "ko",
97             "lt",
98             "mk",
99             "ms",
100             "nb-NO",
101             "nl",
102             "pl",
103             "pt-BR",
104             "ro",
105             "ru",
106             "sv-SE",
107             "th",
108             "tr",
109             "vi",
110             "zh-CN",
111             "zh-TW",
112         ]
113         default_locale = locale.getlocale()[0]
114         if default_locale is None:
115             self.language = "en-US"
116         else:
117             self.language = default_locale.replace("_", "-")
118             if self.language not in available_languages:
119                 self.language = self.language.split("-")[0]
120                 if self.language not in available_languages:
121                     for l in available_languages:
122                         if l[0:2] == self.language:
123                             self.language = l
124             # if language isn't available, default to english
125             if self.language not in available_languages:
126                 self.language = "en-US"
127
128     # get value of environment variable, if it is not set return the default value
129     @staticmethod
130     def get_env(var_name, default_value):
131         value = os.getenv(var_name)
132         if not value:
133             value = default_value
134         return value
135
136     # build all relevant paths
137     def build_paths(self, tbb_version=None):
138         homedir = os.getenv("HOME")
139         if not homedir:
140             homedir = "/tmp/.torbrowser-" + os.getenv("USER")
141             if not os.path.exists(homedir):
142                 try:
143                     os.mkdir(homedir, 0o700)
144                 except:
145                     self.set_gui(
146                         "error", _("Error creating {0}").format(homedir), [], False
147                     )
148         if not os.access(homedir, os.W_OK):
149             self.set_gui("error", _("{0} is not writable").format(homedir), [], False)
150
151         tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
152         tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
153         tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
154         old_tbb_data = '{0}/.torbrowser'.format(homedir)
155
156         if tbb_version:
157             # tarball filename
158             if self.architecture == "x86_64":
159                 arch = "linux64"
160             else:
161                 arch = "linux32"
162
163             if hasattr(self, "settings") and self.settings["force_en-US"]:
164                 language = "en-US"
165             else:
166                 language = self.language
167             tarball_filename = (
168                 "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
169             )
170
171             # tarball
172             self.paths["tarball_url"] = (
173                 "{0}torbrowser/" + tbb_version + "/" + tarball_filename
174             )
175             self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
176             self.paths["tarball_filename"] = tarball_filename
177
178             # sig
179             self.paths["sig_url"] = (
180                 "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
181             )
182             self.paths["sig_file"] = (
183                 tbb_cache + "/download/" + tarball_filename + ".asc"
184             )
185             self.paths["sig_filename"] = tarball_filename + ".asc"
186         else:
187             self.paths = {
188                 "dirs": {"config": tbb_config, "cache": tbb_cache, "local": tbb_local,},
189                 "old_data_dir": old_tbb_data,
190                 "tbl_bin": sys.argv[0],
191                 "icon_file": os.path.join(
192                     os.path.dirname(SHARE), "pixmaps/torbrowser.png"
193                 ),
194                 "torproject_pem": os.path.join(SHARE, "torproject.pem"),
195                 "signing_keys": {
196                     "tor_browser_developers": os.path.join(
197                         SHARE, "tor-browser-developers.asc"
198                     )
199                 },
200                 "mirrors_txt": [
201                     os.path.join(SHARE, "mirrors.txt"),
202                     tbb_config + "/mirrors.txt",
203                 ],
204                 "download_dir": tbb_cache + "/download",
205                 "gnupg_homedir": tbb_local + "/gnupg_homedir",
206                 "settings_file": tbb_config + "/settings.json",
207                 "settings_file_pickle": tbb_config + "/settings",
208                 "version_check_url": "https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US",
209                 "version_check_file": tbb_cache + "/download/release.xml",
210                 "tbb": {
211                     "changelog": tbb_local
212                     + "/tbb/"
213                     + self.architecture
214                     + "/tor-browser_"
215                     + self.language
216                     + "/Browser/TorBrowser/Docs/ChangeLog.txt",
217                     "dir": tbb_local + "/tbb/" + self.architecture,
218                     "dir_tbb": tbb_local
219                     + "/tbb/"
220                     + self.architecture
221                     + "/tor-browser_"
222                     + self.language,
223                     "start": tbb_local
224                     + "/tbb/"
225                     + self.architecture
226                     + "/tor-browser_"
227                     + self.language
228                     + "/start-tor-browser.desktop",
229                 },
230             }
231
232         # Add the expected fingerprint for imported keys:
233         self.fingerprints = {
234             "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
235         }
236
237     # create a directory
238     @staticmethod
239     def mkdir(path):
240         try:
241             if not os.path.exists(path):
242                 os.makedirs(path, 0o700)
243                 return True
244         except:
245             print(_("Cannot create directory {0}").format(path))
246             return False
247         if not os.access(path, os.W_OK):
248             print(_("{0} is not writable").format(path))
249             return False
250         return True
251
252     # if gnupg_homedir isn't set up, set it up
253     def init_gnupg(self):
254         if not os.path.exists(self.paths["gnupg_homedir"]):
255             print(_("Creating GnuPG homedir"), self.paths["gnupg_homedir"])
256             self.mkdir(self.paths["gnupg_homedir"])
257         self.import_keys()
258
259     def refresh_keyring(self, fingerprint=None):
260         if fingerprint is not None:
261             print("Refreshing local keyring... Missing key: " + fingerprint)
262         else:
263             print("Refreshing local keyring...")
264
265         # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
266         p = subprocess.Popen(
267             [
268                 "gpg2",
269                 "--status-fd",
270                 "2",
271                 "--homedir",
272                 self.paths["gnupg_homedir"],
273                 "--auto-key-locate",
274                 "nodefault,wkd",
275                 "--locate-keys",
276                 "torbrowser@torproject.org",
277             ],
278             stderr=subprocess.PIPE,
279         )
280         p.wait()
281
282         for output in p.stderr.readlines():
283             match = gnupg_import_ok_pattern.match(output)
284             if match and match.group(2) == "IMPORT_OK":
285                 fingerprint = str(match.group(4))
286                 if match.group(3) == "0":
287                     print("Keyring refreshed successfully...")
288                     print("  No key updates for key: " + fingerprint)
289                 elif match.group(3) == "4":
290                     print("Keyring refreshed successfully...")
291                     print("  New signatures for key: " + fingerprint)
292                 else:
293                     print("Keyring refreshed successfully...")
294
295     def import_key_and_check_status(self, key):
296         """Import a GnuPG key and check that the operation was successful.
297         :param str key: A string specifying the key's filepath from
298             ``Common.paths``
299         :rtype: bool
300         :returns: ``True`` if the key is now within the keyring (or was
301             previously and hasn't changed). ``False`` otherwise.
302         """
303         with gpg.Context() as c:
304             c.set_engine_info(
305                 gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
306             )
307
308             impkey = self.paths["signing_keys"][key]
309             try:
310                 c.op_import(gpg.Data(file=impkey))
311             except:
312                 return False
313             else:
314                 result = c.op_import_result()
315                 if result and self.fingerprints[key] in result.imports[0].fpr:
316                     return True
317                 else:
318                     return False
319
320     # import gpg keys
321     def import_keys(self):
322         """Import all GnuPG keys.
323         :rtype: bool
324         :returns: ``True`` if all keys were successfully imported; ``False``
325             otherwise.
326         """
327         keys = [
328             "tor_browser_developers",
329         ]
330         all_imports_succeeded = True
331
332         for key in keys:
333             imported = self.import_key_and_check_status(key)
334             if not imported:
335                 print(
336                     _(
337                         "Could not import key with fingerprint: %s."
338                         % self.fingerprints[key]
339                     )
340                 )
341                 all_imports_succeeded = False
342
343         if not all_imports_succeeded:
344             print(_("Not all keys were imported successfully!"))
345
346         return all_imports_succeeded
347
348     # load mirrors
349     def load_mirrors(self):
350         self.mirrors = []
351         for srcfile in self.paths["mirrors_txt"]:
352             if not os.path.exists(srcfile):
353                 continue
354             for mirror in open(srcfile, "r").readlines():
355                 if mirror.strip() not in self.mirrors:
356                     self.mirrors.append(mirror.strip())
357
358     # load settings
359     def load_settings(self):
360         default_settings = {
361             "tbl_version": self.tbl_version,
362             "installed": False,
363             "download_over_tor": False,
364             "tor_socks_address": "127.0.0.1:9050",
365             "mirror": self.default_mirror,
366             "force_en-US": False,
367         }
368
369         if os.path.isfile(self.paths["settings_file"]):
370             settings = json.load(open(self.paths["settings_file"]))
371             resave = False
372
373             # detect installed
374             settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
375
376             # make sure settings file is up-to-date
377             for setting in default_settings:
378                 if setting not in settings:
379                     settings[setting] = default_settings[setting]
380                     resave = True
381
382             # make sure tor_socks_address doesn't start with 'tcp:'
383             if settings["tor_socks_address"].startswith("tcp:"):
384                 settings["tor_socks_address"] = settings["tor_socks_address"][4:]
385                 resave = True
386
387             # make sure the version is current
388             if settings["tbl_version"] != self.tbl_version:
389                 settings["tbl_version"] = self.tbl_version
390                 resave = True
391
392             self.settings = settings
393             if resave:
394                 self.save_settings()
395
396         # if settings file is still using old pickle format, convert to json
397         elif os.path.isfile(self.paths["settings_file_pickle"]):
398             self.settings = pickle.load(open(self.paths["settings_file_pickle"]))
399             self.save_settings()
400             os.remove(self.paths["settings_file_pickle"])
401             self.load_settings()
402
403         else:
404             self.settings = default_settings
405             self.save_settings()
406
407     # save settings
408     def save_settings(self):
409         json.dump(self.settings, open(self.paths["settings_file"], "w"))
410         return True