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 sys.path.append('/usr/share/torbrowser-launcher/lib/')
34 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
36 from twisted.internet import gtk2reactor
38 from twisted.internet import reactor
44 import os, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil
46 from twisted.web.client import Agent, ResponseDone
47 from twisted.web.http_headers import Headers
48 from twisted.internet.protocol import Protocol
49 from twisted.internet.ssl import ClientContextFactory
50 from twisted.internet.endpoints import TCP4ClientEndpoint
52 from txsocksx.client import SOCKS5ClientEndpoint
54 from OpenSSL.SSL import Context, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT
55 from OpenSSL.crypto import load_certificate, FILETYPE_PEM
57 class VerifyTorProjectCert(ClientContextFactory):
59 def __init__(self, torproject_pem):
60 self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
62 def getContext(self, host, port):
63 ctx = ClientContextFactory.getContext(self)
64 ctx.set_verify_depth(0)
65 ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
68 def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
69 return cert.digest('sha256') == self.torproject_ca.digest('sha256')
73 def __init__(self, tbl_version):
74 print _('Initializing Tor Browser Launcher')
75 self.tbl_version = tbl_version
78 self.discover_arch_lang()
80 self.mkdir(self.paths['data_dir'])
82 self.mkdir(self.paths['download_dir'])
83 self.mkdir(self.paths['tbb'][self.settings['preferred']]['dir'])
86 self.available_versions = {
87 'stable': _('Tor Browser Bundle - stable'),
88 'alpha': _('Tor Browser Bundle - alpha')
91 # allow buttons to have icons
93 gtk_settings = gtk.settings_get_default()
94 gtk_settings.props.gtk_button_images = True
98 # discover the architecture and language
99 def discover_arch_lang(self):
100 # figure out the architecture
101 (sysname, nodename, release, version, machine) = os.uname()
102 self.architecture = machine
104 # figure out the language
105 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
106 default_locale = locale.getdefaultlocale()[0]
107 if default_locale == None:
108 self.language = 'en-US'
110 self.language = default_locale.replace('_', '-')
111 if self.language not in available_languages:
112 self.language = self.language.split('-')[0]
113 if self.language not in available_languages:
114 for l in available_languages:
115 if l[0:2] == self.language:
117 # if language isn't available, default to english
118 if self.language not in available_languages:
119 self.language = 'en-US'
121 # build all relevant paths
122 def build_paths(self, tbb_version = None):
123 homedir = os.getenv('HOME')
125 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
126 if os.path.exists(homedir) == False:
128 os.mkdir(homedir, 0700)
130 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
131 if not os.access(homedir, os.W_OK):
132 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
134 tbb_data = '%s/.torbrowser' % homedir
137 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
138 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
139 self.paths['tarball_sig_file'] = tbb_data+'/download/'+tarball_filename+'.asc'
140 self.paths['tarball_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
141 self.paths['tarball_sig_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
142 self.paths['tarball_filename'] = tarball_filename
143 self.paths['tarball_sig_filename'] = tarball_filename+'.asc'
147 'tbl_bin': '/usr/bin/torbrowser-launcher',
148 'icon_file': '/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 'data_dir': tbb_data,
154 'download_dir': tbb_data+'/download',
155 'gnupg_homedir': tbb_data+'/gnupg_homedir',
156 'settings_file': tbb_data+'/settings',
157 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
158 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
161 'dir': tbb_data+'/tbb/stable/'+self.architecture,
162 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
163 'vidalia_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
164 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
165 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
168 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
169 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
170 'vidalia_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
171 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
172 'firefox_profile': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
178 def mkdir(self, path):
180 if os.path.exists(path) == False:
181 os.makedirs(path, 0700)
184 print _("Cannot create directory {0}").format(path)
186 if not os.access(path, os.W_OK):
187 print _("{0} is not writable").format(path)
191 # if gnupg_homedir isn't set up, set it up
192 def init_gnupg(self):
193 if not os.path.exists(self.paths['gnupg_homedir']):
194 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
195 if self.mkdir(self.paths['gnupg_homedir']):
197 print _('Importing keys')
198 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
199 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['sebastian_key']]).wait()
200 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['alexandre_key']]).wait()
203 def load_settings(self):
205 'tbl_version': self.tbl_version,
206 'preferred': 'stable',
207 'installed_version': {
215 'update_over_tor': True,
216 'check_for_updates': False,
217 'last_update_check_timestamp': 0
220 if os.path.isfile(self.paths['settings_file']):
221 settings = pickle.load(open(self.paths['settings_file']))
223 # what version settings is this?
224 if not 'tbl_version' in settings:
225 settings['tbl_version'] = '0.0.1'
227 # sanity checks for current version
228 if settings['tbl_version'] == self.tbl_version:
230 if not 'preferred' in settings:
231 good_settings = False
232 if not 'installed_version' in settings:
233 good_settings = False
234 if not 'stable' in settings['installed_version']:
235 good_settings = False
236 if not 'alpha' in settings['installed_version']:
237 good_settings = False
238 if not 'latest_version' in settings:
239 good_settings = False
240 if not 'stable' in settings['latest_version']:
241 good_settings = False
242 if not 'alpha' in settings['latest_version']:
243 good_settings = False
244 if not 'update_over_tor' in settings:
245 good_settings = False
246 if not 'check_for_updates' in settings:
247 good_settings = False
248 if not 'last_update_check_timestamp' in settings:
249 good_settings = False
252 self.settings = settings
254 self.settings = default_settings
256 # settings migrations for previous versions
257 elif settings['tbl_version'] == '0.0.1':
258 self.settings = default_settings
259 self.settings['installed_version']['alpha'] = settings['installed_version']
263 self.mkdir(self.paths['tbb']['alpha']['dir'])
264 # todo: move already-installed TBB alpha to new location
265 if os.path.exists(self.paths['data_dir']+'/tbb/x86_64'):
267 if os.path.exists(self.paths['data_dir']+'/tbb/i686'):
271 self.settings = default_settings
275 def save_settings(self):
276 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
279 # get the process id of a program
280 def get_pid(self, bin_path, python = False):
283 for p in psutil.process_iter():
285 if p.pid != os.getpid():
288 if len(p.cmdline) > 1:
289 if 'python' in p.cmdline[0]:
292 if len(p.cmdline) > 0:
303 # bring program's x window to front
304 def bring_window_to_front(self, pid):
305 # figure out the window id
307 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
308 for line in p.stdout.readlines():
309 line_split = line.split()
310 cur_win_id = line_split[0]
311 cur_win_pid = int(line_split[2])
312 if cur_win_pid == pid:
317 subprocess.call(['wmctrl', '-i', '-a', win_id])
320 def __init__(self, common):
321 print _('Starting settings dialog')
325 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
326 self.window.set_title(_("Tor Browser Launcher Settings"))
327 self.window.set_icon_from_file(self.common.paths['icon_file'])
328 self.window.set_position(gtk.WIN_POS_CENTER)
329 self.window.set_border_width(10)
330 self.window.connect("delete_event", self.delete_event)
331 self.window.connect("destroy", self.destroy)
333 # build the rest of the UI
334 self.box = gtk.VBox(False, 10)
335 self.window.add(self.box)
338 self.hbox = gtk.HBox(False, 10)
339 self.box.pack_start(self.hbox, True, True, 0)
342 self.settings_box = gtk.VBox(False, 10)
343 self.hbox.pack_start(self.settings_box, True, True, 0)
344 self.settings_box.show()
346 self.labels_box = gtk.VBox(False, 10)
347 self.hbox.pack_start(self.labels_box, True, True, 0)
348 self.labels_box.show()
351 self.preferred_box = gtk.HBox(False, 10)
352 self.settings_box.pack_start(self.preferred_box, True, True, 0)
353 self.preferred_box.show()
355 self.preferred_label = gtk.Label(_('I prefer'))
356 self.preferred_label.set_line_wrap(True)
357 self.preferred_box.pack_start(self.preferred_label, True, True, 0)
358 self.preferred_label.show()
360 self.preferred_options = []
361 for i in self.common.available_versions:
362 self.preferred_options.append(self.common.available_versions[i])
363 self.preferred_options.sort()
365 self.preferred = gtk.combo_box_new_text()
366 for option in self.preferred_options:
367 self.preferred.append_text(option)
368 if self.common.settings['preferred'] in self.common.available_versions:
369 self.preferred.set_active( self.preferred_options.index(self.common.available_versions[self.common.settings['preferred']]) )
371 self.preferred.set_active(0)
372 self.preferred_box.pack_start(self.preferred, True, True, 0)
373 self.preferred.show()
376 self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
377 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
378 if self.common.settings['update_over_tor']:
379 self.tor_update_checkbox.set_active(True)
381 self.tor_update_checkbox.set_active(False)
382 self.tor_update_checkbox.show()
385 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
386 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
387 if self.common.settings['check_for_updates']:
388 self.update_checkbox.set_active(True)
390 self.update_checkbox.set_active(False)
391 self.update_checkbox.show()
394 if(self.common.settings['installed_version'][self.common.settings['preferred']]):
395 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version'][self.common.settings['preferred']]))
397 self.label1 = gtk.Label(_('Not installed'))
398 self.label1.set_line_wrap(True)
399 self.labels_box.pack_start(self.label1, True, True, 0)
402 if(self.common.settings['last_update_check_timestamp'] > 0):
403 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']))))
405 self.label1 = gtk.Label(_('Never checked for updates'))
406 self.label1.set_line_wrap(True)
407 self.labels_box.pack_start(self.label1, True, True, 0)
411 self.button_box = gtk.HButtonBox()
412 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
413 self.box.pack_start(self.button_box, True, True, 0)
414 self.button_box.show()
416 # save and launch button
417 save_launch_image = gtk.Image()
418 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
419 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
420 self.save_launch_button.set_image(save_launch_image)
421 self.save_launch_button.connect("clicked", self.save_launch, None)
422 self.button_box.add(self.save_launch_button)
423 self.save_launch_button.show()
425 # save and exit button
426 save_exit_image = gtk.Image()
427 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
428 self.save_exit_button = gtk.Button(_("Save & Exit"))
429 self.save_exit_button.set_image(save_exit_image)
430 self.save_exit_button.connect("clicked", self.save_exit, None)
431 self.button_box.add(self.save_exit_button)
432 self.save_exit_button.show()
435 cancel_image = gtk.Image()
436 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
437 self.cancel_button = gtk.Button(_("Cancel"))
438 self.cancel_button.set_image(cancel_image)
439 self.cancel_button.connect("clicked", self.destroy, None)
440 self.button_box.add(self.cancel_button)
441 self.cancel_button.show()
450 def save_launch(self, widget, data=None):
452 p = subprocess.Popen([self.common.paths['tbl_bin']])
456 def save_exit(self, widget, data=None):
462 # figure out the selected preferred option
464 selected = self.preferred_options[self.preferred.get_active()]
465 for i in self.common.available_versions:
466 if self.common.available_versions[i] == selected:
469 self.common.settings['preferred'] = preferred
472 self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
473 self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
476 self.common.save_settings()
479 def delete_event(self, widget, event, data=None):
481 def destroy(self, widget, data=None):
486 def __init__(self, common):
487 print _('Starting launcher dialog')
491 self.set_gui(None, '', [])
492 self.launch_gui = True
493 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
495 # is vidalia already running and we just need to open a new firefox?
496 if self.common.settings['installed_version']:
497 vidalia_pid = self.common.get_pid('./App/vidalia')
498 firefox_pid = self.common.get_pid(self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'])
500 if vidalia_pid and not firefox_pid:
501 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
502 self.common.bring_window_to_front(vidalia_pid)
503 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']])
505 elif vidalia_pid and firefox_pid:
506 print _('Vidalia and Firefox are already open, bringing them to focus')
508 # bring firefox to front, then vidalia
509 self.common.bring_window_to_front(firefox_pid)
510 self.common.bring_window_to_front(vidalia_pid)
514 check_for_updates = False
515 if self.common.settings['check_for_updates']:
516 check_for_updates = True
518 if not check_for_updates:
519 # how long was it since the last update check?
520 # 86400 seconds = 24 hours
521 current_timestamp = int(time.time())
522 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
523 check_for_updates = True
525 if check_for_updates:
527 print 'Checking for update'
528 self.set_gui('task', _("Checking for Tor Browser update."),
529 ['download_update_check',
532 # no need to check for update
533 print _('Checked for update within 24 hours, skipping')
534 self.start_launcher()
538 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
539 self.window.set_title(_("Tor Browser"))
540 self.window.set_icon_from_file(self.common.paths['icon_file'])
541 self.window.set_position(gtk.WIN_POS_CENTER)
542 self.window.set_border_width(10)
543 self.window.connect("delete_event", self.delete_event)
544 self.window.connect("destroy", self.destroy)
546 # build the rest of the UI
549 # download or run TBB
550 def start_launcher(self):
551 # is TBB already installed?
552 start = self.common.paths['tbb'][self.common.settings['preferred']]['start']
553 if os.path.isfile(start) and os.access(start, os.X_OK):
554 if self.common.settings['installed_version'][self.common.settings['preferred']] == self.common.settings['latest_version'][self.common.settings['preferred']]:
555 # current version of tbb is installed, launch it
557 self.launch_gui = False
558 elif self.common.settings['installed_version'][self.common.settings['preferred']] < self.common.settings['latest_version'][self.common.settings['preferred']]:
559 # there is a tbb upgrade available
560 self.set_gui('task', _("Your Tor Browser is out of date."),
561 ['download_tarball_sig',
567 # for some reason the installed tbb is newer than the current version?
568 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
572 # are the tarball and sig already downloaded?
573 if os.path.isfile(self.common.paths['tarball_file']) and os.path.isfile(self.common.paths['tarball_sig_file']):
574 # start the gui with verify
575 self.set_gui('task', _("Installing Tor Browser."),
582 self.set_gui('task', _("Downloading and installing Tor Browser."),
583 ['download_tarball_sig',
589 # there are different GUIs that might appear, this sets which one we want
590 def set_gui(self, gui, message, tasks, autostart=True):
592 self.gui_message = message
593 self.gui_tasks = tasks
595 self.gui_autostart = autostart
597 # set all gtk variables to False
599 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
604 self.progressbar = False
605 self.button_box = False
606 self.start_button = False
607 self.exit_button = False
609 # build the application's UI
613 self.box = gtk.VBox(False, 20)
614 self.window.add(self.box)
616 if self.gui == 'error':
618 self.label = gtk.Label( self.gui_message )
619 self.label.set_line_wrap(True)
620 self.box.pack_start(self.label, True, True, 0)
624 exit_image = gtk.Image()
625 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
626 self.exit_button = gtk.Button("Exit")
627 self.exit_button.set_image(exit_image)
628 self.exit_button.connect("clicked", self.destroy, None)
629 self.box.add(self.exit_button)
630 self.exit_button.show()
632 elif self.gui == 'task':
634 self.label = gtk.Label( self.gui_message )
635 self.label.set_line_wrap(True)
636 self.box.pack_start(self.label, True, True, 0)
640 self.progressbar = gtk.ProgressBar(adjustment=None)
641 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
642 self.progressbar.set_pulse_step(0.01)
643 self.box.pack_start(self.progressbar, True, True, 0)
646 self.button_box = gtk.HButtonBox()
647 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
648 self.box.pack_start(self.button_box, True, True, 0)
649 self.button_box.show()
652 start_image = gtk.Image()
653 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
654 self.start_button = gtk.Button(_("Start"))
655 self.start_button.set_image(start_image)
656 self.start_button.connect("clicked", self.start, None)
657 self.button_box.add(self.start_button)
658 if not self.gui_autostart:
659 self.start_button.show()
662 exit_image = gtk.Image()
663 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
664 self.exit_button = gtk.Button(_("Exit"))
665 self.exit_button.set_image(exit_image)
666 self.exit_button.connect("clicked", self.destroy, None)
667 self.button_box.add(self.exit_button)
668 self.exit_button.show()
673 if self.gui_autostart:
676 # start button clicked, begin tasks
677 def start(self, widget, data=None):
678 # disable the start button
679 if self.start_button:
680 self.start_button.set_sensitive(False)
682 # start running tasks
685 # run the next task in the task list
689 if self.gui_task_i >= len(self.gui_tasks):
693 task = self.gui_tasks[self.gui_task_i]
695 # get ready for the next task
698 if task == 'download_update_check':
699 print _('Downloading'), self.common.paths['update_check_url']
700 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
702 if task == 'attempt_update':
703 print _('Checking to see if update it needed')
704 self.attempt_update()
706 elif task == 'download_tarball':
707 print _('Downloading'), self.common.paths['tarball_url']
708 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
710 elif task == 'download_tarball_sig':
711 print _('Downloading'), self.common.paths['tarball_sig_url']
712 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
714 elif task == 'verify':
715 print _('Verifying signature')
718 elif task == 'extract':
719 print _('Extracting'), self.common.paths['tarball_filename']
723 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
726 elif task == 'start_over':
727 print _('Starting download over again')
730 def response_received(self, response):
731 class FileDownloader(Protocol):
732 def __init__(self, file, total, progress, done_cb):
736 self.progress = progress
737 self.all_done = done_cb
739 def dataReceived(self, bytes):
740 self.file.write(bytes)
741 self.so_far += len(bytes)
742 percent = float(self.so_far) / float(self.total)
743 self.progress.set_fraction(percent)
744 amount = float(self.so_far)
746 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
749 amount = amount / float(size)
752 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
754 def connectionLost(self, reason):
755 print _('Finished receiving body:'), reason.getErrorMessage()
756 self.all_done(reason)
758 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
759 response.deliverBody(dl)
761 def response_finished(self, msg):
762 if msg.check(ResponseDone):
763 self.file_download.close()
768 print "FINISHED", msg
769 ## FIXME handle errors
771 def download_error(self, f):
772 print _("Download error"), f
773 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
777 def download(self, name, url, path):
778 # initialize the progress bar
779 self.progressbar.set_fraction(0)
780 self.progressbar.set_text(_('Downloading {0}').format(name))
781 self.progressbar.show()
784 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
785 d = agent.request('GET', url,
786 Headers({'User-Agent': ['torbrowser-launcher']}),
789 self.file_download = open(path, 'w')
790 d.addCallback(self.response_received).addErrback(self.download_error)
792 if not reactor.running:
795 def attempt_update(self):
796 # load the update check file
798 versions = json.load(open(self.common.paths['update_check_file']))
802 # filter linux versions
804 for version in versions:
805 if str(version).find('-Linux') != -1:
806 valid_versions.append(str(version))
808 for version in valid_versions:
809 if version.find('alpha') != -1:
810 latest_alpha = version
811 valid_versions.remove(latest_alpha)
812 # find stable (whatever is left after alpha)
813 if len(valid_versions):
814 latest_stable = valid_versions[0]
816 if latest_stable or latest_alpha:
818 self.common.settings['latest_version']['stable'] = latest_stable[:-len('-Linux')]
820 self.common.settings['latest_version']['alpha'] = latest_alpha[:-len('-Linux')]
821 self.common.settings['last_update_check_timestamp'] = int(time.time())
822 self.common.settings['check_for_updates'] = False
823 self.common.save_settings()
824 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
825 self.start_launcher()
828 # failed to find the latest version
829 self.set_gui('error', _("Error checking for updates."), [], False)
832 # not a valid JSON object
833 self.set_gui('error', _("Error checking for updates."), [], False)
840 # initialize the progress bar
841 self.progressbar.set_fraction(0)
842 self.progressbar.set_text(_('Verifying Signature'))
843 self.progressbar.show()
845 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['tarball_sig_file']])
846 self.pulse_until_process_exits(p)
848 if p.returncode == 0:
851 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)
855 if not reactor.running:
859 # initialize the progress bar
860 self.progressbar.set_fraction(0)
861 self.progressbar.set_text(_('Installing'))
862 self.progressbar.show()
865 # make sure this file is a tarfile
866 if tarfile.is_tarfile(self.common.paths['tarball_file']):
867 tf = tarfile.open(self.common.paths['tarball_file'])
868 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
870 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
874 # installation is finished, so save installed_version
875 self.common.settings['installed_version'] = self.common.settings['latest_version']
876 self.common.save_settings()
880 def run(self, run_next_task = True):
881 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
885 # make the progress bar pulse until process p (a Popen object) finishes
886 def pulse_until_process_exits(self, p):
887 while p.poll() == None:
889 self.progressbar.pulse()
892 # start over and download TBB again
893 def start_over(self):
894 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
895 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
900 def refresh_gtk(self):
901 while gtk.events_pending():
902 gtk.main_iteration(False)
905 def delete_event(self, widget, event, data=None):
907 def destroy(self, widget, data=None):
908 if hasattr(self, 'file_download'):
909 self.file_download.close()
913 if __name__ == "__main__":
914 tor_browser_launcher_version = '0.0.2'
916 print _('Tor Browser Launcher')
917 print _('By Micah Lee, licensed under GPLv3')
918 print _('version {0}').format(tor_browser_launcher_version)
919 print 'https://github.com/micahflee/torbrowser-launcher'
921 common = TBLCommon(tor_browser_launcher_version)
923 # is torbrowser-launcher already running?
924 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
926 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
927 common.bring_window_to_front(tbl_pid)
930 if '-settings' in sys.argv:
932 app = TBLSettings(common)
936 app = TBLLauncher(common)