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 'stable': _('Tor Browser Bundle - stable'),
82 'alpha': _('Tor Browser Bundle - alpha')
83 # commenting out obs bundle until I have a reliable way to find latest version. related bugs:
84 # https://trac.torproject.org/projects/tor/ticket/8644
85 # https://trac.torproject.org/projects/tor/ticket/8645
86 #'obs': _('Obsfproxy Tor Browser Bundle')
89 # allow buttons to have icons
91 gtk_settings = gtk.settings_get_default()
92 gtk_settings.props.gtk_button_images = True
96 # discover the architecture and language
97 def discover_arch_lang(self):
98 # figure out the architecture
99 (sysname, nodename, release, version, machine) = os.uname()
100 self.architecture = machine
102 # figure out the language
103 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
104 default_locale = locale.getdefaultlocale()[0]
105 if default_locale == None:
106 self.language = 'en-US'
108 self.language = default_locale.replace('_', '-')
109 if self.language not in available_languages:
110 self.language = self.language.split('-')[0]
111 if self.language not in available_languages:
112 for l in available_languages:
113 if l[0:2] == self.language:
115 # if language isn't available, default to english
116 if self.language not in available_languages:
117 self.language = 'en-US'
119 # build all relevant paths
120 def build_paths(self, tbb_version = None):
121 homedir = os.getenv('HOME')
123 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
124 if os.path.exists(homedir) == False:
126 os.mkdir(homedir, 0700)
128 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
129 if not os.access(homedir, os.W_OK):
130 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
132 tbb_data = '%s/.torbrowser' % homedir
135 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
136 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
137 self.paths['tarball_sig_file'] = tbb_data+'/download/'+tarball_filename+'.asc'
138 self.paths['tarball_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
139 self.paths['tarball_sig_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
140 self.paths['tarball_filename'] = tarball_filename
141 self.paths['tarball_sig_filename'] = tarball_filename+'.asc'
145 'tbl_bin': '/usr/bin/torbrowser-launcher',
146 'icon_file': '/usr/share/pixmaps/torbrowser80.xpm',
147 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
148 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
149 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
150 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc',
151 'data_dir': tbb_data,
152 'download_dir': tbb_data+'/download',
153 'gnupg_homedir': tbb_data+'/gnupg_homedir',
154 'settings_file': tbb_data+'/settings',
155 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
156 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
159 'dir': tbb_data+'/tbb/stable/'+self.architecture,
160 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
161 'vidalia_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
162 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
163 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
166 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
167 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
168 'vidalia_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
169 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
170 'firefox_profile': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
173 'dir': tbb_data+'/tbb/obs/'+self.architecture,
174 'start': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
175 'vidalia_bin': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
176 'firefox_bin': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
177 'firefox_profile': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
183 def mkdir(self, path):
185 if os.path.exists(path) == False:
186 os.makedirs(path, 0700)
189 print _("Cannot create directory {0}").format(path)
191 if not os.access(path, os.W_OK):
192 print _("{0} is not writable").format(path)
196 # if gnupg_homedir isn't set up, set it up
197 def init_gnupg(self):
198 if not os.path.exists(self.paths['gnupg_homedir']):
199 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
200 if self.mkdir(self.paths['gnupg_homedir']):
202 print _('Importing keys')
203 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
204 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['sebastian_key']]).wait()
205 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['alexandre_key']]).wait()
208 def load_settings(self):
210 'tbl_version': self.tbl_version,
211 'preferred': 'stable',
212 'installed_version': {
222 'update_over_tor': True,
223 'check_for_updates': False,
224 'last_update_check_timestamp': 0
227 if os.path.isfile(self.paths['settings_file']):
228 settings = pickle.load(open(self.paths['settings_file']))
230 # what version settings is this?
231 if not 'tbl_version' in settings:
232 settings['tbl_version'] = '0.0.1'
234 # sanity checks for current version
235 if settings['tbl_version'] == self.tbl_version:
237 if not 'preferred' in settings:
238 good_settings = False
239 if not 'installed_version' in settings:
240 good_settings = False
241 if not 'stable' in settings['installed_version']:
242 good_settings = False
243 if not 'alpha' in settings['installed_version']:
244 good_settings = False
245 if not 'obs' in settings['installed_version']:
246 good_settings = False
247 if not 'latest_version' in settings:
248 good_settings = False
249 if not 'stable' in settings['latest_version']:
250 good_settings = False
251 if not 'alpha' in settings['latest_version']:
252 good_settings = False
253 if not 'obs' in settings['latest_version']:
254 good_settings = False
255 if not 'update_over_tor' in settings:
256 good_settings = False
257 if not 'check_for_updates' in settings:
258 good_settings = False
259 if not 'last_update_check_timestamp' in settings:
260 good_settings = False
263 self.settings = settings
265 self.settings = default_settings
267 # settings migrations for previous versions
268 elif settings['tbl_version'] == '0.0.1':
269 self.settings = default_settings
270 self.settings['installed_version']['alpha'] = settings['installed_version']
274 self.mkdir(self.paths['tbb']['alpha']['dir'])
275 # todo: move already-installed TBB alpha to new location
276 if os.path.exists(self.paths['data_dir']+'/tbb/x86_64'):
278 if os.path.exists(self.paths['data_dir']+'/tbb/i686'):
282 self.settings = default_settings
286 def save_settings(self):
287 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
290 # get the process id of a program
291 def get_pid(self, bin_path, python = False):
294 for p in psutil.process_iter():
296 if p.pid != os.getpid():
299 if len(p.cmdline) > 1:
300 if 'python' in p.cmdline[0]:
303 if len(p.cmdline) > 0:
314 # bring program's x window to front
315 def bring_window_to_front(self, pid):
316 # figure out the window id
318 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
319 for line in p.stdout.readlines():
320 line_split = line.split()
321 cur_win_id = line_split[0]
322 cur_win_pid = int(line_split[2])
323 if cur_win_pid == pid:
328 subprocess.call(['wmctrl', '-i', '-a', win_id])
331 def __init__(self, common):
332 print _('Starting settings dialog')
336 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
337 self.window.set_title(_("Tor Browser Launcher Settings"))
338 self.window.set_icon_from_file(self.common.paths['icon_file'])
339 self.window.set_position(gtk.WIN_POS_CENTER)
340 self.window.set_border_width(10)
341 self.window.connect("delete_event", self.delete_event)
342 self.window.connect("destroy", self.destroy)
344 # build the rest of the UI
345 self.box = gtk.VBox(False, 10)
346 self.window.add(self.box)
349 self.hbox = gtk.HBox(False, 10)
350 self.box.pack_start(self.hbox, True, True, 0)
353 self.settings_box = gtk.VBox(False, 10)
354 self.hbox.pack_start(self.settings_box, True, True, 0)
355 self.settings_box.show()
357 self.labels_box = gtk.VBox(False, 10)
358 self.hbox.pack_start(self.labels_box, True, True, 0)
359 self.labels_box.show()
362 self.preferred_box = gtk.HBox(False, 10)
363 self.settings_box.pack_start(self.preferred_box, True, True, 0)
364 self.preferred_box.show()
366 self.preferred_label = gtk.Label(_('I prefer'))
367 self.preferred_label.set_line_wrap(True)
368 self.preferred_box.pack_start(self.preferred_label, True, True, 0)
369 self.preferred_label.show()
371 self.preferred_options = []
372 for i in self.common.available_versions:
373 self.preferred_options.append(self.common.available_versions[i])
374 self.preferred_options.sort()
376 self.preferred = gtk.combo_box_new_text()
377 for option in self.preferred_options:
378 self.preferred.append_text(option)
379 if self.common.settings['preferred'] in self.common.available_versions:
380 self.preferred.set_active( self.preferred_options.index(self.common.available_versions[self.common.settings['preferred']]) )
382 self.preferred.set_active(0)
383 self.preferred_box.pack_start(self.preferred, True, True, 0)
384 self.preferred.show()
387 self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
388 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
389 if self.common.settings['update_over_tor']:
390 self.tor_update_checkbox.set_active(True)
392 self.tor_update_checkbox.set_active(False)
393 self.tor_update_checkbox.show()
396 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
397 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
398 if self.common.settings['check_for_updates']:
399 self.update_checkbox.set_active(True)
401 self.update_checkbox.set_active(False)
402 self.update_checkbox.show()
405 if(self.common.settings['installed_version'][self.common.settings['preferred']]):
406 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version'][self.common.settings['preferred']]))
408 self.label1 = gtk.Label(_('Not installed'))
409 self.label1.set_line_wrap(True)
410 self.labels_box.pack_start(self.label1, True, True, 0)
413 if(self.common.settings['last_update_check_timestamp'] > 0):
414 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']))))
416 self.label1 = gtk.Label(_('Never checked for updates'))
417 self.label1.set_line_wrap(True)
418 self.labels_box.pack_start(self.label1, True, True, 0)
422 self.button_box = gtk.HButtonBox()
423 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
424 self.box.pack_start(self.button_box, True, True, 0)
425 self.button_box.show()
427 # save and launch button
428 save_launch_image = gtk.Image()
429 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
430 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
431 self.save_launch_button.set_image(save_launch_image)
432 self.save_launch_button.connect("clicked", self.save_launch, None)
433 self.button_box.add(self.save_launch_button)
434 self.save_launch_button.show()
436 # save and exit button
437 save_exit_image = gtk.Image()
438 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
439 self.save_exit_button = gtk.Button(_("Save & Exit"))
440 self.save_exit_button.set_image(save_exit_image)
441 self.save_exit_button.connect("clicked", self.save_exit, None)
442 self.button_box.add(self.save_exit_button)
443 self.save_exit_button.show()
446 cancel_image = gtk.Image()
447 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
448 self.cancel_button = gtk.Button(_("Cancel"))
449 self.cancel_button.set_image(cancel_image)
450 self.cancel_button.connect("clicked", self.destroy, None)
451 self.button_box.add(self.cancel_button)
452 self.cancel_button.show()
461 def save_launch(self, widget, data=None):
463 p = subprocess.Popen([self.common.paths['tbl_bin']])
467 def save_exit(self, widget, data=None):
473 # figure out the selected preferred option
475 selected = self.preferred_options[self.preferred.get_active()]
476 for i in self.common.available_versions:
477 if self.common.available_versions[i] == selected:
480 self.common.settings['preferred'] = preferred
483 self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
484 self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
487 self.common.save_settings()
490 def delete_event(self, widget, event, data=None):
492 def destroy(self, widget, data=None):
497 def __init__(self, common):
498 print _('Starting launcher dialog')
502 self.set_gui(None, '', [])
503 self.launch_gui = True
504 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
506 # is vidalia already running and we just need to open a new firefox?
507 if self.common.settings['installed_version']:
508 vidalia_pid = self.common.get_pid('./App/vidalia')
509 firefox_pid = self.common.get_pid(self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'])
511 if vidalia_pid and not firefox_pid:
512 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
513 self.common.bring_window_to_front(vidalia_pid)
514 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']])
516 elif vidalia_pid and firefox_pid:
517 print _('Vidalia and Firefox are already open, bringing them to focus')
519 # bring firefox to front, then vidalia
520 self.common.bring_window_to_front(firefox_pid)
521 self.common.bring_window_to_front(vidalia_pid)
525 check_for_updates = False
526 if self.common.settings['check_for_updates']:
527 check_for_updates = True
529 if not check_for_updates:
530 # how long was it since the last update check?
531 # 86400 seconds = 24 hours
532 current_timestamp = int(time.time())
533 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
534 check_for_updates = True
536 if check_for_updates:
538 print 'Checking for update'
539 self.set_gui('task', _("Checking for Tor Browser update."),
540 ['download_update_check',
543 # no need to check for update
544 print _('Checked for update within 24 hours, skipping')
545 self.start_launcher()
549 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
550 self.window.set_title(_("Tor Browser"))
551 self.window.set_icon_from_file(self.common.paths['icon_file'])
552 self.window.set_position(gtk.WIN_POS_CENTER)
553 self.window.set_border_width(10)
554 self.window.connect("delete_event", self.delete_event)
555 self.window.connect("destroy", self.destroy)
557 # build the rest of the UI
560 # download or run TBB
561 def start_launcher(self):
562 # is TBB already installed?
563 start = self.common.paths['tbb'][self.common.settings['preferred']]['start']
564 if os.path.isfile(start) and os.access(start, os.X_OK):
565 if self.common.settings['installed_version'][self.common.settings['preferred']] == self.common.settings['latest_version'][self.common.settings['preferred']]:
566 # current version of tbb is installed, launch it
568 self.launch_gui = False
569 elif self.common.settings['installed_version'][self.common.settings['preferred']] < self.common.settings['latest_version'][self.common.settings['preferred']]:
570 # there is a tbb upgrade available
571 self.set_gui('task', _("Your Tor Browser is out of date."),
573 'download_tarball_sig',
578 # for some reason the installed tbb is newer than the current version?
579 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
583 # are the tarball and sig already downloaded?
584 if os.path.isfile(self.common.paths['tarball_file']) and os.path.isfile(self.common.paths['tarball_sig_file']):
585 # start the gui with verify
586 self.set_gui('task', _("Installing Tor Browser."),
593 self.set_gui('task', _("Downloading and installing Tor Browser."),
595 'download_tarball_sig',
600 # there are different GUIs that might appear, this sets which one we want
601 def set_gui(self, gui, message, tasks, autostart=True):
603 self.gui_message = message
604 self.gui_tasks = tasks
606 self.gui_autostart = autostart
608 # set all gtk variables to False
610 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
615 self.progressbar = False
616 self.button_box = False
617 self.start_button = False
618 self.exit_button = False
620 # build the application's UI
624 self.box = gtk.VBox(False, 20)
625 self.window.add(self.box)
627 if self.gui == 'error':
629 self.label = gtk.Label( self.gui_message )
630 self.label.set_line_wrap(True)
631 self.box.pack_start(self.label, True, True, 0)
635 exit_image = gtk.Image()
636 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
637 self.exit_button = gtk.Button("Exit")
638 self.exit_button.set_image(exit_image)
639 self.exit_button.connect("clicked", self.destroy, None)
640 self.box.add(self.exit_button)
641 self.exit_button.show()
643 elif self.gui == 'task':
645 self.label = gtk.Label( self.gui_message )
646 self.label.set_line_wrap(True)
647 self.box.pack_start(self.label, True, True, 0)
651 self.progressbar = gtk.ProgressBar(adjustment=None)
652 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
653 self.progressbar.set_pulse_step(0.01)
654 self.box.pack_start(self.progressbar, True, True, 0)
657 self.button_box = gtk.HButtonBox()
658 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
659 self.box.pack_start(self.button_box, True, True, 0)
660 self.button_box.show()
663 start_image = gtk.Image()
664 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
665 self.start_button = gtk.Button(_("Start"))
666 self.start_button.set_image(start_image)
667 self.start_button.connect("clicked", self.start, None)
668 self.button_box.add(self.start_button)
669 if not self.gui_autostart:
670 self.start_button.show()
673 exit_image = gtk.Image()
674 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
675 self.exit_button = gtk.Button(_("Exit"))
676 self.exit_button.set_image(exit_image)
677 self.exit_button.connect("clicked", self.destroy, None)
678 self.button_box.add(self.exit_button)
679 self.exit_button.show()
684 if self.gui_autostart:
687 # start button clicked, begin tasks
688 def start(self, widget, data=None):
689 # disable the start button
690 if self.start_button:
691 self.start_button.set_sensitive(False)
693 # start running tasks
696 # run the next task in the task list
700 if self.gui_task_i >= len(self.gui_tasks):
704 task = self.gui_tasks[self.gui_task_i]
706 # get ready for the next task
709 if task == 'download_update_check':
710 print _('Downloading'), self.common.paths['update_check_url']
711 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
713 if task == 'attempt_update':
714 print _('Checking to see if update it needed')
715 self.attempt_update()
717 elif task == 'download_tarball':
718 print _('Downloading'), self.common.paths['tarball_url']
719 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
721 elif task == 'download_tarball_sig':
722 print _('Downloading'), self.common.paths['tarball_sig_url']
723 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
725 elif task == 'verify':
726 print _('Verifying signature')
729 elif task == 'extract':
730 print _('Extracting'), self.common.paths['tarball_filename']
734 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
737 elif task == 'start_over':
738 print _('Starting download over again')
741 def response_received(self, response):
742 class FileDownloader(Protocol):
743 def __init__(self, file, total, progress, done_cb):
747 self.progress = progress
748 self.all_done = done_cb
750 def dataReceived(self, bytes):
751 self.file.write(bytes)
752 self.so_far += len(bytes)
753 percent = float(self.so_far) / float(self.total)
754 self.progress.set_fraction(percent)
755 amount = float(self.so_far)
757 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
760 amount = amount / float(size)
763 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
765 def connectionLost(self, reason):
766 print _('Finished receiving body:'), reason.getErrorMessage()
767 self.all_done(reason)
769 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
770 response.deliverBody(dl)
772 def response_finished(self, msg):
773 if msg.check(ResponseDone):
774 self.file_download.close()
779 print "FINISHED", msg
780 ## FIXME handle errors
782 def download_error(self, f):
783 print _("Download error"), f
784 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
788 def download(self, name, url, path):
789 # initialize the progress bar
790 self.progressbar.set_fraction(0)
791 self.progressbar.set_text(_('Downloading {0}').format(name))
792 self.progressbar.show()
795 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
796 d = agent.request('GET', url,
797 Headers({'User-Agent': ['torbrowser-launcher']}),
800 self.file_download = open(path, 'w')
801 d.addCallback(self.response_received).addErrback(self.download_error)
803 if not reactor.running:
806 def attempt_update(self):
807 # load the update check file
809 versions = json.load(open(self.common.paths['update_check_file']))
814 # filter linux versions
816 for version in versions:
817 if str(version).find('-Linux') != -1:
818 valid_versions.append(str(version))
820 for version in valid_versions:
821 if version.find('alpha') != -1:
822 latest_alpha = version
823 valid_versions.remove(latest_alpha)
824 # find stable (whatever is left after alpha)
825 if len(valid_versions):
826 latest_stable = valid_versions[0]
828 if latest_stable or latest_alpha or latest_obs:
830 self.common.settings['latest_version']['stable'] = latest_stable[:-len('-Linux')]
832 self.common.settings['latest_version']['alpha'] = latest_alpha[:-len('-Linux')]
834 self.common.settings['latest_version']['obs'] = latest_obs[:-len('-Linux')]
835 self.common.settings['last_update_check_timestamp'] = int(time.time())
836 self.common.settings['check_for_updates'] = False
837 self.common.save_settings()
838 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
839 self.start_launcher()
842 # failed to find the latest version
843 self.set_gui('error', _("Error checking for updates."), [], False)
846 # not a valid JSON object
847 self.set_gui('error', _("Error checking for updates."), [], False)
854 # initialize the progress bar
855 self.progressbar.set_fraction(0)
856 self.progressbar.set_text(_('Verifying Signature'))
857 self.progressbar.show()
859 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['tarball_sig_file']])
860 self.pulse_until_process_exits(p)
862 if p.returncode == 0:
865 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)
869 if not reactor.running:
873 # initialize the progress bar
874 self.progressbar.set_fraction(0)
875 self.progressbar.set_text(_('Installing'))
876 self.progressbar.show()
879 # make sure this file is a tarfile
880 if tarfile.is_tarfile(self.common.paths['tarball_file']):
881 tf = tarfile.open(self.common.paths['tarball_file'])
882 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
884 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
888 # installation is finished, so save installed_version
889 self.common.settings['installed_version'] = self.common.settings['latest_version']
890 self.common.save_settings()
894 def run(self, run_next_task = True):
895 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
899 # make the progress bar pulse until process p (a Popen object) finishes
900 def pulse_until_process_exits(self, p):
901 while p.poll() == None:
903 self.progressbar.pulse()
906 # start over and download TBB again
907 def start_over(self):
908 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
909 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
914 def refresh_gtk(self):
915 while gtk.events_pending():
916 gtk.main_iteration(False)
919 def delete_event(self, widget, event, data=None):
921 def destroy(self, widget, data=None):
922 if hasattr(self, 'file_download'):
923 self.file_download.close()
927 if __name__ == "__main__":
928 tor_browser_launcher_version = '0.0.2'
930 print _('Tor Browser Launcher')
931 print _('By Micah Lee, licensed under GPLv3')
932 print _('version {0}').format(tor_browser_launcher_version)
933 print 'https://github.com/micahflee/torbrowser-launcher'
935 common = TBLCommon(tor_browser_launcher_version)
937 # is torbrowser-launcher already running?
938 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
940 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
941 common.bring_window_to_front(tbl_pid)
944 if '-settings' in sys.argv:
946 app = TBLSettings(common)
950 app = TBLLauncher(common)