3 import os, sys, subprocess, locale, urllib2, gobject, time
9 class TorBrowserLauncher:
10 def __init__(self, current_tbb_version):
12 self.current_tbb_version = current_tbb_version
13 self.discover_arch_lang()
19 # is TBB already installed?
20 if os.path.isfile(self.paths['file']['start']) and os.access(self.paths['file']['start'], os.X_OK):
21 # does the version file exist?
22 if os.path.isfile(self.paths['file']['version']):
23 installed_tbb_version = open(self.paths['file']['version']).read().strip()
25 if installed_tbb_version == current_tbb_version:
26 # current version is tbb is installed, launch it
29 elif installed_tbb_version < self.current_tbb_version:
30 # there is a tbb upgrade available
31 self.set_gui('task', "Your Tor Browser is out of date.",
33 'download_tarball_sig',
38 # for some reason the installed tbb is newer than the current version?
39 self.set_gui('error', "Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?", [])
42 # if tbb is installed but the version file doesn't exist, something is wrong
43 self.set_gui('error', "Something is wrong. You have the Tor Browser Bundle installed, but the version file is missing.", [])
47 # save the current version to the file
48 open(self.paths['file']['version'], 'w').write(self.current_tbb_version)
50 # are the tarball and sig already downloaded?
51 if os.path.isfile(self.paths['file']['tarball']) and os.path.isfile(self.paths['file']['tarball_sig']):
52 # start the gui with verify
53 self.set_gui('task', "Installing Tor Browser.",
60 self.set_gui('task', "Downloading and installing Tor Browser.",
62 'download_tarball_sig',
71 # discover the architecture and language
72 def discover_arch_lang(self):
73 # figure out the architecture
74 (sysname, nodename, release, version, machine) = os.uname()
75 self.architecture = machine
77 # figure out the language
78 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
79 default_locale = locale.getdefaultlocale()[0]
80 if default_locale == None:
81 self.language = 'en-US'
83 self.language = default_locale.replace('_', '-')
84 if self.language not in available_languages:
85 self.language = self.language.split('-')[0]
86 if self.language not in available_languages:
87 for l in available_languages:
88 if l[0:2] == self.language:
90 # if language isn't available, default to english
91 if self.language not in available_languages:
92 self.language = 'en-US'
94 # build all relevant paths
95 def build_paths(self):
96 tbb_data = os.getenv('HOME')+'/.torbrowser'
97 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+self.current_tbb_version+'-dev-'+self.language+'.tar.gz'
102 'download': tbb_data+'/download',
103 'tbb': tbb_data+'/tbb/'+self.architecture,
104 'gpg': tbb_data+'/gpgtmp'
107 'version': tbb_data+'/version',
108 'start': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
109 'tarball': tbb_data+'/download/'+tarball_filename,
110 'tarball_sig': tbb_data+'/download/'+tarball_filename+'.asc',
111 'verify': '/usr/share/torbrowser-launcher/verify.sh'
114 'tarball': 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename,
115 'tarball_sig': 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
118 'tarball': tarball_filename,
119 'tarball_sig': tarball_filename+'.asc'
123 # create directories that don't exist
125 if os.path.exists(self.paths['dir']['download']) == False:
126 os.makedirs(self.paths['dir']['download'])
127 if os.path.exists(self.paths['dir']['tbb']) == False:
128 os.makedirs(self.paths['dir']['tbb'])
130 # there are different GUIs that might appear, this sets which one we want
131 def set_gui(self, gui, message, tasks, autostart=True):
133 self.gui_message = message
134 self.gui_tasks = tasks
135 self.gui_autostart = autostart
137 # build the application's UI
141 # allow buttons to have icons
143 settings = gtk.settings_get_default()
144 settings.props.gtk_button_images = True
149 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
150 self.window.set_title("Tor Browser")
151 self.window.set_position(gtk.WIN_POS_CENTER)
152 self.window.set_border_width(10)
153 self.window.connect("delete_event", self.delete_event)
154 self.window.connect("destroy", self.destroy)
156 self.box = gtk.VBox(False, 20)
157 self.window.add(self.box)
159 if self.gui == 'error':
161 self.label1 = gtk.Label( self.gui_message )
162 self.label1.set_line_wrap(True)
163 self.box.pack_start(self.label1, True, True, 0)
166 self.label2 = gtk.Label("You can fix the problem by deleting:\n"+self.paths['dir']['data']+"\n\nHowever, you will lose all your bookmarks and other Tor Browser preferences.")
167 self.label2.set_line_wrap(True)
168 self.box.pack_start(self.label2, True, True, 0)
172 exit_image = gtk.Image()
173 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
174 self.exit_button = gtk.Button("Exit")
175 self.exit_button.set_image(exit_image)
176 self.exit_button.connect("clicked", self.destroy, None)
177 self.box.add(self.exit_button)
178 self.exit_button.show()
180 elif self.gui == 'task':
182 self.label = gtk.Label( self.gui_message )
183 self.label.set_line_wrap(True)
184 self.box.pack_start(self.label, True, True, 0)
188 self.progressbar = gtk.ProgressBar(adjustment=None)
189 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
190 self.progressbar.set_pulse_step(0.01)
191 self.box.pack_start(self.progressbar, True, True, 0)
194 self.button_box = gtk.HButtonBox()
195 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
196 self.box.pack_start(self.button_box, True, True, 0)
197 self.button_box.show()
200 start_image = gtk.Image()
201 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
202 self.start_button = gtk.Button("Start")
203 self.start_button.set_image(start_image)
204 self.start_button.connect("clicked", self.start, None)
205 self.button_box.add(self.start_button)
206 if not self.gui_autostart:
207 self.start_button.show()
210 exit_image = gtk.Image()
211 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
212 self.exit_button = gtk.Button("Exit")
213 self.exit_button.set_image(exit_image)
214 self.exit_button.connect("clicked", self.destroy, None)
215 self.button_box.add(self.exit_button)
216 self.exit_button.show()
221 if self.gui_autostart:
224 # start button clicked, begin tasks
225 def start(self, widget, data=None):
226 # disable the start button
227 self.start_button.set_sensitive(False)
229 # start running tasks
233 # run the next task in the task list
235 if self.gui_task_i >= len(self.gui_tasks):
239 task = self.gui_tasks[self.gui_task_i]
241 # get ready for the next task
244 if task == 'download_tarball':
245 print 'Downloading '+self.paths['url']['tarball']
246 self.download('tarball', self.paths['url']['tarball'], self.paths['file']['tarball'])
248 elif task == 'download_tarball_sig':
249 print 'Downloading '+self.paths['url']['tarball_sig']
250 self.download('signature', self.paths['url']['tarball_sig'], self.paths['file']['tarball_sig'])
252 elif task == 'verify':
253 print 'Verifying signature'
256 elif task == 'extract':
257 print 'Extracting '+self.paths['filename']['tarball']
261 print 'Running '+self.paths['file']['start']
264 elif task == 'start_over':
265 print 'Starting download over again'
269 def download(self, name, url, path):
270 # initialize the progress bar
271 self.progressbar.set_fraction(0)
272 self.progressbar.set_text('Downloading '+name)
273 self.progressbar.show()
277 self.dl_response = urllib2.urlopen(url)
278 self.dl_total_size = self.dl_response.info().getheader('Content-Length').strip()
279 self.dl_total_size = int(self.dl_total_size)
280 self.dl_bytes_so_far = 0
282 # set a timer to download more chunks
283 self.timer = gobject.timeout_add(1, self.download_chunk, name)
285 # open a file to write to
286 self.file_download = open(path, 'w')
288 def download_chunk(self, name):
289 # download 10kb a time
290 chunk = self.dl_response.read(10240)
291 self.dl_bytes_so_far += len(chunk)
292 self.file_download.write(chunk)
295 self.file_download.close()
300 percent = float(self.dl_bytes_so_far) / self.dl_total_size
301 self.progressbar.set_fraction(percent)
302 percent = round(percent*100, 2)
303 self.progressbar.set_text("Downloaded %d%% of %s" % (percent, name))
306 sys.stdout.write("Downloaded %d of %d bytes (%0.2f%%)\r" % (self.dl_bytes_so_far, self.dl_total_size, percent))
308 if self.dl_bytes_so_far >= self.dl_total_size:
309 sys.stdout.write('\n')
314 # initialize the progress bar
315 self.progressbar.set_fraction(0)
316 self.progressbar.set_text('Verifying Signature')
317 self.progressbar.show()
319 p = subprocess.Popen([self.paths['file']['verify'], self.paths['dir']['gpg'], self.paths['file']['tarball_sig']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
320 self.pulse_until_process_exits(p)
322 output = p.stdout.read()
324 if 'Good signature' in output:
327 self.progressbar.hide()
328 self.label.set_text("SIGNATURE VERIFICATION FAILED!\n\nYou might be under attack, or there might just be a networking problem. Click Start try the download again.")
329 self.gui_tasks = ['start_over']
331 self.start_button.show()
332 self.start_button.set_sensitive(True)
335 # initialize the progress bar
336 self.progressbar.set_fraction(0)
337 self.progressbar.set_text('Installing')
338 self.progressbar.show()
340 p = subprocess.Popen(['tar', '-xf', self.paths['file']['tarball'], '-C', self.paths['dir']['tbb']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
341 self.pulse_until_process_exits(p)
345 def run(self, run_next_task = True):
346 subprocess.Popen([self.paths['file']['start']])
350 # make the progress bar pulse until process p (a Popen object) finishes
351 def pulse_until_process_exits(self, p):
352 while p.poll() == None:
354 self.progressbar.pulse()
357 # start over and download TBB again
358 def start_over(self):
359 self.label.set_text("Downloading Tor Browser Bundle over again.")
360 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
365 def refresh_gtk(self):
366 while gtk.events_pending():
367 gtk.main_iteration(False)
370 def delete_event(self, widget, event, data=None):
372 def destroy(self, widget, data=None):
374 gobject.source_remove(self.timer)
379 if __name__ == "__main__":
380 current_tbl_version = '0.1'
381 current_tbb_version = '2.3.25-2'
383 print 'Tor Browser Launcher'
384 print 'version %s' % (current_tbl_version)
385 print 'https://github.com/micahflee/torbrowser-launcher'
387 app = TorBrowserLauncher(current_tbb_version)