]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser_launcher/common.py
Use print_function from __future__
[torbrowser-launcher.git] / torbrowser_launcher / common.py
1 """
2 Tor Browser Launcher
3 https://github.com/micahflee/torbrowser-launcher/
4
5 Copyright (c) 2013-2014 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 from __future__ import print_function
30
31 import os, sys, platform, subprocess, locale, pickle, json, psutil, re
32
33 import pygtk
34 pygtk.require('2.0')
35 import gtk
36
37 SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
38
39 import gettext
40 gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
41
42 from twisted.internet import gtk2reactor
43 gtk2reactor.install()
44
45
46 # We're looking for output which:
47 #
48 #   1. The first portion must be `[GNUPG:] IMPORT_OK`
49 #   2. The second must be an integer between [0, 15], inclusive
50 #   3. The third must be an uppercased hex-encoded 160-bit fingerprint
51 gnupg_import_ok_pattern = re.compile(
52     "(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})")
53
54
55 class Common:
56
57     def __init__(self, tbl_version):
58         self.tbl_version = tbl_version
59
60         # initialize the app
61         self.default_mirror = 'https://www.torproject.org/dist/'
62         self.discover_arch_lang()
63         self.build_paths()
64         for d in self.paths['dirs']:
65             self.mkdir(self.paths['dirs'][d])
66         self.load_mirrors()
67         self.load_settings()
68         self.mkdir(self.paths['download_dir'])
69         self.mkdir(self.paths['tbb']['dir'])
70         self.init_gnupg()
71
72         # allow buttons to have icons
73         try:
74             gtk_settings = gtk.settings_get_default()
75             gtk_settings.props.gtk_button_images = True
76         except:
77             pass
78
79     # discover the architecture and language
80     def discover_arch_lang(self):
81         # figure out the architecture
82         self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
83
84         # figure out the language
85         available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
86         default_locale = locale.getlocale(locale.LC_MESSAGES)[0]
87         if default_locale is None:
88             self.language = 'en-US'
89         else:
90             self.language = default_locale.replace('_', '-')
91             if self.language not in available_languages:
92                 self.language = self.language.split('-')[0]
93                 if self.language not in available_languages:
94                     for l in available_languages:
95                         if l[0:2] == self.language:
96                             self.language = l
97             # if language isn't available, default to english
98             if self.language not in available_languages:
99                 self.language = 'en-US'
100
101     # build all relevant paths
102     def build_paths(self, tbb_version=None):
103         homedir = os.getenv('HOME')
104         if not homedir:
105             homedir = '/tmp/.torbrowser-'+os.getenv('USER')
106             if not os.path.exists(homedir):
107                 try:
108                     os.mkdir(homedir, 0700)
109                 except:
110                     self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
111         if not os.access(homedir, os.W_OK):
112             self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
113
114         tbb_config = '{0}/.config/torbrowser'.format(homedir)
115         tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
116         tbb_local = '{0}/.local/share/torbrowser'.format(homedir)
117         old_tbb_data = '{0}/.torbrowser'.format(homedir)
118
119         if tbb_version:
120             # tarball filename
121             if self.architecture == 'x86_64':
122                 arch = 'linux64'
123             else:
124                 arch = 'linux32'
125
126             if hasattr(self, 'settings') and self.settings['force_en-US']:
127                 language = 'en-US'
128             else:
129                 language = self.language
130             tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+language+'.tar.xz'
131
132             # tarball
133             self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
134             self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename
135             self.paths['tarball_filename'] = tarball_filename
136
137             # sig
138             self.paths['sig_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename+'.asc'
139             self.paths['sig_file'] = tbb_cache+'/download/'+tarball_filename+'.asc'
140             self.paths['sig_filename'] = tarball_filename+'.asc'
141         else:
142             self.paths = {
143                 'dirs': {
144                     'config': tbb_config,
145                     'cache': tbb_cache,
146                     'local': tbb_local,
147                 },
148                 'old_data_dir': old_tbb_data,
149                 'tbl_bin': sys.argv[0],
150                 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'),
151                 'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
152                 'signing_keys': {
153                     'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
154                 },
155                 'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
156                                 tbb_config+'/mirrors.txt'],
157                 'modem_sound': os.path.join(SHARE, 'modem.ogg'),
158                 'download_dir': tbb_cache+'/download',
159                 'gnupg_homedir': tbb_local+'/gnupg_homedir',
160                 'settings_file': tbb_config+'/settings.json',
161                 'settings_file_pickle': tbb_config+'/settings',
162                 'version_check_url': 'https://dist.torproject.org/torbrowser/update_2/release/Linux_x86_64-gcc3/x/en-US',
163                 'version_check_file': tbb_cache+'/download/release.xml',
164                 'tbb': {
165                     'dir': tbb_local+'/tbb/'+self.architecture,
166                     'dir_tbb': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language,
167                     'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser.desktop',
168                     'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Browser/TorBrowser/Docs/sources/versions',
169                 },
170             }
171
172         # Add the expected fingerprint for imported keys:
173         self.fingerprints = {
174             'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
175         }
176
177     # create a directory
178     @staticmethod
179     def mkdir(path):
180         try:
181             if not os.path.exists(path):
182                 os.makedirs(path, 0700)
183                 return True
184         except:
185             print(_("Cannot create directory {0}").format(path))
186             return False
187         if not os.access(path, os.W_OK):
188             print(_("{0} is not writable").format(path))
189             return False
190         return True
191
192     # if gnupg_homedir isn't set up, set it up
193     def init_gnupg(self):
194         if not os.path.exists(self.paths['gnupg_homedir']):
195             print(_('Creating GnuPG homedir'), self.paths['gnupg_homedir'])
196             self.mkdir(self.paths['gnupg_homedir'])
197         self.import_keys()
198
199     def import_key_and_check_status(self, key):
200         """Import a GnuPG key and check that the operation was successful.
201
202         :param str key: A string specifying the key's filepath from
203             ``Common.paths``, as well as its fingerprint in
204             ``Common.fingerprints``.
205         :rtype: bool
206         :returns: ``True`` if the key is now within the keyring (or was
207             previously and hasn't changed). ``False`` otherwise.
208         """
209         success = False
210
211         p = subprocess.Popen(['/usr/bin/gpg', '--status-fd', '2',
212                               '--homedir', self.paths['gnupg_homedir'],
213                               '--import', self.paths['signing_keys'][key]],
214                              stderr=subprocess.PIPE)
215         p.wait()
216
217         for output in p.stderr.readlines():
218             match = gnupg_import_ok_pattern.match(output)
219             if match:
220                 # The output must match everything in the
221                 # ``gnupg_import_ok_pattern``, as well as the expected fingerprint:
222                 if match.group().find(self.fingerprints[key]) >= 0:
223                     success = True
224                     break
225
226         return success
227
228     # import gpg keys
229     def import_keys(self):
230         """Import all GnuPG keys.
231
232         :rtype: bool
233         :returns: ``True`` if all keys were successfully imported; ``False``
234             otherwise.
235         """
236         keys = ['tor_browser_developers',]
237         all_imports_succeeded = True
238
239         for key in keys:
240             imported = self.import_key_and_check_status(key)
241             if not imported:
242                 print(_('Could not import key with fingerprint: %s.'
243                         % self.fingerprints[key]))
244                 all_imports_succeeded = False
245
246         if not all_imports_succeeded:
247             print(_('Not all keys were imported successfully!'))
248
249         return all_imports_succeeded
250
251     # load mirrors
252     def load_mirrors(self):
253         self.mirrors = []
254         for srcfile in self.paths['mirrors_txt']:
255             if not os.path.exists(srcfile):
256                 continue
257             for mirror in open(srcfile, 'r').readlines():
258                 if mirror.strip() not in self.mirrors:
259                     self.mirrors.append(mirror.strip())
260
261     # load settings
262     def load_settings(self):
263         default_settings = {
264             'tbl_version': self.tbl_version,
265             'installed': False,
266             'download_over_tor': False,
267             'modem_sound': False,
268             'tor_socks_address': 'tcp:127.0.0.1:9050',
269             'mirror': self.default_mirror,
270             'force_en-US': False,
271         }
272
273         if os.path.isfile(self.paths['settings_file']):
274             settings = json.load(open(self.paths['settings_file']))
275             resave = False
276
277             # detect installed
278             settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
279
280             # make sure settings file is up-to-date
281             for setting in default_settings:
282                 if setting not in settings:
283                     settings[setting] = default_settings[setting]
284                     resave = True
285
286             # make sure the version is current
287             if settings['tbl_version'] != self.tbl_version:
288                 settings['tbl_version'] = self.tbl_version
289                 resave = True
290
291             self.settings = settings
292             if resave:
293                 self.save_settings()
294
295         # if settings file is still using old pickle format, convert to json
296         elif os.path.isfile(self.paths['settings_file_pickle']):
297             self.settings = pickle.load(open(self.paths['settings_file_pickle']))
298             self.save_settings()
299             os.remove(self.paths['settings_file_pickle'])
300             self.load_settings()
301
302         else:
303             self.settings = default_settings
304             self.save_settings()
305
306     # save settings
307     def save_settings(self):
308         json.dump(self.settings, open(self.paths['settings_file'], 'w'))
309         return True