4 https://github.com/micahflee/torbrowser-launcher/
6 Copyright (c) 2013 Micah Lee <micahflee@riseup.net>
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
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
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.
31 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
33 from twisted.internet import gtk2reactor
35 from twisted.internet import reactor
41 import os, sys, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil
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
48 from OpenSSL.SSL import Context, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT
49 from OpenSSL.crypto import load_certificate, FILETYPE_PEM
51 class VerifyTorProjectCert(ClientContextFactory):
53 def __init__(self, torproject_pem):
54 self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
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)
62 def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
63 return cert.digest('sha256') == self.torproject_ca.digest('sha256')
67 def __init__(self, tbl_version):
68 print _('Initializing Tor Browser Launcher')
69 self.tbl_version = tbl_version
72 self.discover_arch_lang()
74 self.mkdir(self.paths['data_dir'])
76 self.mkdir(self.paths['download_dir'])
77 self.mkdir(self.paths['tbb'][self.settings['preferred']]['dir'])
80 self.available_versions = {
81 'tbb_stable': _('Tor Browser Bundle - stable'),
82 'tbb_alpha': _('Tor Browser Bundle - alpha'),
83 'obs_tbb': _('Obsfproxy Tor Browser Bundle')
86 # allow buttons to have icons
88 gtk_settings = gtk.settings_get_default()
89 gtk_settings.props.gtk_button_images = True
93 # discover the architecture and language
94 def discover_arch_lang(self):
95 # figure out the architecture
96 (sysname, nodename, release, version, machine) = os.uname()
97 self.architecture = machine
99 # figure out the language
100 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
101 default_locale = locale.getdefaultlocale()[0]
102 if default_locale == None:
103 self.language = 'en-US'
105 self.language = default_locale.replace('_', '-')
106 if self.language not in available_languages:
107 self.language = self.language.split('-')[0]
108 if self.language not in available_languages:
109 for l in available_languages:
110 if l[0:2] == self.language:
112 # if language isn't available, default to english
113 if self.language not in available_languages:
114 self.language = 'en-US'
116 # build all relevant paths
117 def build_paths(self, tbb_version = None):
118 homedir = os.getenv('HOME')
120 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
121 if os.path.exists(homedir) == False:
123 os.mkdir(homedir, 0700)
125 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
126 if not os.access(homedir, os.W_OK):
127 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
129 tbb_data = '%s/.torbrowser' % homedir
132 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
133 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
134 self.paths['tarball_sig_file'] = tbb_data+'/download/'+tarball_filename+'.asc'
135 self.paths['tarball_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
136 self.paths['tarball_sig_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
137 self.paths['tarball_filename'] = tarball_filename
138 self.paths['tarball_sig_filename'] = tarball_filename+'.asc'
142 'tbl_bin': '/usr/bin/torbrowser-launcher',
143 'icon_file': '/usr/share/pixmaps/torbrowser80.xpm',
144 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
145 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
146 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
147 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc',
148 'data_dir': tbb_data,
149 'download_dir': tbb_data+'/download',
150 'gnupg_homedir': tbb_data+'/gnupg_homedir',
151 'settings_file': tbb_data+'/settings',
152 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
153 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
156 'dir': tbb_data+'/tbb/stable/'+self.architecture,
157 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
158 'vidalia_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
159 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
160 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
163 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
164 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
165 'vidalia_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
166 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
167 'firefox_profile': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
170 'dir': tbb_data+'/tbb/obs/'+self.architecture,
171 'start': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
172 'vidalia_bin': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
173 'firefox_bin': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
174 'firefox_profile': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
180 def mkdir(self, path):
182 if os.path.exists(path) == False:
183 os.makedirs(path, 0700)
186 print _("Cannot create directory {0}").format(path)
188 if not os.access(path, os.W_OK):
189 print _("{0} is not writable").format(path)
193 # if gnupg_homedir isn't set up, set it up
194 def init_gnupg(self):
195 if not os.path.exists(self.paths['gnupg_homedir']):
196 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
197 if self.mkdir(self.paths['gnupg_homedir']):
199 print _('Importing keys')
200 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
201 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['sebastian_key']]).wait()
202 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['alexandre_key']]).wait()
205 def load_settings(self):
207 'tbl_version': self.tbl_version,
208 'preferred': 'tbb_stable',
209 'installed_version': {
219 'update_over_tor': True,
220 'check_for_updates': False,
221 'last_update_check_timestamp': 0
224 if os.path.isfile(self.paths['settings_file']):
225 settings = pickle.load(open(self.paths['settings_file']))
227 # what version settings is this?
228 if not 'tbl_version' in settings:
229 settings['tbl_version'] = '0.0.1'
231 # sanity checks for current version
232 if settings['tbl_version'] == self.tbl_version:
234 if not 'preferred' in settings:
235 good_settings = False
236 if not 'installed_version' in settings:
237 good_settings = False
238 if not 'tbb_stable' in settings['installed_version']:
239 good_settings = False
240 if not 'tbb_alpha' in settings['installed_version']:
241 good_settings = False
242 if not 'obs_tbb' in settings['installed_version']:
243 good_settings = False
244 if not 'latest_version' in settings:
245 good_settings = False
246 if not 'tbb_stable' in settings['latest_version']:
247 good_settings = False
248 if not 'tbb_alpha' in settings['latest_version']:
249 good_settings = False
250 if not 'obs_tbb' in settings['latest_version']:
251 good_settings = False
252 if not 'update_over_tor' in settings:
253 good_settings = False
254 if not 'check_for_updates' in settings:
255 good_settings = False
256 if not 'last_update_check_timestamp' in settings:
257 good_settings = False
260 self.settings = settings
262 setting.settings = default_settings
264 # settings migrations for previous versions
265 elif settings['tbl_version'] == '0.0.1':
266 self.settings = default_settings
267 self.settings['installed_version']['tbb_alpha'] = settings['installed_version']
271 self.mkdir(self.paths['tbb']['tbb_alpha']['dir'])
272 # todo: move already-installed TBB alpha to new location
273 if os.path.exists(self.paths['data_dir']+'/tbb/x86_64'):
275 if os.path.exists(self.paths['data_dir']+'/tbb/i686'):
279 self.settings = default_settings
283 def save_settings(self):
284 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
287 # get the process id of a program
288 def get_pid(self, bin_path, python = False):
291 for p in psutil.process_iter():
293 if p.pid != os.getpid():
296 if len(p.cmdline) > 1:
297 if 'python' in p.cmdline[0]:
300 if len(p.cmdline) > 0:
311 # bring program's x window to front
312 def bring_window_to_front(self, pid):
313 # figure out the window id
315 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
316 for line in p.stdout.readlines():
317 line_split = line.split()
318 cur_win_id = line_split[0]
319 cur_win_pid = int(line_split[2])
320 if cur_win_pid == pid:
325 subprocess.call(['wmctrl', '-i', '-a', win_id])
328 def __init__(self, common):
329 print _('Starting settings dialog')
333 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
334 self.window.set_title(_("Tor Browser Launcher Settings"))
335 self.window.set_icon_from_file(self.common.paths['icon_file'])
336 self.window.set_position(gtk.WIN_POS_CENTER)
337 self.window.set_border_width(10)
338 self.window.connect("delete_event", self.delete_event)
339 self.window.connect("destroy", self.destroy)
341 # build the rest of the UI
342 self.box = gtk.VBox(False, 10)
343 self.window.add(self.box)
346 self.hbox = gtk.HBox(False, 10)
347 self.box.pack_start(self.hbox, True, True, 0)
350 self.settings_box = gtk.VBox(False, 10)
351 self.hbox.pack_start(self.settings_box, True, True, 0)
352 self.settings_box.show()
354 self.labels_box = gtk.VBox(False, 10)
355 self.hbox.pack_start(self.labels_box, True, True, 0)
356 self.labels_box.show()
359 self.pref_ver_box = gtk.HBox(False, 10)
360 self.settings_box.pack_start(self.pref_ver_box, True, True, 0)
361 self.pref_ver_box.show()
363 self.pref_ver_label = gtk.Label(_('I prefer'))
364 self.pref_ver_label.set_line_wrap(True)
365 self.pref_ver_box.pack_start(self.pref_ver_label, True, True, 0)
366 self.pref_ver_label.show()
369 for i in self.common.available_versions:
370 options.append(self.common.available_versions[i])
373 self.pref_ver = gtk.combo_box_new_text()
374 for option in options:
375 self.pref_ver.append_text(option)
376 self.pref_ver.set_active(0)
377 self.pref_ver_box.pack_start(self.pref_ver, True, True, 0)
381 self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
382 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
383 self.tor_update_checkbox.show()
386 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
387 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
388 self.update_checkbox.show()
391 if(self.common.settings['installed_version']):
392 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
394 self.label1 = gtk.Label(_('Not installed'))
395 self.label1.set_line_wrap(True)
396 self.labels_box.pack_start(self.label1, True, True, 0)
399 if(self.common.settings['last_update_check_timestamp'] > 0):
400 self.label1 = gtk.Label(_('Last checked for updates:\n{0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
402 self.label1 = gtk.Label(_('Never checked for updates'))
403 self.label1.set_line_wrap(True)
404 self.labels_box.pack_start(self.label1, True, True, 0)
408 self.button_box = gtk.HButtonBox()
409 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
410 self.box.pack_start(self.button_box, True, True, 0)
411 self.button_box.show()
413 # save and launch button
414 save_launch_image = gtk.Image()
415 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
416 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
417 self.save_launch_button.set_image(save_launch_image)
418 self.save_launch_button.connect("clicked", self.save_launch, None)
419 self.button_box.add(self.save_launch_button)
420 self.save_launch_button.show()
422 # save and exit button
423 save_exit_image = gtk.Image()
424 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
425 self.save_exit_button = gtk.Button(_("Save & Exit"))
426 self.save_exit_button.set_image(save_exit_image)
427 self.save_exit_button.connect("clicked", self.save_exit, None)
428 self.button_box.add(self.save_exit_button)
429 self.save_exit_button.show()
432 cancel_image = gtk.Image()
433 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
434 self.cancel_button = gtk.Button(_("Cancel"))
435 self.cancel_button.set_image(cancel_image)
436 self.cancel_button.connect("clicked", self.destroy, None)
437 self.button_box.add(self.cancel_button)
438 self.cancel_button.show()
447 def save_launch(self, widget, data=None):
449 p = subprocess.Popen([self.common.paths['tbl_bin']])
453 def save_exit(self, widget, data=None):
462 def delete_event(self, widget, event, data=None):
464 def destroy(self, widget, data=None):
469 def __init__(self, common):
470 print _('Starting launcher dialog')
474 self.set_gui(None, '', [])
475 self.launch_gui = True
476 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
478 # is vidalia already running and we just need to open a new firefox?
479 if self.common.settings['installed_version']:
480 vidalia_pid = self.common.get_pid('./App/vidalia')
481 firefox_pid = self.common.get_pid(self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'])
483 if vidalia_pid and not firefox_pid:
484 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
485 self.common.bring_window_to_front(vidalia_pid)
486 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'], '-no-remote', '-profile', self.common.paths['tbb'][self.common.settings['preferred']]['firefox_profile']])
488 elif vidalia_pid and firefox_pid:
489 print _('Vidalia and Firefox are already open, bringing them to focus')
491 # bring firefox to front, then vidalia
492 self.common.bring_window_to_front(firefox_pid)
493 self.common.bring_window_to_front(vidalia_pid)
497 check_for_updates = False
498 if self.common.settings['check_for_updates']:
499 check_for_updates = True
501 if not check_for_updates:
502 # how long was it since the last update check?
503 # 86400 seconds = 24 hours
504 current_timestamp = int(time.time())
505 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
506 check_for_updates = True
508 if check_for_updates:
510 print 'Checking for update'
511 self.set_gui('task', _("Checking for Tor Browser update."),
512 ['download_update_check',
515 # no need to check for update
516 print _('Checked for update within 24 hours, skipping')
517 self.start_launcher()
521 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
522 self.window.set_title(_("Tor Browser"))
523 self.window.set_icon_from_file(self.common.paths['icon_file'])
524 self.window.set_position(gtk.WIN_POS_CENTER)
525 self.window.set_border_width(10)
526 self.window.connect("delete_event", self.delete_event)
527 self.window.connect("destroy", self.destroy)
529 # build the rest of the UI
532 # download or run TBB
533 def start_launcher(self):
534 # is TBB already installed?
535 if os.path.isfile(self.common.paths['tbb'][self.common.settings['preferred']]['start']) and os.access(self.common.paths['tbb'][self.common.settings['preferred']]['start'], os.X_OK):
536 if self.common.settings['installed_version'] == self.common.settings['latest_version'][self.common.settings['preferred']]:
537 # current version of tbb is installed, launch it
539 self.launch_gui = False
540 elif self.common.settings['installed_version'] < self.common.settings['latest_version'][self.common.settings['preferred']]:
541 # there is a tbb upgrade available
542 self.set_gui('task', _("Your Tor Browser is out of date."),
544 'download_tarball_sig',
549 # for some reason the installed tbb is newer than the current version?
550 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
554 # are the tarball and sig already downloaded?
555 if os.path.isfile(self.common.paths['tarball_file']) and os.path.isfile(self.common.paths['tarball_sig_file']):
556 # start the gui with verify
557 self.set_gui('task', _("Installing Tor Browser."),
564 self.set_gui('task', _("Downloading and installing Tor Browser."),
566 'download_tarball_sig',
571 # there are different GUIs that might appear, this sets which one we want
572 def set_gui(self, gui, message, tasks, autostart=True):
574 self.gui_message = message
575 self.gui_tasks = tasks
577 self.gui_autostart = autostart
579 # set all gtk variables to False
581 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
586 self.progressbar = False
587 self.button_box = False
588 self.start_button = False
589 self.exit_button = False
591 # build the application's UI
595 self.box = gtk.VBox(False, 20)
596 self.window.add(self.box)
598 if self.gui == 'error':
600 self.label = gtk.Label( self.gui_message )
601 self.label.set_line_wrap(True)
602 self.box.pack_start(self.label, True, True, 0)
606 exit_image = gtk.Image()
607 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
608 self.exit_button = gtk.Button("Exit")
609 self.exit_button.set_image(exit_image)
610 self.exit_button.connect("clicked", self.destroy, None)
611 self.box.add(self.exit_button)
612 self.exit_button.show()
614 elif self.gui == 'task':
616 self.label = gtk.Label( self.gui_message )
617 self.label.set_line_wrap(True)
618 self.box.pack_start(self.label, True, True, 0)
622 self.progressbar = gtk.ProgressBar(adjustment=None)
623 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
624 self.progressbar.set_pulse_step(0.01)
625 self.box.pack_start(self.progressbar, True, True, 0)
628 self.button_box = gtk.HButtonBox()
629 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
630 self.box.pack_start(self.button_box, True, True, 0)
631 self.button_box.show()
634 start_image = gtk.Image()
635 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
636 self.start_button = gtk.Button(_("Start"))
637 self.start_button.set_image(start_image)
638 self.start_button.connect("clicked", self.start, None)
639 self.button_box.add(self.start_button)
640 if not self.gui_autostart:
641 self.start_button.show()
644 exit_image = gtk.Image()
645 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
646 self.exit_button = gtk.Button(_("Exit"))
647 self.exit_button.set_image(exit_image)
648 self.exit_button.connect("clicked", self.destroy, None)
649 self.button_box.add(self.exit_button)
650 self.exit_button.show()
655 if self.gui_autostart:
658 # start button clicked, begin tasks
659 def start(self, widget, data=None):
660 # disable the start button
661 if self.start_button:
662 self.start_button.set_sensitive(False)
664 # start running tasks
667 # run the next task in the task list
671 if self.gui_task_i >= len(self.gui_tasks):
675 task = self.gui_tasks[self.gui_task_i]
677 # get ready for the next task
680 if task == 'download_update_check':
681 print _('Downloading'), self.common.paths['update_check_url']
682 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
684 if task == 'attempt_update':
685 print _('Checking to see if update it needed')
686 self.attempt_update()
688 elif task == 'download_tarball':
689 print _('Downloading'), self.common.paths['tarball_url']
690 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
692 elif task == 'download_tarball_sig':
693 print _('Downloading'), self.common.paths['tarball_sig_url']
694 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
696 elif task == 'verify':
697 print _('Verifying signature')
700 elif task == 'extract':
701 print _('Extracting'), self.common.paths['tarball_filename']
705 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
708 elif task == 'start_over':
709 print _('Starting download over again')
712 def response_received(self, response):
713 class FileDownloader(Protocol):
714 def __init__(self, file, total, progress, done_cb):
718 self.progress = progress
719 self.all_done = done_cb
721 def dataReceived(self, bytes):
722 self.file.write(bytes)
723 self.so_far += len(bytes)
724 percent = float(self.so_far) / float(self.total)
725 self.progress.set_fraction(percent)
726 amount = float(self.so_far)
728 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
731 amount = amount / float(size)
734 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
736 def connectionLost(self, reason):
737 print _('Finished receiving body:'), reason.getErrorMessage()
738 self.all_done(reason)
740 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
741 response.deliverBody(dl)
743 def response_finished(self, msg):
744 if msg.check(ResponseDone):
745 self.file_download.close()
750 print "FINISHED", msg
751 ## FIXME handle errors
753 def download_error(self, f):
754 print _("Download error"), f
755 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
759 def download(self, name, url, path):
760 # initialize the progress bar
761 self.progressbar.set_fraction(0)
762 self.progressbar.set_text(_('Downloading {0}').format(name))
763 self.progressbar.show()
766 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
767 d = agent.request('GET', url,
768 Headers({'User-Agent': ['torbrowser-launcher']}),
771 self.file_download = open(path, 'w')
772 d.addCallback(self.response_received).addErrback(self.download_error)
774 if not reactor.running:
777 def attempt_update(self):
778 # load the update check file
780 versions = json.load(open(self.common.paths['update_check_file']))
781 latest_version = None
784 for version in versions:
785 if str(version).find(end) != -1:
786 latest_version = str(version)
789 self.common.settings['latest_version'][self.common.settings['preferred']] = latest_version[:-len(end)]
790 self.common.settings['last_update_check_timestamp'] = int(time.time())
791 self.common.settings['check_for_updates'] = False
792 self.common.save_settings()
793 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
794 self.start_launcher()
797 # failed to find the latest version
798 self.set_gui('error', _("Error checking for updates."), [], False)
801 # not a valid JSON object
802 self.set_gui('error', _("Error checking for updates."), [], False)
809 # initialize the progress bar
810 self.progressbar.set_fraction(0)
811 self.progressbar.set_text(_('Verifying Signature'))
812 self.progressbar.show()
814 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['tarball_sig_file']])
815 self.pulse_until_process_exits(p)
817 if p.returncode == 0:
820 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)
824 if not reactor.running:
828 # initialize the progress bar
829 self.progressbar.set_fraction(0)
830 self.progressbar.set_text(_('Installing'))
831 self.progressbar.show()
834 # make sure this file is a tarfile
835 if tarfile.is_tarfile(self.common.paths['tarball_file']):
836 tf = tarfile.open(self.common.paths['tarball_file'])
837 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
839 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
843 # installation is finished, so save installed_version
844 self.common.settings['installed_version'] = self.common.settings['latest_version']
845 self.common.save_settings()
849 def run(self, run_next_task = True):
850 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
854 # make the progress bar pulse until process p (a Popen object) finishes
855 def pulse_until_process_exits(self, p):
856 while p.poll() == None:
858 self.progressbar.pulse()
861 # start over and download TBB again
862 def start_over(self):
863 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
864 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
869 def refresh_gtk(self):
870 while gtk.events_pending():
871 gtk.main_iteration(False)
874 def delete_event(self, widget, event, data=None):
876 def destroy(self, widget, data=None):
877 if hasattr(self, 'file_download'):
878 self.file_download.close()
882 if __name__ == "__main__":
883 tor_browser_launcher_version = '0.0.2'
885 print _('Tor Browser Launcher')
886 print _('By Micah Lee, licensed under GPLv3')
887 print _('version {0}').format(tor_browser_launcher_version)
888 print 'https://github.com/micahflee/torbrowser-launcher'
890 common = TBLCommon(tor_browser_launcher_version)
892 # is torbrowser-launcher already running?
893 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
895 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
896 common.bring_window_to_front(tbl_pid)
899 if '-settings' in sys.argv:
901 app = TBLSettings(common)
905 app = TBLLauncher(common)