From 880912bb2ca468d1a6a52dd6e9f318df3903f7ca Mon Sep 17 00:00:00 2001 From: Cecylia Bocovich Date: Mon, 13 Apr 2020 16:28:24 -0400 Subject: [PATCH] Remove all old upload scripts These have been replaced by newer scripts located in the top-level scripts/ directory. --- upload/bundles2drive.py | 315 ------------------------------ upload/bundles2dropbox.py | 150 -------------- upload/bundles2github.py | 158 --------------- upload/drive.cfg | 9 - upload/dropbox.cfg | 8 - upload/fetch_latest_torbrowser.py | 134 ------------- upload/github.cfg | 8 - upload/landing_gh.tpl | 137 ------------- upload/latest_torbrowser.cfg | 3 - upload/readme_gh.tpl | 40 ---- upload/torbrowser-key.asc | Bin 25381 -> 0 bytes 11 files changed, 962 deletions(-) delete mode 100644 upload/bundles2drive.py delete mode 100644 upload/bundles2dropbox.py delete mode 100644 upload/bundles2github.py delete mode 100644 upload/drive.cfg delete mode 100644 upload/dropbox.cfg delete mode 100644 upload/fetch_latest_torbrowser.py delete mode 100644 upload/github.cfg delete mode 100644 upload/landing_gh.tpl delete mode 100644 upload/latest_torbrowser.cfg delete mode 100644 upload/readme_gh.tpl delete mode 100644 upload/torbrowser-key.asc diff --git a/upload/bundles2drive.py b/upload/bundles2drive.py deleted file mode 100644 index 0acada5..0000000 --- a/upload/bundles2drive.py +++ /dev/null @@ -1,315 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: poly -# Israel Leiva -# see also AUTHORS file -# -# :copyright: (c) 2008-2014, The Tor Project, Inc. -# (c) 2014, Poly -# (c) 2014, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. - -import re -import os -import gnupg -import hashlib -import logging -import argparse -import ConfigParser -import gettor.core -from gettor.utils import get_bundle_info, get_file_sha256, valid_format - -# import google drive libs -import httplib2 -from apiclient.discovery import build -from apiclient.http import MediaFileUpload -from apiclient import errors -from oauth2client.client import FlowExchangeError -from oauth2client.client import OAuth2WebServerFlow -from oauth2client.client import Credentials - - -def upload_files(client, basedir): - """Upload files to Google Drive. - - Looks for tor browser files inside basedir. - - :param: basedir (string) path of the folder with the files to be - uploaded. - :param: client (object) Google Drive object. - - :raise: UploadError if something goes wrong while uploading the - files to Google Drive. All files are uploaded to '/'. - - :return: (dict) the names of the uploaded files as the keys, - and file id as the value - - """ - files = [] - - for name in os.listdir(basedir): - path = os.path.abspath(os.path.join(basedir, name)) - if os.path.isfile(path) and valid_format(name, 'linux'): - files.append(name) - - for name in os.listdir(basedir): - path = os.path.abspath(os.path.join(basedir, name)) - if os.path.isfile(path) and valid_format(name, 'windows'): - files.append(name) - - for name in os.listdir(basedir): - path = os.path.abspath(os.path.join(basedir, name)) - if os.path.isfile(path) and valid_format(name, 'osx'): - files.append(name) - - # dictionary to store file names and IDs - files_dict = dict() - - for file in files: - asc = "%s.asc" % file - abs_file = os.path.abspath(os.path.join(basedir, file)) - abs_asc = os.path.abspath(os.path.join(basedir, asc)) - - if not os.path.isfile(abs_asc): - # there are some .mar files that don't have .asc, don't upload it - continue - - # upload tor browser installer - file_body = MediaFileUpload( - abs_file, - mimetype="application/octet-stream", - resumable=True - ) - body = { - 'title': file - } - print "Uploading '%s'..." % file - try: - file_data = drive_service.files().insert( - body=body, - media_body=file_body - ).execute() - except errors.HttpError, e: - print str(e) - - # upload signature - asc_body = MediaFileUpload(abs_asc, resumable=True) - asc_head = { - 'title': "%s.asc" % file - } - print "Uploading '%s'..." % asc - try: - asc_data = drive_service.files().insert( - body=asc_head, - media_body=asc_body - ).execute() - except errors.HttpError, e: - print str(e) - - # add filenames and file id to dict - files_dict[file] = file_data['id'] - files_dict[asc] = asc_data['id'] - - return files_dict - - -def share_file(service, file_id): - """Make files public - - For a given file-id, sets role 'reader' to 'anyone'. Returns public - link to file. - - :param: file_id (string) - - :return: (string) url to shared file - - """ - permission = { - 'type': "anyone", - 'role': "reader", - 'withLink': True - } - - try: - service.permissions().insert( - fileId=file_id, - body=permission - ).execute() - except errors.HttpError, error: - print('An error occured while sharing: %s' % file_id) - - try: - file = service.files().get(fileId=file_id).execute() - except errors.HttpError, error: - print('Error occured while fetch public link for file: %s' % file_id) - - print "Uploaded %s to %s" % (file['title'], file['webContentLink']) - return file['webContentLink'] - - -def get_files_links(service, v): - """Print links of uploaded files. - - :param: service (object): Goolge Drive service object. - :param: v (string): Version of Tor Browser to look for. - - """ - - windows_re = 'torbrowser-install-%s_\w\w(-\w\w)?\.exe(\.asc)?' % v - linux_re = 'tor-browser-linux\d\d-%s_(\w\w)(-\w\w)?\.tar\.xz(\.asc)?' % v - osx_re = 'TorBrowser-%s-osx\d\d_(\w\w)(-\w\w)?\.dmg(\.asc)?' % v - - # dictionary to store file names and IDs - files_dict = dict() - - print "Trying to fetch links of uploaded files..." - links = service.files().list().execute() - items = links.get('items', []) - - if not items: - raise ValueError('No files found.') - - else: - for item in items: - if re.search(windows_re, item['title']): - files_dict[item['title']] = item['id'] - elif re.search(linux_re, item['title']): - files_dict[item['title']] = item['id'] - elif re.search(osx_re, item['title']): - files_dict[item['title']] = item['id'] - return files_dict - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Utility to upload Tor Browser to Google Drive.' - ) - - # if no LC specified, download all - parser.add_argument( - '-l', '--links', default=None, - help='Create links file with files already uploaded and '\ - 'matching the specified version. ' - ) - - args = parser.parse_args() - - config = ConfigParser.ConfigParser() - config.read('drive.cfg') - - client_id = config.get('app', 'client-id') - app_secret = config.get('app', 'secret') - refresh_token = config.get('app', 'refresh_token') - upload_dir = config.get('general', 'upload_dir') - - # important: this key must be the one that signed the packages - tbb_key = config.get('general', 'tbb_key') - - # requests full access to drive account - OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive' - REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' - - print "Authenticating..." - - flow = OAuth2WebServerFlow( - client_id, - app_secret, - OAUTH_SCOPE, - redirect_uri=REDIRECT_URI - ) - - # If no valid token found, need to prompt user. - # this should only occur once - if not refresh_token: - flow.params['access_type'] = 'offline' - flow.params['approval_prompt'] = 'force' - authorize_url = flow.step1_get_authorize_url() - print 'Go to the following link in your browser: ' + authorize_url - code = raw_input('Enter verification code: ').strip() - try: - credentials = flow.step2_exchange(code) - except FlowExchangeError as e: - print str(e) - - # oauth2 credentials instance must be stored as json string - config.set('app', 'refresh_token', credentials.to_json()) - with open('drive.cfg', 'wb') as configfile: - config.write(configfile) - else: - # we already have a valid token - credentials = Credentials.new_from_json(refresh_token) - - # authenticate with oauth2 - http = httplib2.Http() - http = credentials.authorize(http) - - # initialize drive instance - drive_service = build('drive', 'v2', http=http) - - # import key fingerprint - gpg = gnupg.GPG() - key_data = open(tbb_key).read() - import_result = gpg.import_keys(key_data) - fp = import_result.results[0]['fingerprint'] - - # make groups of four characters to make fingerprint more readable - # e.g. 123A 456B 789C 012D 345E 678F 901G 234H 567I 890J - readable = ' '.join(fp[i:i+4] for i in xrange(0, len(fp), 4)) - - try: - # helpful when something fails but files are uploaded. - if args.links: - uploaded_files = get_files_links(drive_service, args.links) - - if not uploaded_files: - raise ValueError("There are no files for that version") - else: - uploaded_files = upload_files(drive_service, upload_dir) - # use default config - core = gettor.core.Core('/home/gettor/core.cfg') - - # erase old links - core.create_links_file('Drive', readable) - - # recognize file OS by its extension - p1 = re.compile('.*\.tar.xz$') - p2 = re.compile('.*\.exe$') - p3 = re.compile('.*\.dmg$') - p4 = re.compile('.*\.asc$') - - for file in uploaded_files.keys(): - # only run for tor browser installers - if p4.match(file): - continue - asc = "%s.asc" % file - abs_file = os.path.abspath(os.path.join(upload_dir, file)) - abs_asc = os.path.abspath(os.path.join(upload_dir, asc)) - - sha_file = get_file_sha256(abs_file) - - # build links - link_file = share_file( - drive_service, - uploaded_files[file] - ) - - link_asc = share_file( - drive_service, - uploaded_files["%s.asc" % file] - ) - - if p1.match(file): - osys, arch, lc = get_bundle_info(file, 'linux') - elif p2.match(file): - osys, arch, lc = get_bundle_info(file, 'windows') - elif p3.match(file): - osys, arch, lc = get_bundle_info(file, 'osx') - - link = "%s$%s$%s$" % (link_file, link_asc, sha_file) - - # note that you should only upload bundles for supported locales - core.add_link('Drive', osys, lc, link) - except (ValueError, RuntimeError) as e: - print str(e) diff --git a/upload/bundles2dropbox.py b/upload/bundles2dropbox.py deleted file mode 100644 index 04e86af..0000000 --- a/upload/bundles2dropbox.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: Israel Leiva -# see also AUTHORS file -# -# :copyright: (c) 2008-2014, The Tor Project, Inc. -# (c) 2014, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. - -import re -import os -import gnupg -import hashlib -import ConfigParser - -import dropbox -import gettor.core -from gettor.utils import get_bundle_info, get_file_sha256, valid_format - - -def upload_files(basedir, client): - """Upload files to Dropbox. - - Looks for files ending with 'tar.xz' inside basedir. - - :param: basedir (string) path of the folder with the files to be - uploaded. - :param: client (object) DropboxClient object. - - :raise: ValueError if the .xz file doesn't have an .asc file. - :raise: UploadError if something goes wrong while uploading the - files to Dropbox. All files are uploaded to '/'. - - :return: (list) the names of the uploaded files. - - """ - files = [] - - for name in os.listdir(basedir): - path = os.path.abspath(os.path.join(basedir, name)) - if os.path.isfile(path) and valid_format(name, 'linux'): - files.append(name) - - for name in os.listdir(basedir): - path = os.path.abspath(os.path.join(basedir, name)) - if os.path.isfile(path) and valid_format(name, 'windows'): - files.append(name) - - for name in os.listdir(basedir): - path = os.path.abspath(os.path.join(basedir, name)) - if os.path.isfile(path) and valid_format(name, 'osx'): - files.append(name) - - for file in files: - asc = "%s.asc" % file - abs_file = os.path.abspath(os.path.join(basedir, file)) - abs_asc = os.path.abspath(os.path.join(basedir, asc)) - - if not os.path.isfile(abs_asc): - # there are some .mar files that don't have .asc, don't upload it - continue - - # chunk upload for big files - to_upload = open(abs_file, 'rb') - size = os.path.getsize(abs_file) - uploader = client.get_chunked_uploader(to_upload, size) - while uploader.offset < size: - try: - upload = uploader.upload_chunked() - except dropbox.rest.ErrorResponse, e: - print("An error ocurred while uploading %s: %s" % abs_file, e) - uploader.finish(file) - print "Uploading %s" % file - - # this should be small, upload it simple - to_upload_asc = open(abs_asc, 'rb') - response = client.put_file(asc, to_upload_asc) - print "Uploading %s" % asc - - return files - -if __name__ == '__main__': - config = ConfigParser.ConfigParser() - config.read('dropbox.cfg') - - app_key = config.get('app', 'key') - app_secret = config.get('app', 'secret') - access_token = config.get('app', 'access_token') - upload_dir = config.get('general', 'upload_dir') - - # important: this key must be the one that signed the packages - tbb_key = config.get('general', 'tbb_key') - - client = dropbox.client.DropboxClient(access_token) - - # import key fingerprint - gpg = gnupg.GPG() - key_data = open(tbb_key).read() - import_result = gpg.import_keys(key_data) - fp = import_result.results[0]['fingerprint'] - - # make groups of four characters to make fingerprint more readable - # e.g. 123A 456B 789C 012D 345E 678F 901G 234H 567I 890J - readable = ' '.join(fp[i:i+4] for i in xrange(0, len(fp), 4)) - - try: - uploaded_files = upload_files(upload_dir, client) - # use default config - core = gettor.core.Core('/home/gettor/core.cfg') - - # erase old links - core.create_links_file('Dropbox', readable) - - # recognize file OS by its extension - p1 = re.compile('.*\.tar.xz$') - p2 = re.compile('.*\.exe$') - p3 = re.compile('.*\.dmg$') - - for file in uploaded_files: - # build file names - asc = "%s.asc" % file - abs_file = os.path.abspath(os.path.join(upload_dir, file)) - abs_asc = os.path.abspath(os.path.join(upload_dir, asc)) - - sha_file = get_file_sha256(abs_file) - - # build links - link_file = client.share(file, short_url=False) - # if someone finds how to do this with the API, please tell me! - link_file[u'url'] = link_file[u'url'].replace('?dl=0', '?dl=1') - link_asc = client.share(asc, short_url=False) - link_asc[u'url'] = link_asc[u'url'].replace('?dl=0', '?dl=1') - if p1.match(file): - osys, arch, lc = get_bundle_info(file, 'linux') - elif p2.match(file): - osys, arch, lc = get_bundle_info(file, 'windows') - elif p3.match(file): - osys, arch, lc = get_bundle_info(file, 'osx') - - link = "%s$%s$%s$" % (link_file[u'url'], link_asc[u'url'], sha_file) - - # note that you should only upload bundles for supported locales - core.add_link('Dropbox', osys, lc, link) - except (ValueError, RuntimeError) as e: - print str(e) - except dropbox.rest.ErrorResponse as e: - print str(e) diff --git a/upload/bundles2github.py b/upload/bundles2github.py deleted file mode 100644 index ee157f0..0000000 --- a/upload/bundles2github.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: Israel Leiva -# see also AUTHORS file -# -# :copyright: (c) 2015, The Tor Project, Inc. -# (c) 2015, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. -# - -# Use pyopenssl to verify TLS certifcates -try: - import urllib3.contrib.pyopenssl - urllib3.contrib.pyopenssl.inject_into_urllib3() -except ImportError: - pass - -import os -import sys -import argparse -import ConfigParser -import gnupg - -import github3 - -import gettor.core -from gettor.utils import (get_bundle_info, get_file_sha256, - find_files_to_upload) - - -def upload_new_release(github_repo, version, upload_dir): - """ - Returns a Release object - """ - - # Create a new release of this TBB - release = target_repo.create_release( - 'v{}'.format(version), - target_commitish="master", - name='Tor Browser {}'.format(version), - body='', - draft=True, - ) - - for filename in find_files_to_upload(upload_dir): - # Upload each file for this release - file_path = os.path.join(upload_dir, filename) - print("Uploading file {}".format(filename)) - release.upload_asset('application/zip', - filename, open(file_path, 'rb')) - - return release - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Utility to upload Tor Browser to Github.' - ) - - # with this we only get the links of files already uploaded - # useful when somethings fail after uploading - parser.add_argument( - '-l', '--links', default=None, - help='Create links file with files already uploaded.' - ) - - args = parser.parse_args() - - config = ConfigParser.ConfigParser() - config.read('github.cfg') - - tbb_version_path = config.get('general', 'version_cfg_path') - - tbb_version_config = ConfigParser.ConfigParser() - tbb_version_config.read(tbb_version_path) - version = tbb_version_config.get('version', 'current') - - # the token allow us to run this script without GitHub user/pass - github_access_token = config.get('app', 'access_token') - - # path to the fingerprint that signed the packages - tb_key = config.get('general', 'tbb_key_path') - - # path to the latest version of Tor Browser - tb_path = config.get('general', 'latest_path') - - # path to gettor code configuration - core_path = config.get('general', 'core_path') - - # user and repository where we upload Tor Browser - github_user = config.get('app', 'user') - github_repo = config.get('app', 'repo') - - gh = github3.login(token=github_access_token) - target_repo = gh.repository(github_user, github_repo) - - # import key fingerprint - gpg = gnupg.GPG() - key_data = open(tb_key).read() - import_result = gpg.import_keys(key_data) - fp = import_result.results[0]['fingerprint'] - - # make groups of four characters to make fingerprint more readable - # e.g. 123A 456B 789C 012D 345E 678F 901G 234H 567I 890J - readable_fp = ' '.join(fp[i:i+4] for i in xrange(0, len(fp), 4)) - - # Find any published releases with this version number - for release in target_repo.releases(): - if release.tag_name == 'v{}'.format(version) and not release.draft: - print("Found an existing published release with this version. " - "Not uploading again unless you delete the published " - "release '{}'.".format(release.tag_name)) - break - else: - release = None - - if args.links or release: - # Only generating link file, should use previously published release - if not release: - print("Error occured! Could not find a published release for " - "version {}".format(version)) - sys.exit(1) - - else: - # Remove any drafts to clean broken uploads - print('Uploading release, please wait, this might take a while!') - # Upload the latest browser bundles to a new release - release = upload_new_release(target_repo, version, tb_path) - - # Upload success, publish the release - release.edit(draft=False) - - # Create the links file for this release - core = gettor.core.Core(core_path) - - # Erase old links if any and create a new empty one - core.create_links_file('GitHub', readable_fp) - - print("Creating links file") - for asset in release.assets(): - url = asset.browser_download_url - if url.endswith('.asc'): - continue - - osys, arch, lc = get_bundle_info(asset.name) - sha256 = get_file_sha256( - os.path.abspath(os.path.join(tb_path, asset.name)) - ) - - link = "{}${}${}$".format(url, url + ".asc", sha256) - - print("Adding {}".format(url)) - core.add_link('GitHub', osys, lc, link) - - print "Github links updated!" diff --git a/upload/drive.cfg b/upload/drive.cfg deleted file mode 100644 index a71bf57..0000000 --- a/upload/drive.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[general] -upload_dir = latest -tbb_key = torbrowser-key.asc - -[app] -client-id = -secret = -refresh_token = - diff --git a/upload/dropbox.cfg b/upload/dropbox.cfg deleted file mode 100644 index 7528565..0000000 --- a/upload/dropbox.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -upload_dir: latest -tbb_key: torbrowser-key.asc - -[app] -key: suchkey -secret: suchsecret -access_token: suchtoken diff --git a/upload/fetch_latest_torbrowser.py b/upload/fetch_latest_torbrowser.py deleted file mode 100644 index 3fe1f9a..0000000 --- a/upload/fetch_latest_torbrowser.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: Israel Leiva -# -# :copyright: (c) 2015, The Tor Project, Inc. -# (c) 2015, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. -# - -import os - -import urllib2 -import json -import argparse -import ConfigParser -import shutil - -# this path should be relative to this script (or absolute) -UPLOAD_SCRIPTS = { - 'dropbox': 'bundles2dropbox.py', - 'drive': 'bundles2drive.py' -} - -# "regex" for filtering downloads in wget -OS_RE = { - 'windows': '%s.exe,%s.exe.asc', - 'linux': '%s.tar.xz,%s.tar.xz.asc', - 'osx': '%s.dmg,%s.dmg.asc', -} - - -def main(): - """Script to fetch the latest Tor Browser. - - Fetch the latest version of Tor Browser and upload it to the supported - providers (e.g. Dropbox). Ideally, this script should be executed with - a cron in order to automate the updating of the files served by GetTor - when a new version of Tor Browser is released. - - Usage: python2.7 fetch.py --os= --lc= - - Some fetch examples: - - Fetch Tor Browser for all platforms and languages: - $ python2.7 fetch.py - - Fetch Tor Browser only for Linux: - $ python2.7 fetch.py --os=linux - - Fetch Tor Browser only for Windows and in US English: - $ python2.7 fetch.py --os=windows --lc=en-US - - Fetch Tor Browser for all platforms, but only in Spanish: - $ python2.7 fetch.py --lc=es-ES - - """ - parser = argparse.ArgumentParser( - description='Utility to fetch the latest Tor Browser and upload it \ - to popular cloud services.' - ) - - # if no OS specified, download all - parser.add_argument('-o', '--os', default=None, - help='filter by OS') - - # if no LC specified, download all - parser.add_argument('-l', '--lc', default='', - help='filter by locale') - - args = parser.parse_args() - - # server from which to download Tor Browser - dist_tpo = 'https://dist.torproject.org/torbrowser/' - - # find out the latest version - url = 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions' - response = urllib2.urlopen(url) - json_response = json.load(response) - latest_version = json_response[0] - - # find out the current version delivered by GetTor - config = ConfigParser.RawConfigParser() - config.read('latest_torbrowser.cfg') - current_version = config.get('version', 'current') - - if current_version != latest_version: - mirror = '%s%s/' % (dist_tpo, latest_version) - - # what LC should we download? - lc_re = args.lc - - # what OS should we download? - if args.os == 'windows': - os_re = OS_RE['windows'] % (lc_re, lc_re) - - elif args.os == 'osx': - os_re = OS_RE['osx'] % (lc_re, lc_re) - - elif args.os == 'linux': - os_re = OS_RE['linux'] % (lc_re, lc_re) - - else: - os_re = '%s.exe,%s.exe.asc,%s.dmg,%s.dmg.asc,%s.tar.xz,%s.tar'\ - '.xz.asc' % (lc_re, lc_re, lc_re, lc_re, lc_re, lc_re) - - params = "-nH --cut-dirs=1 -L 1 --accept %s" % os_re - - # in wget we trust - cmd = 'wget %s --mirror %s' % (params, mirror) - - print "Going to execute %s" % cmd - # make the mirror - # a folder with the value of 'latest_version' will be created - os.system(cmd) - # everything inside upload will be uploaded by the provivers' scripts - shutil.move('latest', 'latest_backup') - shutil.move(latest_version, 'latest') - shutil.rmtree('latest_backup') - - # latest version of Tor Browser has been syncronized - # let's upload it - for provider in UPLOAD_SCRIPTS: - os.system('python2.7 %s' % UPLOAD_SCRIPTS[provider]) - - # if everything is OK, update the current version delivered by GetTor - config.set('version', 'current', latest_version) - with open(r'latest_torbrowser.cfg', 'wb') as config_file: - config.write(config_file) - -if __name__ == "__main__": - main() diff --git a/upload/github.cfg b/upload/github.cfg deleted file mode 100644 index 7bdbd90..0000000 --- a/upload/github.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -upload_dir: latest -tbb_key: torbrowser-key.asc - -[app] -access_token: suchtoken -user: username -repo: gettor-front diff --git a/upload/landing_gh.tpl b/upload/landing_gh.tpl deleted file mode 100644 index 771756a..0000000 --- a/upload/landing_gh.tpl +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - GetTor | Download Tor Browser for Windows, Linux, OS X - - - - - - - - -
-
- -
-
-
- -
-
-

