]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser_launcher/common.py
Merge branch 'fix/137' of https://github.com/isislovecruft/torbrowser-launcher into...
[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 import os, sys, platform, subprocess, locale, pickle, json, psutil, re
30
31 import pygtk
32 pygtk.require('2.0')
33 import gtk
34
35 SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
36
37 import gettext
38 gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
39
40 from twisted.internet import gtk2reactor
41 gtk2reactor.install()
42
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     "(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})")
51
52
53 class Common:
54
55     def __init__(self, tbl_version):
56         self.tbl_version = tbl_version
57
58         # initialize the app
59         self.default_mirror = 'https://www.torproject.org/dist/'
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         # allow buttons to have icons
71         try:
72             gtk_settings = gtk.settings_get_default()
73             gtk_settings.props.gtk_button_images = True
74         except:
75             pass
76
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'
81
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'
87         else:
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:
94                             self.language = l
95             # if language isn't available, default to english
96             if self.language not in available_languages:
97                 self.language = 'en-US'
98
99     # build all relevant paths
100     def build_paths(self, tbb_version=None):
101         homedir = os.getenv('HOME')
102         if not homedir:
103             homedir = '/tmp/.torbrowser-'+os.getenv('USER')
104             if not os.path.exists(homedir):
105                 try:
106                     os.mkdir(homedir, 0700)
107                 except:
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)
111
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)
116
117         if tbb_version:
118             # tarball filename
119             if self.architecture == 'x86_64':
120                 arch = 'linux64'
121             else:
122                 arch = 'linux32'
123
124             if hasattr(self, 'settings') and self.settings['force_en-US']:
125                 language = 'en-US'
126             else:
127                 language = self.language
128             tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+language+'.tar.xz'
129
130             # tarball
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
134
135             # sig
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'
139         else:
140             self.paths = {
141                 'dirs': {
142                     'config': tbb_config,
143                     'cache': tbb_cache,
144                     'local': tbb_local,
145                 },
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'),
150                 'signing_keys': {
151                     'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
152                 },
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',
162                 'tbb': {
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',
167                 },
168             }
169
170         # Add the expected fingerprint for imported keys:
171         self.fingerprints = {
172             'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
173         }
174
175     # create a directory
176     @staticmethod
177     def mkdir(path):
178         try:
179             if not os.path.exists(path):
180                 os.makedirs(path, 0700)
181                 return True
182         except:
183             print _("Cannot create directory {0}").format(path)
184             return False
185         if not os.access(path, os.W_OK):
186             print _("{0} is not writable").format(path)
187             return False
188         return True
189
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'])
195         self.import_keys()
196
197     def import_key_and_check_status(self, key):
198         """Import a GnuPG key and check that the operation was successful.
199
200         :param str key: A string specifying the key's filepath from
201             ``Common.paths``, as well as its fingerprint in
202             ``Common.fingerprints``.
203         :rtype: bool
204         :returns: ``True`` if the key is now within the keyring (or was
205             previously and hasn't changed). ``False`` otherwise.
206         """
207         success = False
208
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)
213         p.wait()
214
215         output = p.stderr.read()
216         print "---output begin---\n{}\n---output end---".format(output)
217         match = gnupg_import_ok_pattern.match(output)
218         if match:
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:
222                 success = True
223
224         return success
225
226     # import gpg keys
227     def import_keys(self):
228         """Import all GnuPG keys.
229
230         :rtype: bool
231         :returns: ``True`` if all keys were successfully imported; ``False``
232             otherwise.
233         """
234
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'])
239
240         keys = ['tor_browser_developers',]
241         all_imports_succeeded = True
242
243         print _('Importing keys')
244         for key in keys:
245             imported = self.import_key_and_check_status(key)
246             if not imported:
247                 print _('Could not import key with fingerprint: %s.'
248                         % self.fingerprints[key])
249                 all_imports_succeeded = False
250
251         if all_imports_succeeded:
252             print _('Successfully imported all keys.')
253         else:
254             print _('Not all keys were imported successfully!')
255
256         return all_imports_succeeded
257
258     # load mirrors
259     def load_mirrors(self):
260         self.mirrors = []
261         for srcfile in self.paths['mirrors_txt']:
262             if not os.path.exists(srcfile):
263                 continue
264             for mirror in open(srcfile, 'r').readlines():
265                 if mirror.strip() not in self.mirrors:
266                     self.mirrors.append(mirror.strip())
267
268     # load settings
269     def load_settings(self):
270         default_settings = {
271             'tbl_version': self.tbl_version,
272             'installed': False,
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,
278         }
279
280         if os.path.isfile(self.paths['settings_file']):
281             settings = json.load(open(self.paths['settings_file']))
282             resave = False
283
284             # detect installed
285             settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
286
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]
291                     resave = True
292
293             # make sure the version is current
294             if settings['tbl_version'] != self.tbl_version:
295                 settings['tbl_version'] = self.tbl_version
296                 resave = True
297
298             self.settings = settings
299             if resave:
300                 self.save_settings()
301
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']))
305             self.save_settings()
306             os.remove(self.paths['settings_file_pickle'])
307             self.load_settings()
308
309         else:
310             self.settings = default_settings
311             self.save_settings()
312
313     # save settings
314     def save_settings(self):
315         json.dump(self.settings, open(self.paths['settings_file'], 'w'))
316         return True