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 TryStableException(Exception):
59 class DownloadErrorException(Exception):
62 class VerifyTorProjectCert(ClientContextFactory):
64 def __init__(self, torproject_pem):
65 self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
67 def getContext(self, host, port):
68 ctx = ClientContextFactory.getContext(self)
69 ctx.set_verify_depth(0)
70 ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
73 def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
74 return cert.digest('sha256') == self.torproject_ca.digest('sha256')
78 def __init__(self, tbl_version):
79 print _('Initializing Tor Browser Launcher')
80 self.tbl_version = tbl_version
83 self.discover_arch_lang()
85 self.mkdir(self.paths['data_dir'])
87 self.mkdir(self.paths['download_dir'])
88 self.mkdir(self.paths['tbb'][self.settings['preferred']]['dir'])
91 self.available_versions = {
92 'stable': _('Tor Browser Bundle - stable'),
93 'alpha': _('Tor Browser Bundle - alpha')
96 # allow buttons to have icons
98 gtk_settings = gtk.settings_get_default()
99 gtk_settings.props.gtk_button_images = True
103 # discover the architecture and language
104 def discover_arch_lang(self):
105 # figure out the architecture
106 (sysname, nodename, release, version, machine) = os.uname()
107 self.architecture = machine
109 # figure out the language
110 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
111 default_locale = locale.getdefaultlocale()[0]
112 if default_locale == None:
113 self.language = 'en-US'
115 self.language = default_locale.replace('_', '-')
116 if self.language not in available_languages:
117 self.language = self.language.split('-')[0]
118 if self.language not in available_languages:
119 for l in available_languages:
120 if l[0:2] == self.language:
122 # if language isn't available, default to english
123 if self.language not in available_languages:
124 self.language = 'en-US'
126 # build all relevant paths
127 def build_paths(self, tbb_version = None):
128 homedir = os.getenv('HOME')
130 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
131 if os.path.exists(homedir) == False:
133 os.mkdir(homedir, 0700)
135 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
136 if not os.access(homedir, os.W_OK):
137 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
139 tbb_data = '%s/.torbrowser' % homedir
142 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
143 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
144 self.paths['tarball_sig_file'] = tbb_data+'/download/'+tarball_filename+'.asc'
145 self.paths['tarball_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
146 self.paths['tarball_sig_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
147 self.paths['tarball_filename'] = tarball_filename
148 self.paths['tarball_sig_filename'] = tarball_filename+'.asc'
152 'tbl_bin': '/usr/bin/torbrowser-launcher',
153 'icon_file': '/usr/share/pixmaps/torbrowser80.xpm',
154 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
155 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
156 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
157 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc',
158 'data_dir': tbb_data,
159 'download_dir': tbb_data+'/download',
160 'gnupg_homedir': tbb_data+'/gnupg_homedir',
161 'settings_file': tbb_data+'/settings',
162 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
163 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
166 'dir': tbb_data+'/tbb/stable/'+self.architecture,
167 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
168 'vidalia_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
169 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
170 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
173 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
174 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
175 'vidalia_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
176 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
177 'firefox_profile': tbb_data+'/tbb/alpha/'+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': {
220 'update_over_tor': True,
221 'check_for_updates': False,
222 'last_update_check_timestamp': 0
225 if os.path.isfile(self.paths['settings_file']):
226 settings = pickle.load(open(self.paths['settings_file']))
228 # what version settings is this?
229 if not 'tbl_version' in settings:
230 settings['tbl_version'] = '0.0.1'
232 # sanity checks for current version
233 if settings['tbl_version'] == self.tbl_version:
235 if not 'preferred' in settings:
236 good_settings = False
237 if not 'installed_version' in settings:
238 good_settings = False
239 if not 'stable' in settings['installed_version']:
240 good_settings = False
241 if not 'alpha' in settings['installed_version']:
242 good_settings = False
243 if not 'latest_version' in settings:
244 good_settings = False
245 if not 'stable' in settings['latest_version']:
246 good_settings = False
247 if not 'alpha' in settings['latest_version']:
248 good_settings = False
249 if not 'update_over_tor' in settings:
250 good_settings = False
251 if not 'check_for_updates' in settings:
252 good_settings = False
253 if not 'last_update_check_timestamp' in settings:
254 good_settings = False
257 self.settings = settings
259 self.settings = default_settings
261 # settings migrations for previous versions
262 elif settings['tbl_version'] == '0.0.1':
263 self.settings = default_settings
264 self.settings['installed_version']['alpha'] = settings['installed_version']
268 self.mkdir(self.paths['tbb']['alpha']['dir'])
269 # todo: move already-installed TBB alpha to new location
270 if os.path.exists(self.paths['data_dir']+'/tbb/x86_64'):
272 if os.path.exists(self.paths['data_dir']+'/tbb/i686'):
276 self.settings = default_settings
280 def save_settings(self):
281 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
284 # get the process id of a program
285 def get_pid(self, bin_path, python = False):
288 for p in psutil.process_iter():
290 if p.pid != os.getpid():
293 if len(p.cmdline) > 1:
294 if 'python' in p.cmdline[0]:
297 if len(p.cmdline) > 0:
308 # bring program's x window to front
309 def bring_window_to_front(self, pid):
310 # figure out the window id
312 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
313 for line in p.stdout.readlines():
314 line_split = line.split()
315 cur_win_id = line_split[0]
316 cur_win_pid = int(line_split[2])
317 if cur_win_pid == pid:
322 subprocess.call(['wmctrl', '-i', '-a', win_id])
325 def __init__(self, common):
326 print _('Starting settings dialog')
330 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
331 self.window.set_title(_("Tor Browser Launcher Settings"))
332 self.window.set_icon_from_file(self.common.paths['icon_file'])
333 self.window.set_position(gtk.WIN_POS_CENTER)
334 self.window.set_border_width(10)
335 self.window.connect("delete_event", self.delete_event)
336 self.window.connect("destroy", self.destroy)
338 # build the rest of the UI
339 self.box = gtk.VBox(False, 10)
340 self.window.add(self.box)
343 self.hbox = gtk.HBox(False, 10)
344 self.box.pack_start(self.hbox, True, True, 0)
347 self.settings_box = gtk.VBox(False, 10)
348 self.hbox.pack_start(self.settings_box, True, True, 0)
349 self.settings_box.show()
351 self.labels_box = gtk.VBox(False, 10)
352 self.hbox.pack_start(self.labels_box, True, True, 0)
353 self.labels_box.show()
356 self.preferred_box = gtk.HBox(False, 10)
357 self.settings_box.pack_start(self.preferred_box, True, True, 0)
358 self.preferred_box.show()
360 self.preferred_label = gtk.Label(_('I prefer'))
361 self.preferred_label.set_line_wrap(True)
362 self.preferred_box.pack_start(self.preferred_label, True, True, 0)
363 self.preferred_label.show()
365 self.preferred_options = []
366 for i in self.common.available_versions:
367 self.preferred_options.append(self.common.available_versions[i])
368 self.preferred_options.sort()
370 self.preferred = gtk.combo_box_new_text()
371 for option in self.preferred_options:
372 self.preferred.append_text(option)
373 if self.common.settings['preferred'] in self.common.available_versions:
374 self.preferred.set_active( self.preferred_options.index(self.common.available_versions[self.common.settings['preferred']]) )
376 self.preferred.set_active(0)
377 self.preferred_box.pack_start(self.preferred, True, True, 0)
378 self.preferred.show()
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 if self.common.settings['update_over_tor']:
384 self.tor_update_checkbox.set_active(True)
386 self.tor_update_checkbox.set_active(False)
387 self.tor_update_checkbox.show()
390 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
391 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
392 if self.common.settings['check_for_updates']:
393 self.update_checkbox.set_active(True)
395 self.update_checkbox.set_active(False)
396 self.update_checkbox.show()
399 if(self.common.settings['installed_version'][self.common.settings['preferred']]):
400 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version'][self.common.settings['preferred']]))
402 self.label1 = gtk.Label(_('Not installed'))
403 self.label1.set_line_wrap(True)
404 self.labels_box.pack_start(self.label1, True, True, 0)
407 if(self.common.settings['last_update_check_timestamp'] > 0):
408 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']))))
410 self.label1 = gtk.Label(_('Never checked for updates'))
411 self.label1.set_line_wrap(True)
412 self.labels_box.pack_start(self.label1, True, True, 0)
416 self.button_box = gtk.HButtonBox()
417 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
418 self.box.pack_start(self.button_box, True, True, 0)
419 self.button_box.show()
421 # save and launch button
422 save_launch_image = gtk.Image()
423 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
424 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
425 self.save_launch_button.set_image(save_launch_image)
426 self.save_launch_button.connect("clicked", self.save_launch, None)
427 self.button_box.add(self.save_launch_button)
428 self.save_launch_button.show()
430 # save and exit button
431 save_exit_image = gtk.Image()
432 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
433 self.save_exit_button = gtk.Button(_("Save & Exit"))
434 self.save_exit_button.set_image(save_exit_image)
435 self.save_exit_button.connect("clicked", self.save_exit, None)
436 self.button_box.add(self.save_exit_button)
437 self.save_exit_button.show()
440 cancel_image = gtk.Image()
441 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
442 self.cancel_button = gtk.Button(_("Cancel"))
443 self.cancel_button.set_image(cancel_image)
444 self.cancel_button.connect("clicked", self.destroy, None)
445 self.button_box.add(self.cancel_button)
446 self.cancel_button.show()
455 def save_launch(self, widget, data=None):
457 p = subprocess.Popen([self.common.paths['tbl_bin']])
461 def save_exit(self, widget, data=None):
467 # figure out the selected preferred option
469 selected = self.preferred_options[self.preferred.get_active()]
470 for i in self.common.available_versions:
471 if self.common.available_versions[i] == selected:
474 self.common.settings['preferred'] = preferred
477 self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
478 self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
481 self.common.save_settings()
484 def delete_event(self, widget, event, data=None):
486 def destroy(self, widget, data=None):
491 def __init__(self, common):
492 print _('Starting launcher dialog')
496 self.set_gui(None, '', [])
497 self.launch_gui = True
498 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
500 # is vidalia already running and we just need to open a new firefox?
501 if self.common.settings['installed_version']:
502 vidalia_pid = self.common.get_pid('./App/vidalia')
503 firefox_pid = self.common.get_pid(self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'])
505 if vidalia_pid and not firefox_pid:
506 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
507 self.common.bring_window_to_front(vidalia_pid)
508 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']])
510 elif vidalia_pid and firefox_pid:
511 print _('Vidalia and Firefox are already open, bringing them to focus')
513 # bring firefox to front, then vidalia
514 self.common.bring_window_to_front(firefox_pid)
515 self.common.bring_window_to_front(vidalia_pid)
519 check_for_updates = False
520 if self.common.settings['check_for_updates']:
521 check_for_updates = True
523 if not check_for_updates:
524 # how long was it since the last update check?
525 # 86400 seconds = 24 hours
526 current_timestamp = int(time.time())
527 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
528 check_for_updates = True
530 if check_for_updates:
532 print 'Checking for update'
533 self.set_gui('task', _("Checking for Tor Browser update."),
534 ['download_update_check',
537 # no need to check for update
538 print _('Checked for update within 24 hours, skipping')
539 self.start_launcher()
543 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
544 self.window.set_title(_("Tor Browser"))
545 self.window.set_icon_from_file(self.common.paths['icon_file'])
546 self.window.set_position(gtk.WIN_POS_CENTER)
547 self.window.set_border_width(10)
548 self.window.connect("delete_event", self.delete_event)
549 self.window.connect("destroy", self.destroy)
551 # build the rest of the UI
554 # download or run TBB
555 def start_launcher(self):
556 # is TBB already installed?
557 start = self.common.paths['tbb'][self.common.settings['preferred']]['start']
558 if os.path.isfile(start) and os.access(start, os.X_OK):
559 if self.common.settings['installed_version'][self.common.settings['preferred']] == self.common.settings['latest_version'][self.common.settings['preferred']]:
560 # current version of tbb is installed, launch it
562 self.launch_gui = False
563 elif self.common.settings['installed_version'][self.common.settings['preferred']] < self.common.settings['latest_version'][self.common.settings['preferred']]:
564 # there is a tbb upgrade available
565 self.set_gui('task', _("Your Tor Browser is out of date."),
566 ['download_tarball_sig',
572 # for some reason the installed tbb is newer than the current version?
573 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
577 # are the tarball and sig already downloaded?
578 if os.path.isfile(self.common.paths['tarball_file']) and os.path.isfile(self.common.paths['tarball_sig_file']):
579 # start the gui with verify
580 self.set_gui('task', _("Installing Tor Browser."),
587 self.set_gui('task', _("Downloading and installing Tor Browser."),
588 ['download_tarball_sig',
594 # there are different GUIs that might appear, this sets which one we want
595 def set_gui(self, gui, message, tasks, autostart=True):
597 self.gui_message = message
598 self.gui_tasks = tasks
600 self.gui_autostart = autostart
602 # set all gtk variables to False
604 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
609 self.progressbar = False
610 self.button_box = False
611 self.start_button = False
612 self.exit_button = False
614 # build the application's UI
618 self.box = gtk.VBox(False, 20)
619 self.window.add(self.box)
621 if self.gui == 'error' or self.gui == 'error_try_stable':
623 self.label = gtk.Label( self.gui_message )
624 self.label.set_line_wrap(True)
625 self.box.pack_start(self.label, True, True, 0)
629 self.button_box = gtk.HButtonBox()
630 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
631 self.box.pack_start(self.button_box, True, True, 0)
632 self.button_box.show()
634 if self.gui == 'error_try_stable':
636 yes_image = gtk.Image()
637 yes_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
638 self.yes_button = gtk.Button("Yes")
639 self.yes_button.set_image(yes_image)
640 self.yes_button.connect("clicked", self.try_stable, None)
641 self.button_box.add(self.yes_button)
642 self.yes_button.show()
645 exit_image = gtk.Image()
646 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
647 self.exit_button = gtk.Button("Exit")
648 self.exit_button.set_image(exit_image)
649 self.exit_button.connect("clicked", self.destroy, None)
650 self.button_box.add(self.exit_button)
651 self.exit_button.show()
653 elif self.gui == 'task':
655 self.label = gtk.Label( self.gui_message )
656 self.label.set_line_wrap(True)
657 self.box.pack_start(self.label, True, True, 0)
661 self.progressbar = gtk.ProgressBar(adjustment=None)
662 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
663 self.progressbar.set_pulse_step(0.01)
664 self.box.pack_start(self.progressbar, True, True, 0)
667 self.button_box = gtk.HButtonBox()
668 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
669 self.box.pack_start(self.button_box, True, True, 0)
670 self.button_box.show()
673 start_image = gtk.Image()
674 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
675 self.start_button = gtk.Button(_("Start"))
676 self.start_button.set_image(start_image)
677 self.start_button.connect("clicked", self.start, None)
678 self.button_box.add(self.start_button)
679 if not self.gui_autostart:
680 self.start_button.show()
683 exit_image = gtk.Image()
684 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
685 self.exit_button = gtk.Button(_("Exit"))
686 self.exit_button.set_image(exit_image)
687 self.exit_button.connect("clicked", self.destroy, None)
688 self.button_box.add(self.exit_button)
689 self.exit_button.show()
694 if self.gui_autostart:
697 # start button clicked, begin tasks
698 def start(self, widget, data=None):
699 # disable the start button
700 if self.start_button:
701 self.start_button.set_sensitive(False)
703 # start running tasks
706 # run the next task in the task list
710 if self.gui_task_i >= len(self.gui_tasks):
714 task = self.gui_tasks[self.gui_task_i]
716 # get ready for the next task
719 if task == 'download_update_check':
720 print _('Downloading'), self.common.paths['update_check_url']
721 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
723 if task == 'attempt_update':
724 print _('Checking to see if update it needed')
725 self.attempt_update()
727 elif task == 'download_tarball':
728 print _('Downloading'), self.common.paths['tarball_url']
729 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
731 elif task == 'download_tarball_sig':
732 print _('Downloading'), self.common.paths['tarball_sig_url']
733 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
735 elif task == 'verify':
736 print _('Verifying signature')
739 elif task == 'extract':
740 print _('Extracting'), self.common.paths['tarball_filename']
744 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
747 elif task == 'start_over':
748 print _('Starting download over again')
751 def response_received(self, response):
752 class FileDownloader(Protocol):
753 def __init__(self, language, file, total, progress, done_cb):
757 self.progress = progress
758 self.all_done = done_cb
760 if response.code != 200:
762 if response.code == 404:
763 if common.settings['preferred'] == 'alpha' and language != 'en-US':
767 print 'about to raise TryStableException'
768 raise TryStableException(_("It looks like the alpha version of Tor Browser Bundle isn't available for your language. Would you like to try the stable version instead?"))
770 raise DownloadErrorException(_("Error with download: {0} {1}").format(response.code, response.phrase))
772 def dataReceived(self, bytes):
773 self.file.write(bytes)
774 self.so_far += len(bytes)
775 percent = float(self.so_far) / float(self.total)
776 self.progress.set_fraction(percent)
777 amount = float(self.so_far)
779 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
782 amount = amount / float(size)
785 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
787 def connectionLost(self, reason):
788 print _('Finished receiving body:'), reason.getErrorMessage()
789 self.all_done(reason)
791 dl = FileDownloader(self.common.language, self.file_download, response.length, self.progressbar, self.response_finished)
792 response.deliverBody(dl)
794 def response_finished(self, msg):
795 if msg.check(ResponseDone):
796 self.file_download.close()
801 print "FINISHED", msg
802 ## FIXME handle errors
804 def download_error(self, f):
805 print _("Download error:"), f.value
807 if isinstance(f.value, TryStableException):
808 f.trap(TryStableException)
809 self.set_gui('error_try_stable', str(f.value), [], False)
811 elif isinstance(f.value, DownloadErrorException):
812 f.trap(DownloadErrorException)
813 self.set_gui('error', str(f.value), [], False)
816 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
820 def download(self, name, url, path):
821 # initialize the progress bar
822 self.progressbar.set_fraction(0)
823 self.progressbar.set_text(_('Downloading {0}').format(name))
824 self.progressbar.show()
827 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
828 d = agent.request('GET', url,
829 Headers({'User-Agent': ['torbrowser-launcher']}),
832 self.file_download = open(path, 'w')
833 d.addCallback(self.response_received).addErrback(self.download_error)
835 if not reactor.running:
838 def try_stable(self, widget, data=None):
839 # change preferred to stable and relaunch TBL
840 self.common.settings['preferred'] = 'stable'
841 self.common.save_settings()
842 p = subprocess.Popen([self.common.paths['tbl_bin']])
845 def attempt_update(self):
846 # load the update check file
848 versions = json.load(open(self.common.paths['update_check_file']))
852 # filter linux versions
854 for version in versions:
855 if str(version).find('-Linux') != -1:
856 valid_versions.append(str(version))
858 for version in valid_versions:
859 if version.find('alpha') != -1:
860 latest_alpha = version
861 valid_versions.remove(latest_alpha)
862 # find stable (whatever is left after alpha)
863 if len(valid_versions):
864 latest_stable = valid_versions[0]
866 if latest_stable or latest_alpha:
868 self.common.settings['latest_version']['stable'] = latest_stable[:-len('-Linux')]
870 self.common.settings['latest_version']['alpha'] = latest_alpha[:-len('-Linux')]
871 self.common.settings['last_update_check_timestamp'] = int(time.time())
872 self.common.settings['check_for_updates'] = False
873 self.common.save_settings()
874 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
875 self.start_launcher()
878 # failed to find the latest version
879 self.set_gui('error', _("Error checking for updates."), [], False)
882 # not a valid JSON object
883 self.set_gui('error', _("Error checking for updates."), [], False)
890 # initialize the progress bar
891 self.progressbar.set_fraction(0)
892 self.progressbar.set_text(_('Verifying Signature'))
893 self.progressbar.show()
895 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['tarball_sig_file']])
896 self.pulse_until_process_exits(p)
898 if p.returncode == 0:
901 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)
905 if not reactor.running:
909 # initialize the progress bar
910 self.progressbar.set_fraction(0)
911 self.progressbar.set_text(_('Installing'))
912 self.progressbar.show()
915 # make sure this file is a tarfile
916 if tarfile.is_tarfile(self.common.paths['tarball_file']):
917 tf = tarfile.open(self.common.paths['tarball_file'])
918 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
920 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
924 # installation is finished, so save installed_version
925 self.common.settings['installed_version'] = self.common.settings['latest_version']
926 self.common.save_settings()
930 def run(self, run_next_task = True):
931 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
935 # make the progress bar pulse until process p (a Popen object) finishes
936 def pulse_until_process_exits(self, p):
937 while p.poll() == None:
939 self.progressbar.pulse()
942 # start over and download TBB again
943 def start_over(self):
944 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
945 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
950 def refresh_gtk(self):
951 while gtk.events_pending():
952 gtk.main_iteration(False)
955 def delete_event(self, widget, event, data=None):
957 def destroy(self, widget, data=None):
958 if hasattr(self, 'file_download'):
959 self.file_download.close()
963 if __name__ == "__main__":
964 tor_browser_launcher_version = '0.0.2'
966 print _('Tor Browser Launcher')
967 print _('By Micah Lee, licensed under GPLv3')
968 print _('version {0}').format(tor_browser_launcher_version)
969 print 'https://github.com/micahflee/torbrowser-launcher'
971 common = TBLCommon(tor_browser_launcher_version)
973 # is torbrowser-launcher already running?
974 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
976 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
977 common.bring_window_to_front(tbl_pid)
980 if '-settings' in sys.argv:
982 app = TBLSettings(common)
986 app = TBLLauncher(common)