-

Download Tor Browser

-
- Below you will find links to download the latest version of Tor Browser (%TB_VERSION%). -

-
-
-
-
-
-

Direct downloads

- -
-
- -
-
- Tor Browser for Windows
- English, Farsi, Chinese, Turkish -
-
-
-
- -
-
- Tor Browser for OS X
- English, Farsi, Chinese, Turkish -
-
-
-
- -
-
- Tor Browser for Linux 32-bit
- English, Farsi, Chinese, Turkish -
-
-
-
- -
-
- Tor Browser for Linux 64-bit
- English, Farsi, Chinese, Turkish -
-
-
-
-

Alternative downloads

-
- Get links via Email: You can send an email to gettor@torproject.org. - Send the word help in the body of the message to learn how to interact with it. -
-
- Get links via XMPP: You can send a message to get_tor@riseup.net using your favorite XMPP client. Simply - enter help in an XMPP message to learn how to interact with it. -
-
- Get links via Twitter: You can send a direct message to @get_tor account - (you don't need to follow). Send the word help in a direct message to learn - how to interact with it. -
-

Get bridges

-
- Bridges are Tor relays that help you circumvent censorship. If you suspect your access to the - Tor network is being blocked, you may want to use bridges. You can get bridges from - the HTTP distributor. You can also send - an email to bridges@torproject.org - (please note that you must send the email using an address from one of the following email providers: riseup, gmail or yahoo). -
-
-
-
-
- -
- -
-

© The Tor Project 2016

-
- - - - - diff --git a/upload/latest_torbrowser.cfg b/upload/latest_torbrowser.cfg deleted file mode 100644 index 0abe3fb..0000000 --- a/upload/latest_torbrowser.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[version] -current = 4.0.3 - diff --git a/upload/readme_gh.tpl b/upload/readme_gh.tpl deleted file mode 100644 index 63ed23a..0000000 --- a/upload/readme_gh.tpl +++ /dev/null @@ -1,40 +0,0 @@ -# Download Tor Browser - -In this repository you will find links to download the latest version of -Tor Browser, which currently is %TB_VERSION%. Please select one of the links below: - -## Windows -[Download Tor Browser](%WINDOWS_EN%) (English) ([signature file](%WINDOWS_EN_SIG%)). - -[Download Tor Browser](%WINDOWS_FA%) (Farsi) ([signature file](%WINDOWS_FA_SIG%)). - -[Download Tor Browser](%WINDOWS_TR%) (Turkish) ([signature file](%WINDOWS_TR_SIG%)). - -[Download Tor Browser](%WINDOWS_ZH%) (Chinese) ([signature file](%WINDOWS_ZH_SIG%)). - -## OS X -[Download Tor Browser](%OSX_EN%) (English) ([signature file](%OSX_EN_SIG%)). - -[Download Tor Browser](%OSX_FA%) (Farsi) ([signature file](%OSX_FA_SIG%)). - -[Download Tor Browser](%OSX_TR%) (Turkish) ([signature file](%OSX_TR_SIG%)). - -[Download Tor Browser](%OSX_ZH%) (Chinese) ([signature file](%OSX_ZH_SIG%)). - -## Linux 32-bit -[Download Tor Browser](%LINUX32_EN%) (English) ([signature file](%LINUX32_EN_SIG%)). - -[Download Tor Browser](%LINUX32_FA%) (Farsi) ([signature file](%LINUX32_FA_SIG%)). - -[Download Tor Browser](%LINUX32_TR%) (Turkish) ([signature file](%LINUX32_TR_SIG%)). - -[Download Tor Browser](%LINUX32_ZH%) (Chinese) ([signature file](%LINUX32_ZH_SIG%)). - -## Linux 64-bit -[Download Tor Browser](%LINUX64_EN%) (English) ([signature file](%LINUX64_EN_SIG%)). - -[Download Tor Browser](%LINUX64_FA%) (Farsi) ([signature file](%LINUX64_FA_SIG%)). - -[Download Tor Browser](%LINUX64_TR%) (Turkish) ([signature file](%LINUX64_TR_SIG%)). - -[Download Tor Browser](%LINUX64_ZH%) (Chinese) ([signature file](%LINUX64_ZH_SIG%)). diff --git a/upload/torbrowser-key.asc b/upload/torbrowser-key.asc deleted file mode 100644 index 345f65dc140458b1eb89ab346a20480c95191533..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25381 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

-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 C-;=li -- GitLab