]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser-launcher
Merge pull request #37 from adrelanos/alexandre
[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                     'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc'
153                 },
154                 'url': {
155                     'update_check': 'https://check.torproject.org/RecommendedTBBVersions'
156                 },
157                 'filename': {}
158             }
159
160     # create a directory
161     def mkdir(self, path):
162         try:
163             if os.path.exists(path) == False:
164                 os.makedirs(path, 0700)
165                 return True
166         except:
167             self.set_gui('error', _("Cannot create directory {0}").format(path), [], False)
168             return False
169         if not os.access(path, os.W_OK):
170             self.set_gui('error', _("{0} is not writable").format(path), [], False)
171             return False
172         return True
173
174     # if gnupg_homedir isn't set up, set it up
175     def init_gnupg(self):
176         if not os.path.exists(self.paths['dir']['gnupg_homedir']):
177             print _('Creating GnuPG homedir'), self.paths['dir']['gnupg_homedir']
178             if self.mkdir(self.paths['dir']['gnupg_homedir']):
179                 # import keys
180                 print _('Importing keys')
181                 p1 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['erinn_key']])
182                 p1.wait()
183                 p2 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['sebastian_key']])
184                 p2.wait()
185                 p3 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['alexandre_key']])
186                 p3.wait()
187
188     # load settings
189     def load_settings(self):
190         if os.path.isfile(self.paths['file']['settings']):
191             self.settings = pickle.load(open(self.paths['file']['settings']))
192             # sanity checks
193             if not 'installed_version' in self.settings:
194                 return False
195             if not 'latest_version' in self.settings:
196                 return False
197             if not 'last_update_check_timestamp' in self.settings:
198                 return False
199         else:
200             self.settings = {
201                 'installed_version': False,
202                 'latest_version': '0',
203                 'last_update_check_timestamp': 0
204             }
205             self.save_settings()
206         return True
207
208     # save settings
209     def save_settings(self):
210         pickle.dump(self.settings, open(self.paths['file']['settings'], 'w'))
211         return True
212
213     # get the process id of a program
214     def get_pid(self, bin_path, python = False):
215         pid = None
216
217         for p in psutil.process_iter():
218             try:
219                 if p.pid != os.getpid():
220                     exe = None
221                     if python:
222                         if len(p.cmdline) > 1:
223                             if 'python' in p.cmdline[0]:
224                                 exe = p.cmdline[1]
225                     else:
226                         if len(p.cmdline) > 0:
227                             exe = p.cmdline[0]
228                     
229                     if exe == bin_path:
230                         pid = p.pid
231
232             except:
233                 pass
234
235         return pid
236
237     # bring program's x window to front
238     def bring_window_to_front(self, pid):
239         # figure out the window id
240         win_id = None
241         p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
242         for line in p.stdout.readlines():
243             line_split = line.split()
244             cur_win_id = line_split[0]
245             cur_win_pid = int(line_split[2])
246             if cur_win_pid == pid:
247                 win_id = cur_win_id
248
249         # bring to front
250         if win_id:
251             subprocess.call(['wmctrl', '-i', '-a', win_id])
252
253 class TBLSettings:
254     def __init__(self, common):
255         print _('Starting settings dialog')
256         self.common = common
257         self.common.load_settings()
258
259         # set up the window
260         self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
261         self.window.set_title(_("Tor Browser Launcher Settings"))
262         self.window.set_icon_from_file(self.common.paths['file']['icon'])
263         self.window.set_position(gtk.WIN_POS_CENTER)
264         self.window.set_border_width(10)
265         self.window.connect("delete_event", self.delete_event)
266         self.window.connect("destroy", self.destroy)
267         
268         # build the rest of the UI
269         self.box = gtk.VBox(False, 20)
270         self.window.add(self.box)
271
272         # labels
273         if(self.common.settings['installed_version']):
274             self.label1 = gtk.Label(_('Installed version: {0}').format(self.common.settings['installed_version']))
275         else:
276             self.label1 = gtk.Label(_('Tor Browser Bundle not installed'))
277         self.label1.set_line_wrap(True)
278         self.box.pack_start(self.label1, True, True, 0)
279         self.label1.show()
280
281         if(self.common.settings['last_update_check_timestamp'] > 0):
282             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']))))
283         else:
284             self.label1 = gtk.Label(_('Never checked for updates'))
285         self.label1.set_line_wrap(True)
286         self.box.pack_start(self.label1, True, True, 0)
287         self.label1.show()
288
289         # button box
290         self.button_box = gtk.HButtonBox()
291         self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
292         self.box.pack_start(self.button_box, True, True, 0)
293         self.button_box.show()
294         
295
296         # save and launch button
297         save_launch_image = gtk.Image()
298         save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
299         self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
300         self.save_launch_button.set_image(save_launch_image)
301         self.save_launch_button.connect("clicked", self.save_launch, None)
302         self.button_box.add(self.save_launch_button)
303         self.save_launch_button.show()
304
305         # save and exit button
306         save_exit_image = gtk.Image()
307         save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
308         self.save_exit_button = gtk.Button(_("Save & Exit"))
309         self.save_exit_button.set_image(save_exit_image)
310         self.save_exit_button.connect("clicked", self.save_exit, None)
311         self.button_box.add(self.save_exit_button)
312         self.save_exit_button.show()
313
314         # cancel button
315         cancel_image = gtk.Image()
316         cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
317         self.cancel_button = gtk.Button(_("Cancel"))
318         self.cancel_button.set_image(cancel_image)
319         self.cancel_button.connect("clicked", self.destroy, None)
320         self.button_box.add(self.cancel_button)
321         self.cancel_button.show()
322
323         # show the window
324         self.box.show()
325         self.window.show()
326
327         # start gtk
328         gtk.main()
329
330     # save and launch
331     def save_launch(self, widget, data=None):
332         self.save()
333         p = subprocess.Popen([self.common.paths['file']['tbl_bin']])
334         self.destroy(False)
335
336     # save and exit
337     def save_exit(self, widget, data=None):
338         self.save()
339         self.destroy(False)
340
341     # save settings
342     def save(self):
343         pass
344
345     # exit
346     def delete_event(self, widget, event, data=None):
347         return False
348     def destroy(self, widget, data=None):
349         gtk.main_quit()
350
351
352 class TBLLauncher:
353     def __init__(self, common):
354         print _('Starting launcher dialog')
355         self.common = common
356
357         self.set_gui(None, '', [])
358         self.launch_gui = True
359
360         # if we haven't already hit an error
361         if self.gui != 'error':
362             # load settings
363             if self.common.load_settings():
364                 self.common.build_paths(self.common.settings['latest_version'])
365
366                 # is vidalia already running and we just need to open a new firefox?
367                 if self.common.settings['installed_version']:
368                     vidalia_pid = self.common.get_pid('./App/vidalia')
369                     firefox_pid = self.common.get_pid(self.common.paths['file']['firefox_bin'])
370
371                     if vidalia_pid and not firefox_pid:
372                         print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
373                         self.common.bring_window_to_front(vidalia_pid)
374                         subprocess.Popen([self.common.paths['file']['firefox_bin'], '-no-remote', '-profile', self.common.paths['file']['firefox_profile']])
375                         return
376                     elif vidalia_pid and firefox_pid:
377                         print _('Vidalia and Firefox are already open, bringing them to focus')
378
379                         # bring firefox to front, then vidalia
380                         self.common.bring_window_to_front(firefox_pid)
381                         self.common.bring_window_to_front(vidalia_pid)
382                         return
383
384                 # how long was it since the last update check?
385                 # 86400 seconds = 24 hours
386                 current_timestamp = int(time.time())
387                 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
388                     # check for update
389                     print 'Checking for update'
390                     self.set_gui('task', _("Checking for Tor Browser update."), 
391                         ['download_update_check', 
392                          'attempt_update'])
393
394                 else:
395                     # no need to check for update
396                     print _('Checked for update within 24 hours, skipping')
397                     self.start_launcher()
398
399             else:
400                 self.set_gui('error', _("Error loading settings. Delete ~/.torbrowser and try again."), [])
401
402         if self.launch_gui:
403             # set up the window
404             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
405             self.window.set_title(_("Tor Browser"))
406             self.window.set_icon_from_file(self.common.paths['file']['icon'])
407             self.window.set_position(gtk.WIN_POS_CENTER)
408             self.window.set_border_width(10)
409             self.window.connect("delete_event", self.delete_event)
410             self.window.connect("destroy", self.destroy)
411
412             # build the rest of the UI
413             self.build_ui()
414
415     # download or run TBB
416     def start_launcher(self):
417       # is TBB already installed?
418       if os.path.isfile(self.common.paths['file']['start']) and os.access(self.common.paths['file']['start'], os.X_OK):
419         if self.common.settings['installed_version'] == self.common.settings['latest_version']:
420           # current version of tbb is installed, launch it
421           self.run(False)
422           self.launch_gui = False
423         elif self.common.settings['installed_version'] < self.common.settings['latest_version']:
424           # there is a tbb upgrade available
425           self.set_gui('task', _("Your Tor Browser is out of date."), 
426             ['download_tarball', 
427              'download_tarball_sig', 
428              'verify', 
429              'extract', 
430              'run'])
431         else:
432           # for some reason the installed tbb is newer than the current version?
433           self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
434
435       # not installed
436       else:
437           # are the tarball and sig already downloaded?
438           if os.path.isfile(self.common.paths['file']['tarball']) and os.path.isfile(self.common.paths['file']['tarball_sig']):
439               # start the gui with verify
440               self.set_gui('task', _("Installing Tor Browser."), 
441                   ['verify', 
442                    'extract', 
443                    'run'])
444
445           # first run
446           else:
447               self.set_gui('task', _("Downloading and installing Tor Browser."), 
448                   ['download_tarball', 
449                    'download_tarball_sig', 
450                    'verify', 
451                    'extract', 
452                    'run'])
453    
454     # there are different GUIs that might appear, this sets which one we want
455     def set_gui(self, gui, message, tasks, autostart=True):
456         self.gui = gui
457         self.gui_message = message
458         self.gui_tasks = tasks
459         self.gui_task_i = 0
460         self.gui_autostart = autostart
461
462     # set all gtk variables to False
463     def clear_ui(self):
464         if hasattr(self, 'box'):
465             self.box.destroy()
466         self.box = False
467
468         self.label = False
469         self.progressbar = False
470         self.button_box = False
471         self.start_button = False
472         self.exit_button = False
473
474     # build the application's UI
475     def build_ui(self):
476         self.box = gtk.VBox(False, 20)
477         self.window.add(self.box)
478
479         if self.gui == 'error':
480             # labels
481             self.label = gtk.Label( self.gui_message ) 
482             self.label.set_line_wrap(True)
483             self.box.pack_start(self.label, True, True, 0)
484             self.label.show()
485
486             # exit button
487             exit_image = gtk.Image()
488             exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
489             self.exit_button = gtk.Button("Exit")
490             self.exit_button.set_image(exit_image)
491             self.exit_button.connect("clicked", self.destroy, None)
492             self.box.add(self.exit_button)
493             self.exit_button.show()
494
495         elif self.gui == 'task':
496             # label
497             self.label = gtk.Label( self.gui_message ) 
498             self.label.set_line_wrap(True)
499             self.box.pack_start(self.label, True, True, 0)
500             self.label.show()
501             
502             # progress bar
503             self.progressbar = gtk.ProgressBar(adjustment=None)
504             self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
505             self.progressbar.set_pulse_step(0.01)
506             self.box.pack_start(self.progressbar, True, True, 0)
507
508             # button box
509             self.button_box = gtk.HButtonBox()
510             self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
511             self.box.pack_start(self.button_box, True, True, 0)
512             self.button_box.show()
513
514             # start button
515             start_image = gtk.Image()
516             start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
517             self.start_button = gtk.Button(_("Start"))
518             self.start_button.set_image(start_image)
519             self.start_button.connect("clicked", self.start, None)
520             self.button_box.add(self.start_button)
521             if not self.gui_autostart:
522               self.start_button.show()
523
524             # exit button
525             exit_image = gtk.Image()
526             exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
527             self.exit_button = gtk.Button(_("Exit"))
528             self.exit_button.set_image(exit_image)
529             self.exit_button.connect("clicked", self.destroy, None)
530             self.button_box.add(self.exit_button)
531             self.exit_button.show()
532
533         self.box.show()
534         self.window.show()
535
536         if self.gui_autostart:
537             self.start(None)
538
539     # start button clicked, begin tasks
540     def start(self, widget, data=None):
541         # disable the start button
542         if self.start_button:
543             self.start_button.set_sensitive(False)
544
545         # start running tasks
546         self.run_task()
547       
548     # run the next task in the task list
549     def run_task(self):
550         self.refresh_gtk()
551
552         if self.gui_task_i >= len(self.gui_tasks):
553             self.destroy(False)
554             return
555
556         task = self.gui_tasks[self.gui_task_i]
557         
558         # get ready for the next task
559         self.gui_task_i += 1
560
561         if task == 'download_update_check':
562             print _('Downloading'), self.common.paths['url']['update_check']
563             self.download('update check', self.common.paths['url']['update_check'], self.common.paths['file']['update_check'])
564         
565         if task == 'attempt_update':
566             print _('Checking to see if update it needed')
567             self.attempt_update()
568
569         elif task == 'download_tarball':
570             print _('Downloading'), self.common.paths['url']['tarball']
571             self.download('tarball', self.common.paths['url']['tarball'], self.common.paths['file']['tarball'])
572
573         elif task == 'download_tarball_sig':
574             print _('Downloading'), self.common.paths['url']['tarball_sig']
575             self.download('signature', self.common.paths['url']['tarball_sig'], self.common.paths['file']['tarball_sig'])
576
577         elif task == 'verify':
578             print _('Verifying signature')
579             self.verify()
580
581         elif task == 'extract':
582             print _('Extracting'), self.common.paths['filename']['tarball']
583             self.extract()
584
585         elif task == 'run':
586             print _('Running'), self.common.paths['file']['start']
587             self.run()
588         
589         elif task == 'start_over':
590             print _('Starting download over again')
591             self.start_over()
592
593     def response_received(self, response):
594         class FileDownloader(Protocol):
595             def __init__(self, file, total, progress, done_cb):
596                 self.file = file
597                 self.total = total
598                 self.so_far = 0
599                 self.progress = progress
600                 self.all_done = done_cb
601
602             def dataReceived(self, bytes):
603                 self.file.write(bytes)
604                 self.so_far += len(bytes)
605                 percent = float(self.so_far) / float(self.total)
606                 self.progress.set_fraction(percent)
607                 amount = float(self.so_far)
608                 units = "bytes"
609                 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
610                     if amount > size:
611                         units = unit
612                         amount = amount / float(size)
613                         break
614
615                 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
616
617             def connectionLost(self, reason):
618                 print _('Finished receiving body:'), reason.getErrorMessage()
619                 self.all_done(reason)
620
621         dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
622         response.deliverBody(dl)
623
624     def response_finished(self, msg):
625         if msg.check(ResponseDone):
626             self.file_download.close()
627             # next task!
628             self.run_task()
629
630         else:
631             print "FINISHED", msg
632             ## FIXME handle errors
633
634     def download_error(self, f):
635         print _("Download error"), f
636         self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
637         self.clear_ui()
638         self.build_ui()
639
640     def download(self, name, url, path):
641         # initialize the progress bar
642         self.progressbar.set_fraction(0) 
643         self.progressbar.set_text(_('Downloading {0}').format(name))
644         self.progressbar.show()
645         self.refresh_gtk()
646
647         agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['file']['torproject_pem']))
648         d = agent.request('GET', url,
649                           Headers({'User-Agent': ['torbrowser-launcher']}),
650                           None)
651
652         self.file_download = open(path, 'w')
653         d.addCallback(self.response_received).addErrback(self.download_error)
654         
655         if not reactor.running:
656             reactor.run()
657
658     def attempt_update(self):
659         # load the update check file
660         try:
661             versions = json.load(open(self.common.paths['file']['update_check']))
662             latest_version = None
663
664             end = '-Linux'
665             for version in versions:
666                 if str(version).find(end) != -1:
667                     latest_version = str(version)
668
669             if latest_version:
670                 self.common.settings['latest_version'] = latest_version[:-len(end)]
671                 self.common.settings['last_update_check_timestamp'] = int(time.time())
672                 self.common.save_settings()
673                 self.common.build_paths(self.common.settings['latest_version'])
674                 self.start_launcher()
675
676             else:
677                 # failed to find the latest version
678                 self.set_gui('error', _("Error checking for updates."), [], False)
679         
680         except:
681             # not a valid JSON object
682             self.set_gui('error', _("Error checking for updates."), [], False)
683
684         # now start over
685         self.clear_ui()
686         self.build_ui()
687
688     def verify(self):
689         # initialize the progress bar
690         self.progressbar.set_fraction(0) 
691         self.progressbar.set_text(_('Verifying Signature'))
692         self.progressbar.show()
693
694         p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['dir']['gnupg_homedir'], '--verify', self.common.paths['file']['tarball_sig']])
695         self.pulse_until_process_exits(p)
696         
697         if p.returncode == 0:
698             self.run_task()
699         else:
700             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)
701             self.clear_ui()
702             self.build_ui()
703
704             if not reactor.running:
705                 reactor.run()
706
707     def extract(self):
708         # initialize the progress bar
709         self.progressbar.set_fraction(0) 
710         self.progressbar.set_text(_('Installing'))
711         self.progressbar.show()
712         self.refresh_gtk()
713
714         # make sure this file is a tarfile
715         if tarfile.is_tarfile(self.common.paths['file']['tarball']):
716           tf = tarfile.open(self.common.paths['file']['tarball'])
717           tf.extractall(self.common.paths['dir']['tbb'])
718         else:
719             self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
720             self.clear_ui()
721             self.build_ui()
722
723         # installation is finished, so save installed_version
724         self.common.settings['installed_version'] = self.common.settings['latest_version']
725         self.common.save_settings()
726
727         self.run_task()
728
729     def run(self, run_next_task = True):
730         subprocess.Popen([self.common.paths['file']['start']])
731         if run_next_task:
732             self.run_task()
733
734     # make the progress bar pulse until process p (a Popen object) finishes
735     def pulse_until_process_exits(self, p):
736         while p.poll() == None:
737             time.sleep(0.01)
738             self.progressbar.pulse()
739             self.refresh_gtk()
740
741     # start over and download TBB again
742     def start_over(self):
743         self.label.set_text(_("Downloading Tor Browser Bundle over again."))
744         self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
745         self.gui_task_i = 0
746         self.start(None)
747    
748     # refresh gtk
749     def refresh_gtk(self):
750         while gtk.events_pending():
751             gtk.main_iteration(False)
752
753     # exit
754     def delete_event(self, widget, event, data=None):
755         return False
756     def destroy(self, widget, data=None):
757         if hasattr(self, 'file_download'):
758             self.file_download.close()
759         if reactor.running:
760             reactor.stop()
761
762 if __name__ == "__main__":
763     tor_browser_launcher_version = '0.0.1'
764
765     print _('Tor Browser Launcher')
766     print _('By Micah Lee, licensed under GPLv3')
767     print _('version {0}').format(tor_browser_launcher_version)
768     print 'https://github.com/micahflee/torbrowser-launcher'
769
770     common = TBLCommon()
771
772     # is torbrowser-launcher already running?
773     tbl_pid = common.get_pid(common.paths['file']['tbl_bin'], True)
774     if tbl_pid:
775         print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
776         common.bring_window_to_front(tbl_pid)
777         sys.exit()
778
779     if '-settings' in sys.argv:
780         # settings mode
781         app = TBLSettings(common)
782
783     else:
784         # launcher mode
785         app = TBLLauncher(common)
786