3 https://github.com/micahflee/torbrowser-launcher/
5 Copyright (c) 2013-2014 Micah Lee <micah@micahflee.com>
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
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
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.
29 import os, sys, platform, subprocess, locale, pickle, json, psutil, re
35 SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
38 gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
40 from twisted.internet import gtk2reactor
44 # We're looking for output which:
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 "(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})")
55 def __init__(self, tbl_version):
56 self.tbl_version = tbl_version
59 self.default_mirror = 'https://www.torproject.org/dist/'
60 self.discover_arch_lang()
62 for d in self.paths['dirs']:
63 self.mkdir(self.paths['dirs'][d])
66 self.mkdir(self.paths['download_dir'])
67 self.mkdir(self.paths['tbb']['dir'])
70 # allow buttons to have icons
72 gtk_settings = gtk.settings_get_default()
73 gtk_settings.props.gtk_button_images = True
77 # discover the architecture and language
78 def discover_arch_lang(self):
79 # figure out the architecture
80 self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
82 # figure out the language
83 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
84 default_locale = locale.getlocale(locale.LC_MESSAGES)[0]
85 if default_locale is None:
86 self.language = 'en-US'
88 self.language = default_locale.replace('_', '-')
89 if self.language not in available_languages:
90 self.language = self.language.split('-')[0]
91 if self.language not in available_languages:
92 for l in available_languages:
93 if l[0:2] == self.language:
95 # if language isn't available, default to english
96 if self.language not in available_languages:
97 self.language = 'en-US'
99 # build all relevant paths
100 def build_paths(self, tbb_version=None):
101 homedir = os.getenv('HOME')
103 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
104 if not os.path.exists(homedir):
106 os.mkdir(homedir, 0700)
108 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
109 if not os.access(homedir, os.W_OK):
110 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
112 tbb_config = '{0}/.config/torbrowser'.format(homedir)
113 tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
114 tbb_local = '{0}/.local/share/torbrowser'.format(homedir)
115 old_tbb_data = '{0}/.torbrowser'.format(homedir)
119 if self.architecture == 'x86_64':
124 if hasattr(self, 'settings') and self.settings['force_en-US']:
127 language = self.language
128 tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+language+'.tar.xz'
131 self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
132 self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename
133 self.paths['tarball_filename'] = tarball_filename
136 self.paths['sig_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename+'.asc'
137 self.paths['sig_file'] = tbb_cache+'/download/'+tarball_filename+'.asc'
138 self.paths['sig_filename'] = tarball_filename+'.asc'
142 'config': tbb_config,
146 'old_data_dir': old_tbb_data,
147 'tbl_bin': sys.argv[0],
148 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'),
149 'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
151 'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
153 'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
154 tbb_config+'/mirrors.txt'],
155 'modem_sound': os.path.join(SHARE, 'modem.ogg'),
156 'download_dir': tbb_cache+'/download',
157 'gnupg_homedir': tbb_local+'/gnupg_homedir',
158 'settings_file': tbb_config+'/settings.json',
159 'settings_file_pickle': tbb_config+'/settings',
160 'version_check_url': 'https://dist.torproject.org/torbrowser/update_2/release/Linux_x86_64-gcc3/x/en-US',
161 'version_check_file': tbb_cache+'/download/release.xml',
163 'dir': tbb_local+'/tbb/'+self.architecture,
164 'dir_tbb': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language,
165 'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser.desktop',
166 'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Browser/TorBrowser/Docs/sources/versions',
170 # Add the expected fingerprint for imported keys:
171 self.fingerprints = {
172 'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
179 if not os.path.exists(path):
180 os.makedirs(path, 0700)
183 print _("Cannot create directory {0}").format(path)
185 if not os.access(path, os.W_OK):
186 print _("{0} is not writable").format(path)
190 # if gnupg_homedir isn't set up, set it up
191 def init_gnupg(self):
192 if not os.path.exists(self.paths['gnupg_homedir']):
193 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
194 self.mkdir(self.paths['gnupg_homedir'])
197 def import_key_and_check_status(self, key):
198 """Import a GnuPG key and check that the operation was successful.
200 :param str key: A string specifying the key's filepath from
201 ``Common.paths``, as well as its fingerprint in
202 ``Common.fingerprints``.
204 :returns: ``True`` if the key is now within the keyring (or was
205 previously and hasn't changed). ``False`` otherwise.
209 p = subprocess.Popen(['/usr/bin/gpg', '--status-fd', '2',
210 '--homedir', self.paths['gnupg_homedir'],
211 '--import', self.paths['signing_keys'][key]],
212 stderr=subprocess.PIPE)
215 output = p.stderr.read()
216 print "---output begin---\n{}\n---output end---".format(output)
217 match = gnupg_import_ok_pattern.match(output)
219 # The output must match everything in the
220 # ``gnupg_import_ok_pattern``, as well as the expected fingerprint:
221 if match.group().find(self.fingerprints[key]) >= 0:
227 def import_keys(self):
228 """Import all GnuPG keys.
231 :returns: ``True`` if all keys were successfully imported; ``False``
235 # run "gpg --list-keys" first, to create an empty keyring before importing
236 #subprocess.call(['/usr/bin/gpg',
237 # '--homedir', self.paths['gnupg_homedir'],
238 # '--list-secret-keys'])
240 keys = ['tor_browser_developers',]
241 all_imports_succeeded = True
243 print _('Importing keys')
245 imported = self.import_key_and_check_status(key)
247 print _('Could not import key with fingerprint: %s.'
248 % self.fingerprints[key])
249 all_imports_succeeded = False
251 if all_imports_succeeded:
252 print _('Successfully imported all keys.')
254 print _('Not all keys were imported successfully!')
256 return all_imports_succeeded
259 def load_mirrors(self):
261 for srcfile in self.paths['mirrors_txt']:
262 if not os.path.exists(srcfile):
264 for mirror in open(srcfile, 'r').readlines():
265 if mirror.strip() not in self.mirrors:
266 self.mirrors.append(mirror.strip())
269 def load_settings(self):
271 'tbl_version': self.tbl_version,
273 'download_over_tor': False,
274 'modem_sound': False,
275 'tor_socks_address': 'tcp:127.0.0.1:9050',
276 'mirror': self.default_mirror,
277 'force_en-US': False,
280 if os.path.isfile(self.paths['settings_file']):
281 settings = json.load(open(self.paths['settings_file']))
285 settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
287 # make sure settings file is up-to-date
288 for setting in default_settings:
289 if setting not in settings:
290 settings[setting] = default_settings[setting]
293 # make sure the version is current
294 if settings['tbl_version'] != self.tbl_version:
295 settings['tbl_version'] = self.tbl_version
298 self.settings = settings
302 # if settings file is still using old pickle format, convert to json
303 elif os.path.isfile(self.paths['settings_file_pickle']):
304 self.settings = pickle.load(open(self.paths['settings_file_pickle']))
306 os.remove(self.paths['settings_file_pickle'])
310 self.settings = default_settings
314 def save_settings(self):
315 json.dump(self.settings, open(self.paths['settings_file'], 'w'))