]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser-launcher
started interface for settings dialog (#29)
[torbrowser-launcher.git] / torbrowser-launcher
1 #!/usr/bin/env python
2 """
3 Tor Browser Launcher
4 https://github.com/micahflee/torbrowser-launcher/
5
6 Copyright (c) 2013 Micah Lee <micahflee@riseup.net>
7
8 Permission is hereby granted, free of charge, to any person
9 obtaining a copy of this software and associated documentation
10 files (the "Software"), to deal in the Software without
11 restriction, including without limitation the rights to use,
12 copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the
14 Software is furnished to do so, subject to the following
15 conditions:
16
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 OTHER DEALINGS IN THE SOFTWARE.
28 """
29
30 import gettext
31 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
32
33 from twisted.internet import gtk2reactor
34 gtk2reactor.install()
35 from twisted.internet import reactor
36
37 import pygtk
38 pygtk.require('2.0')
39 import gtk
40
41 import os, sys, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil
42
43 from twisted.web.client import Agent, ResponseDone
44 from twisted.web.http_headers import Headers
45 from twisted.internet.protocol import Protocol
46 from twisted.internet.ssl import ClientContextFactory
47
48 from OpenSSL.SSL import Context, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT
49 from OpenSSL.crypto import load_certificate, FILETYPE_PEM
50
51 class VerifyTorProjectCert(ClientContextFactory):
52
53     def __init__(self, torproject_pem):
54         self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
55
56     def getContext(self, host, port):
57         ctx = ClientContextFactory.getContext(self)
58         ctx.set_verify_depth(0)
59         ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
60         return ctx
61
62     def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
63         return cert.digest('sha256') == self.torproject_ca.digest('sha256')
64
65 class TBLCommon:
66
67     def __init__(self):
68         print _('Initializing Tor Browser Launcher')
69         
70         # initialize the app
71         self.discover_arch_lang()
72         self.build_paths()
73         self.mkdir(self.paths['dir']['download'])
74         self.mkdir(self.paths['dir']['tbb'])
75         self.init_gnupg()
76
77         # allow buttons to have icons
78         try:
79             settings = gtk.settings_get_default()
80             settings.props.gtk_button_images = True
81         except:
82             pass
83
84     # discover the architecture and language
85     def discover_arch_lang(self):
86         # figure out the architecture
87         (sysname, nodename, release, version, machine) = os.uname()
88         self.architecture = machine
89
90         # figure out the language
91         available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
92         default_locale = locale.getdefaultlocale()[0]
93         if default_locale == None:
94             self.language = 'en-US'
95         else:
96             self.language = default_locale.replace('_', '-')
97             if self.language not in available_languages:
98                 self.language = self.language.split('-')[0]
99                 if self.language not in available_languages:
100                     for l in available_languages:
101                         if l[0:2] == self.language:
102                             self.language = l
103             # if language isn't available, default to english
104             if self.language not in available_languages:
105                 self.language = 'en-US'
106
107     # build all relevant paths
108     def build_paths(self, tbb_version = None):
109         homedir = os.getenv('HOME')
110         if not homedir:
111             homedir = '/tmp/.torbrowser-'+os.getenv('USER')
112             if os.path.exists(homedir) == False:
113                 try:
114                     os.mkdir(homedir, 0700)
115                 except:
116                     self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
117         if not os.access(homedir, os.W_OK):
118             self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
119
120         tbb_data = '%s/.torbrowser' % homedir
121
122         if tbb_version:
123             tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
124             self.paths['file']['tarball'] = tbb_data+'/download/'+tarball_filename
125             self.paths['file']['tarball_sig'] = tbb_data+'/download/'+tarball_filename+'.asc'
126             self.paths['url']['tarball'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
127             self.paths['url']['tarball_sig'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
128             self.paths['filename']['tarball'] = tarball_filename
129             self.paths['filename']['tarball_sig'] = tarball_filename+'.asc'
130
131         else:
132             self.paths = {
133                 'dir': {
134                     'data': tbb_data,
135                     'download': tbb_data+'/download',
136                     'tbb': tbb_data+'/tbb/'+self.architecture,
137                     'gnupg_homedir': tbb_data+'/gnupg_homedir'
138                 },
139                 'file': {
140                     'tbl_bin': '/usr/bin/torbrowser-launcher',
141                     'settings': tbb_data+'/settings',
142                     'version': tbb_data+'/version',
143                     'start': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
144                     'vidalia_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
145                     'firefox_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
146                     'firefox_profile': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
147                     'update_check': tbb_data+'/download/RecommendedTBBVersions',
148                     'icon': '/usr/share/pixmaps/torbrowser80.xpm',
149                     'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
150                     'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
151                     'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc'
152                 },
153                 'url': {
154                     'update_check': 'https://check.torproject.org/RecommendedTBBVersions'
155                 },
156                 'filename': {}
157             }
158
159     # create a directory
160     def mkdir(self, path):
161         try:
162             if os.path.exists(path) == False:
163                 os.makedirs(path, 0700)
164                 return True
165         except:
166             self.set_gui('error', _("Cannot create directory {0}").format(path), [], False)
167             return False
168         if not os.access(path, os.W_OK):
169             self.set_gui('error', _("{0} is not writable").format(path), [], False)
170             return False
171         return True
172
173     # if gnupg_homedir isn't set up, set it up
174     def init_gnupg(self):
175         if not os.path.exists(self.paths['dir']['gnupg_homedir']):
176             print _('Creating GnuPG homedir'), self.paths['dir']['gnupg_homedir']
177             if self.mkdir(self.paths['dir']['gnupg_homedir']):
178                 # import keys
179                 print _('Importing keys')
180                 p1 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['erinn_key']])
181                 p1.wait()
182                 p2 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['sebastian_key']])
183                 p2.wait()
184
185     # load settings
186     def load_settings(self):
187         if os.path.isfile(self.paths['file']['settings']):
188             self.settings = pickle.load(open(self.paths['file']['settings']))
189             # sanity checks
190             if not 'installed_version' in self.settings:
191                 return False
192             if not 'latest_version' in self.settings:
193                 return False
194             if not 'last_update_check_timestamp' in self.settings:
195                 return False
196         else:
197             self.settings = {
198                 'installed_version': False,
199                 'latest_version': '0',
200                 'last_update_check_timestamp': 0
201             }
202             self.save_settings()
203         return True
204
205     # save settings
206     def save_settings(self):
207         pickle.dump(self.settings, open(self.paths['file']['settings'], 'w'))
208         return True
209
210     # get the process id of a program
211     def get_pid(self, bin_path, python = False):
212         pid = None
213
214         for p in psutil.process_iter():
215             try:
216                 if p.pid != os.getpid():
217                     exe = None
218                     if python:
219                         if len(p.cmdline) > 1:
220                             if 'python' in p.cmdline[0]:
221                                 exe = p.cmdline[1]
222                     else:
223                         if len(p.cmdline) > 0:
224                             exe = p.cmdline[0]
225                     
226                     if exe == bin_path:
227                         pid = p.pid
228
229             except:
230                 pass
231
232         return pid
233
234     # bring program's x window to front
235     def bring_window_to_front(self, pid):
236         # figure out the window id
237         win_id = None
238         p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
239         for line in p.stdout.readlines():
240             line_split = line.split()
241             cur_win_id = line_split[0]
242             cur_win_pid = int(line_split[2])
243             if cur_win_pid == pid:
244                 win_id = cur_win_id
245
246         # bring to front
247         if win_id:
248             subprocess.call(['wmctrl', '-i', '-a', win_id])
249
250 class TBLSettings:
251     def __init__(self, common):
252         print _('Starting settings dialog')
253         self.common = common
254         self.common.load_settings()
255
256         # set up the window
257         self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
258         self.window.set_title(_("Tor Browser Launcher Settings"))
259         self.window.set_icon_from_file(self.common.paths['file']['icon'])
260         self.window.set_position(gtk.WIN_POS_CENTER)
261         self.window.set_border_width(10)
262         self.window.connect("delete_event", self.delete_event)
263         self.window.connect("destroy", self.destroy)
264         
265         # build the rest of the UI
266         self.box = gtk.VBox(False, 20)
267         self.window.add(self.box)
268
269         # labels
270         if(self.common.settings['installed_version']):
271             self.label1 = gtk.Label(_('Installed version: {0}').format(self.common.settings['installed_version']))
272         else:
273             self.label1 = gtk.Label(_('Tor Browser Bundle not installed'))
274         self.label1.set_line_wrap(True)
275         self.box.pack_start(self.label1, True, True, 0)
276         self.label1.show()
277
278         if(self.common.settings['last_update_check_timestamp'] > 0):
279             self.label1 = gtk.Label(_('Last checked for updates: {0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
280         else:
281             self.label1 = gtk.Label(_('Never checked for updates'))
282         self.label1.set_line_wrap(True)
283         self.box.pack_start(self.label1, True, True, 0)
284         self.label1.show()
285
286         # button box
287         self.button_box = gtk.HButtonBox()
288         self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
289         self.box.pack_start(self.button_box, True, True, 0)
290         self.button_box.show()
291         
292
293         # save and launch button
294         save_launch_image = gtk.Image()
295         save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
296         self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
297         self.save_launch_button.set_image(save_launch_image)
298         self.save_launch_button.connect("clicked", self.save_launch, None)
299         self.button_box.add(self.save_launch_button)
300         self.save_launch_button.show()
301
302         # save and exit button
303         save_exit_image = gtk.Image()
304         save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
305         self.save_exit_button = gtk.Button(_("Save & Exit"))
306         self.save_exit_button.set_image(save_exit_image)
307         self.save_exit_button.connect("clicked", self.save_exit, None)
308         self.button_box.add(self.save_exit_button)
309         self.save_exit_button.show()
310
311         # cancel button
312         cancel_image = gtk.Image()
313         cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
314         self.cancel_button = gtk.Button(_("Cancel"))
315         self.cancel_button.set_image(cancel_image)
316         self.cancel_button.connect("clicked", self.destroy, None)
317         self.button_box.add(self.cancel_button)
318         self.cancel_button.show()
319
320         # show the window
321         self.box.show()
322         self.window.show()
323
324         # start gtk
325         gtk.main()
326
327     # save and launch
328     def save_launch(self, widget, data=None):
329         self.save()
330         p = subprocess.Popen([self.common.paths['file']['tbl_bin']])
331         self.destroy(False)
332
333     # save and exit
334     def save_exit(self, widget, data=None):
335         self.save()
336         self.destroy(False)
337
338     # save settings
339     def save(self):
340         pass
341
342     # exit
343     def delete_event(self, widget, event, data=None):
344         return False
345     def destroy(self, widget, data=None):
346         gtk.main_quit()
347
348
349 class TBLLauncher:
350     def __init__(self, common):
351         print _('Starting launcher dialog')
352         self.common = common
353
354         self.set_gui(None, '', [])
355         self.launch_gui = True
356
357         # if we haven't already hit an error
358         if self.gui != 'error':
359             # load settings
360             if self.common.load_settings():
361                 self.common.build_paths(self.common.settings['latest_version'])
362
363                 # is vidalia already running and we just need to open a new firefox?
364                 if self.common.settings['installed_version']:
365                     vidalia_pid = self.common.get_pid('./App/vidalia')
366                     firefox_pid = self.common.get_pid(self.common.paths['file']['firefox_bin'])
367
368                     if vidalia_pid and not firefox_pid:
369                         print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
370                         self.common.bring_window_to_front(vidalia_pid)
371                         subprocess.Popen([self.common.paths['file']['firefox_bin'], '-no-remote', '-profile', self.common.paths['file']['firefox_profile']])
372                         return
373                     elif vidalia_pid and firefox_pid:
374                         print _('Vidalia and Firefox are already open, bringing them to focus')
375
376                         # bring firefox to front, then vidalia
377                         self.common.bring_window_to_front(firefox_pid)
378                         self.common.bring_window_to_front(vidalia_pid)
379                         return
380
381                 # how long was it since the last update check?
382                 # 86400 seconds = 24 hours
383                 current_timestamp = int(time.time())
384                 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
385                     # check for update
386                     print 'Checking for update'
387                     self.set_gui('task', _("Checking for Tor Browser update."), 
388                         ['download_update_check', 
389                          'attempt_update'])
390
391                 else:
392                     # no need to check for update
393                     print _('Checked for update within 24 hours, skipping')
394                     self.start_launcher()
395
396             else:
397                 self.set_gui('error', _("Error loading settings. Delete ~/.torbrowser and try again."), [])
398
399         if self.launch_gui:
400             # set up the window
401             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
402             self.window.set_title(_("Tor Browser"))
403             self.window.set_icon_from_file(self.common.paths['file']['icon'])
404             self.window.set_position(gtk.WIN_POS_CENTER)
405             self.window.set_border_width(10)
406             self.window.connect("delete_event", self.delete_event)
407             self.window.connect("destroy", self.destroy)
408
409             # build the rest of the UI
410             self.build_ui()
411
412     # download or run TBB
413     def start_launcher(self):
414       # is TBB already installed?
415       if os.path.isfile(self.common.paths['file']['start']) and os.access(self.common.paths['file']['start'], os.X_OK):
416         if self.common.settings['installed_version'] == self.common.settings['latest_version']:
417           # current version of tbb is installed, launch it
418           self.run(False)
419           self.launch_gui = False
420         elif self.common.settings['installed_version'] < self.common.settings['latest_version']:
421           # there is a tbb upgrade available
422           self.set_gui('task', _("Your Tor Browser is out of date."), 
423             ['download_tarball', 
424              'download_tarball_sig', 
425              'verify', 
426              'extract', 
427              'run'])
428         else:
429           # for some reason the installed tbb is newer than the current version?
430           self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
431
432       # not installed
433       else:
434           # are the tarball and sig already downloaded?
435           if os.path.isfile(self.common.paths['file']['tarball']) and os.path.isfile(self.common.paths['file']['tarball_sig']):
436               # start the gui with verify
437               self.set_gui('task', _("Installing Tor Browser."), 
438                   ['verify', 
439                    'extract', 
440                    'run'])
441
442           # first run
443           else:
444               self.set_gui('task', _("Downloading and installing Tor Browser."), 
445                   ['download_tarball', 
446                    'download_tarball_sig', 
447                    'verify', 
448                    'extract', 
449                    'run'])
450    
451     # there are different GUIs that might appear, this sets which one we want
452     def set_gui(self, gui, message, tasks, autostart=True):
453         self.gui = gui
454         self.gui_message = message
455         self.gui_tasks = tasks
456         self.gui_task_i = 0
457         self.gui_autostart = autostart
458
459     # set all gtk variables to False
460     def clear_ui(self):
461         if hasattr(self, 'box'):
462             self.box.destroy()
463         self.box = False
464
465         self.label = False
466         self.progressbar = False
467         self.button_box = False
468         self.start_button = False
469         self.exit_button = False
470
471     # build the application's UI
472     def build_ui(self):
473         self.box = gtk.VBox(False, 20)
474         self.window.add(self.box)
475
476         if self.gui == 'error':
477             # labels
478             self.label = gtk.Label( self.gui_message ) 
479             self.label.set_line_wrap(True)
480             self.box.pack_start(self.label, True, True, 0)
481             self.label.show()
482
483             # exit button
484             exit_image = gtk.Image()
485             exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
486             self.exit_button = gtk.Button("Exit")
487             self.exit_button.set_image(exit_image)
488             self.exit_button.connect("clicked", self.destroy, None)
489             self.box.add(self.exit_button)
490             self.exit_button.show()
491
492         elif self.gui == 'task':
493             # label
494             self.label = gtk.Label( self.gui_message ) 
495             self.label.set_line_wrap(True)
496             self.box.pack_start(self.label, True, True, 0)
497             self.label.show()
498             
499             # progress bar
500             self.progressbar = gtk.ProgressBar(adjustment=None)
501             self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
502             self.progressbar.set_pulse_step(0.01)
503             self.box.pack_start(self.progressbar, True, True, 0)
504
505             # button box
506             self.button_box = gtk.HButtonBox()
507             self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
508             self.box.pack_start(self.button_box, True, True, 0)
509             self.button_box.show()
510
511             # start button
512             start_image = gtk.Image()
513             start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
514             self.start_button = gtk.Button(_("Start"))
515             self.start_button.set_image(start_image)
516             self.start_button.connect("clicked", self.start, None)
517             self.button_box.add(self.start_button)
518             if not self.gui_autostart:
519               self.start_button.show()
520
521             # exit button
522             exit_image = gtk.Image()
523             exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
524             self.exit_button = gtk.Button(_("Exit"))
525             self.exit_button.set_image(exit_image)
526             self.exit_button.connect("clicked", self.destroy, None)
527             self.button_box.add(self.exit_button)
528             self.exit_button.show()
529
530         self.box.show()
531         self.window.show()
532
533         if self.gui_autostart:
534             self.start(None)
535
536     # start button clicked, begin tasks
537     def start(self, widget, data=None):
538         # disable the start button
539         if self.start_button:
540             self.start_button.set_sensitive(False)
541
542         # start running tasks
543         self.run_task()
544       
545     # run the next task in the task list
546     def run_task(self):
547         self.refresh_gtk()
548
549         if self.gui_task_i >= len(self.gui_tasks):
550             self.destroy(False)
551             return
552
553         task = self.gui_tasks[self.gui_task_i]
554         
555         # get ready for the next task
556         self.gui_task_i += 1
557
558         if task == 'download_update_check':
559             print _('Downloading'), self.common.paths['url']['update_check']
560             self.download('update check', self.common.paths['url']['update_check'], self.common.paths['file']['update_check'])
561         
562         if task == 'attempt_update':
563             print _('Checking to see if update it needed')
564             self.attempt_update()
565
566         elif task == 'download_tarball':
567             print _('Downloading'), self.common.paths['url']['tarball']
568             self.download('tarball', self.common.paths['url']['tarball'], self.common.paths['file']['tarball'])
569
570         elif task == 'download_tarball_sig':
571             print _('Downloading'), self.common.paths['url']['tarball_sig']
572             self.download('signature', self.common.paths['url']['tarball_sig'], self.common.paths['file']['tarball_sig'])
573
574         elif task == 'verify':
575             print _('Verifying signature')
576             self.verify()
577
578         elif task == 'extract':
579             print _('Extracting'), self.common.paths['filename']['tarball']
580             self.extract()
581
582         elif task == 'run':
583             print _('Running'), self.common.paths['file']['start']
584             self.run()
585         
586         elif task == 'start_over':
587             print _('Starting download over again')
588             self.start_over()
589
590     def response_received(self, response):
591         class FileDownloader(Protocol):
592             def __init__(self, file, total, progress, done_cb):
593                 self.file = file
594                 self.total = total
595                 self.so_far = 0
596                 self.progress = progress
597                 self.all_done = done_cb
598
599             def dataReceived(self, bytes):
600                 self.file.write(bytes)
601                 self.so_far += len(bytes)
602                 percent = float(self.so_far) / float(self.total)
603                 self.progress.set_fraction(percent)
604                 amount = float(self.so_far)
605                 units = "bytes"
606                 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
607                     if amount > size:
608                         units = unit
609                         amount = amount / float(size)
610                         break
611
612                 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
613
614             def connectionLost(self, reason):
615                 print _('Finished receiving body:'), reason.getErrorMessage()
616                 self.all_done(reason)
617
618         dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
619         response.deliverBody(dl)
620
621     def response_finished(self, msg):
622         if msg.check(ResponseDone):
623             self.file_download.close()
624             # next task!
625             self.run_task()
626
627         else:
628             print "FINISHED", msg
629             ## FIXME handle errors
630
631     def download_error(self, f):
632         print _("Download error"), f
633         self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
634         self.clear_ui()
635         self.build_ui()
636
637     def download(self, name, url, path):
638         # initialize the progress bar
639         self.progressbar.set_fraction(0) 
640         self.progressbar.set_text(_('Downloading {0}').format(name))
641         self.progressbar.show()
642         self.refresh_gtk()
643
644         agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['file']['torproject_pem']))
645         d = agent.request('GET', url,
646                           Headers({'User-Agent': ['torbrowser-launcher']}),
647                           None)
648
649         self.file_download = open(path, 'w')
650         d.addCallback(self.response_received).addErrback(self.download_error)
651         
652         if not reactor.running:
653             reactor.run()
654
655     def attempt_update(self):
656         # load the update check file
657         try:
658             versions = json.load(open(self.common.paths['file']['update_check']))
659             latest_version = None
660
661             end = '-Linux'
662             for version in versions:
663                 if str(version).find(end) != -1:
664                     latest_version = str(version)
665
666             if latest_version:
667                 self.common.settings['latest_version'] = latest_version[:-len(end)]
668                 self.common.settings['last_update_check_timestamp'] = int(time.time())
669                 self.common.save_settings()
670                 self.common.build_paths(self.common.settings['latest_version'])
671                 self.start_launcher()
672
673             else:
674                 # failed to find the latest version
675                 self.set_gui('error', _("Error checking for updates."), [], False)
676         
677         except:
678             # not a valid JSON object
679             self.set_gui('error', _("Error checking for updates."), [], False)
680
681         # now start over
682         self.clear_ui()
683         self.build_ui()
684
685     def verify(self):
686         # initialize the progress bar
687         self.progressbar.set_fraction(0) 
688         self.progressbar.set_text(_('Verifying Signature'))
689         self.progressbar.show()
690
691         p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['dir']['gnupg_homedir'], '--verify', self.common.paths['file']['tarball_sig']])
692         self.pulse_until_process_exits(p)
693         
694         if p.returncode == 0:
695             self.run_task()
696         else:
697             self.set_gui('task', _("SIGNATURE VERIFICATION FAILED!\n\nYou might be under attack, or there might just be a networking problem. Click Start try the download again."), ['start_over'], False)
698             self.clear_ui()
699             self.build_ui()
700
701             if not reactor.running:
702                 reactor.run()
703
704     def extract(self):
705         # initialize the progress bar
706         self.progressbar.set_fraction(0) 
707         self.progressbar.set_text(_('Installing'))
708         self.progressbar.show()
709         self.refresh_gtk()
710
711         # make sure this file is a tarfile
712         if tarfile.is_tarfile(self.common.paths['file']['tarball']):
713           tf = tarfile.open(self.common.paths['file']['tarball'])
714           tf.extractall(self.common.paths['dir']['tbb'])
715         else:
716             self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
717             self.clear_ui()
718             self.build_ui()
719
720         # installation is finished, so save installed_version
721         self.common.settings['installed_version'] = self.common.settings['latest_version']
722         self.common.save_settings()
723
724         self.run_task()
725
726     def run(self, run_next_task = True):
727         subprocess.Popen([self.common.paths['file']['start']])
728         if run_next_task:
729             self.run_task()
730
731     # make the progress bar pulse until process p (a Popen object) finishes
732     def pulse_until_process_exits(self, p):
733         while p.poll() == None:
734             time.sleep(0.01)
735             self.progressbar.pulse()
736             self.refresh_gtk()
737
738     # start over and download TBB again
739     def start_over(self):
740         self.label.set_text(_("Downloading Tor Browser Bundle over again."))
741         self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
742         self.gui_task_i = 0
743         self.start(None)
744    
745     # refresh gtk
746     def refresh_gtk(self):
747         while gtk.events_pending():
748             gtk.main_iteration(False)
749
750     # exit
751     def delete_event(self, widget, event, data=None):
752         return False
753     def destroy(self, widget, data=None):
754         if hasattr(self, 'file_download'):
755             self.file_download.close()
756         if reactor.running:
757             reactor.stop()
758
759 if __name__ == "__main__":
760     tor_browser_launcher_version = '0.0.1'
761
762     print _('Tor Browser Launcher')
763     print _('By Micah Lee, licensed under GPLv3')
764     print _('version {0}').format(tor_browser_launcher_version)
765     print 'https://github.com/micahflee/torbrowser-launcher'
766
767     common = TBLCommon()
768
769     # is torbrowser-launcher already running?
770     tbl_pid = common.get_pid(common.paths['file']['tbl_bin'], True)
771     if tbl_pid:
772         print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
773         common.bring_window_to_front(tbl_pid)
774         sys.exit()
775
776     if '-settings' in sys.argv:
777         # settings mode
778         app = TBLSettings(common)
779
780     else:
781         # launcher mode
782         app = TBLLauncher(common)
783