mirror of
https://github.com/onionshare/onionshare.git
synced 2025-09-22 05:24:50 -04:00
Split the increasingly-sprawly onionshare module into different modules:
onionshare: the main business logic helpers: helper function used in multiple modules strings: handles all localized strings web: the flask web server
This commit is contained in:
parent
0a6d01f11e
commit
54a37ee28e
4 changed files with 319 additions and 314 deletions
66
onionshare/helpers.py
Normal file
66
onionshare/helpers.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import os, inspect, hashlib, base64, hmac, platform
|
||||||
|
from itertools import izip
|
||||||
|
|
||||||
|
def get_platform():
|
||||||
|
p = platform.system()
|
||||||
|
if p == 'Linux' and platform.uname()[0:2] == ('Linux', 'amnesia'):
|
||||||
|
p = 'Tails'
|
||||||
|
return p
|
||||||
|
|
||||||
|
def get_onionshare_dir():
|
||||||
|
if get_platform() == 'Darwin':
|
||||||
|
onionshare_dir = os.path.dirname(__file__)
|
||||||
|
else:
|
||||||
|
onionshare_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
|
return onionshare_dir
|
||||||
|
|
||||||
|
def constant_time_compare(val1, val2):
|
||||||
|
_builtin_constant_time_compare = getattr(hmac, 'compare_digest', None)
|
||||||
|
if _builtin_constant_time_compare is not None:
|
||||||
|
return _builtin_constant_time_compare(val1, val2)
|
||||||
|
|
||||||
|
len_eq = len(val1) == len(val2)
|
||||||
|
if len_eq:
|
||||||
|
result = 0
|
||||||
|
left = val1
|
||||||
|
else:
|
||||||
|
result = 1
|
||||||
|
left = val2
|
||||||
|
for x, y in izip(bytearray(left), bytearray(val2)):
|
||||||
|
result |= x ^ y
|
||||||
|
return result == 0
|
||||||
|
|
||||||
|
def random_string(num_bytes):
|
||||||
|
b = os.urandom(num_bytes)
|
||||||
|
h = hashlib.sha256(b).digest()[:16]
|
||||||
|
return base64.b32encode(h).lower().replace('=','')
|
||||||
|
|
||||||
|
def human_readable_filesize(b):
|
||||||
|
thresh = 1024.0
|
||||||
|
if b < thresh:
|
||||||
|
return '{0} B'.format(b)
|
||||||
|
units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']
|
||||||
|
u = 0
|
||||||
|
b /= thresh
|
||||||
|
while b >= thresh:
|
||||||
|
b /= thresh
|
||||||
|
u += 1
|
||||||
|
return '{0} {1}'.format(round(b, 1), units[u])
|
||||||
|
|
||||||
|
def is_root():
|
||||||
|
return os.geteuid() == 0
|
||||||
|
|
||||||
|
def file_crunching(filename):
|
||||||
|
# calculate filehash, file size
|
||||||
|
BLOCKSIZE = 65536
|
||||||
|
hasher = hashlib.sha1()
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
buf = f.read(BLOCKSIZE)
|
||||||
|
while len(buf) > 0:
|
||||||
|
hasher.update(buf)
|
||||||
|
buf = f.read(BLOCKSIZE)
|
||||||
|
filehash = hasher.hexdigest()
|
||||||
|
filesize = os.path.getsize(filename)
|
||||||
|
return filehash, filesize
|
||||||
|
|
||||||
|
|
|
@ -1,259 +1,68 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os, sys, subprocess, time, hashlib, platform, json, locale, socket, argparse, Queue, inspect, base64, mimetypes, hmac, shutil
|
import os, sys, subprocess, time, argparse, inspect, shutil, socket
|
||||||
from itertools import izip
|
|
||||||
|
|
||||||
from stem.control import Controller
|
from stem.control import Controller
|
||||||
from stem import SocketError
|
from stem import SocketError
|
||||||
|
|
||||||
from flask import Flask, Response, request, render_template_string, abort
|
import strings, helpers, web
|
||||||
|
|
||||||
class NoTor(Exception): pass
|
class NoTor(Exception): pass
|
||||||
|
class TailsError(Exception): pass
|
||||||
|
|
||||||
def constant_time_compare(val1, val2):
|
class OnionShare(object):
|
||||||
_builtin_constant_time_compare = getattr(hmac, 'compare_digest', None)
|
def __init__(self, debug=False, local_only=False, stay_open=False):
|
||||||
if _builtin_constant_time_compare is not None:
|
# debug mode
|
||||||
return _builtin_constant_time_compare(val1, val2)
|
if debug:
|
||||||
|
web.debug_mode()
|
||||||
|
|
||||||
len_eq = len(val1) == len(val2)
|
# do not use tor -- for development
|
||||||
if len_eq:
|
self.local_only = local_only
|
||||||
result = 0
|
|
||||||
left = val1
|
|
||||||
else:
|
|
||||||
result = 1
|
|
||||||
left = val2
|
|
||||||
for x, y in izip(bytearray(left), bytearray(val2)):
|
|
||||||
result |= x ^ y
|
|
||||||
return result == 0
|
|
||||||
|
|
||||||
def random_string(num_bytes):
|
# automatically close when download is finished
|
||||||
b = os.urandom(num_bytes)
|
self.stay_open = stay_open
|
||||||
h = hashlib.sha256(b).digest()[:16]
|
|
||||||
return base64.b32encode(h).lower().replace('=','')
|
|
||||||
|
|
||||||
def get_platform():
|
# list of hidden service dirs to cleanup
|
||||||
p = platform.system()
|
self.hidserv_dirs = []
|
||||||
if p == 'Linux' and platform.uname()[0:2] == ('Linux', 'amnesia'):
|
|
||||||
p = 'Tails'
|
|
||||||
return p
|
|
||||||
|
|
||||||
# information about the file
|
# choose a random port
|
||||||
filename = filesize = filehash = None
|
self.choose_port()
|
||||||
def set_file_info(new_filename, new_filehash, new_filesize):
|
self.local_host = "127.0.0.1:{0}".format(self.port)
|
||||||
global filename, filehash, filesize
|
|
||||||
filename = new_filename
|
|
||||||
filehash = new_filehash
|
|
||||||
filesize = new_filesize
|
|
||||||
|
|
||||||
# automatically close
|
def cleanup(self):
|
||||||
stay_open = False
|
for d in self.hidserv_dirs:
|
||||||
def set_stay_open(new_stay_open):
|
shutil.rmtree(d)
|
||||||
global stay_open
|
|
||||||
stay_open = new_stay_open
|
|
||||||
|
|
||||||
def get_stay_open():
|
def choose_port(self):
|
||||||
return stay_open
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
def debug_mode():
|
|
||||||
import logging
|
|
||||||
global app
|
|
||||||
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
temp_dir = os.environ['Temp'].replace('\\', '/')
|
|
||||||
else:
|
|
||||||
temp_dir = '/tmp/'
|
|
||||||
|
|
||||||
log_handler = logging.FileHandler('{0}/onionshare_server.log'.format(temp_dir))
|
|
||||||
log_handler.setLevel(logging.WARNING)
|
|
||||||
app.logger.addHandler(log_handler)
|
|
||||||
|
|
||||||
# get path of onioshare directory
|
|
||||||
if get_platform() == 'Darwin':
|
|
||||||
onionshare_dir = os.path.dirname(__file__)
|
|
||||||
else:
|
|
||||||
onionshare_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
|
||||||
|
|
||||||
strings = {}
|
|
||||||
slug = random_string(16)
|
|
||||||
download_count = 0
|
|
||||||
|
|
||||||
REQUEST_LOAD = 0
|
|
||||||
REQUEST_DOWNLOAD = 1
|
|
||||||
REQUEST_PROGRESS = 2
|
|
||||||
REQUEST_OTHER = 3
|
|
||||||
q = Queue.Queue()
|
|
||||||
|
|
||||||
def add_request(type, path, data=None):
|
|
||||||
global q
|
|
||||||
q.put({
|
|
||||||
'type': type,
|
|
||||||
'path': path,
|
|
||||||
'data': data
|
|
||||||
})
|
|
||||||
|
|
||||||
cleanup_q = Queue.Queue()
|
|
||||||
def register_cleanup_handler(directory):
|
|
||||||
global cleanup_q
|
|
||||||
def handler(signum = None, frame = None):
|
|
||||||
shutil.rmtree(directory)
|
|
||||||
cleanup_q.put(handler)
|
|
||||||
|
|
||||||
def execute_cleanup_handlers():
|
|
||||||
global cleanup_q
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
handler = cleanup_q.get(False)
|
|
||||||
handler()
|
|
||||||
except Queue.Empty:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def human_readable_filesize(b):
|
|
||||||
thresh = 1024.0
|
|
||||||
if b < thresh:
|
|
||||||
return '{0} B'.format(b)
|
|
||||||
units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']
|
|
||||||
u = 0
|
|
||||||
b /= thresh
|
|
||||||
while b >= thresh:
|
|
||||||
b /= thresh
|
|
||||||
u += 1
|
|
||||||
return '{0} {1}'.format(round(b, 1), units[u])
|
|
||||||
|
|
||||||
@app.route("/<slug_candidate>")
|
|
||||||
def index(slug_candidate):
|
|
||||||
global filename, filesize, filehash, slug, strings, REQUEST_LOAD, onionshare_dir
|
|
||||||
|
|
||||||
if not constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
add_request(REQUEST_LOAD, request.path)
|
|
||||||
return render_template_string(
|
|
||||||
open('{0}/index.html'.format(onionshare_dir)).read(),
|
|
||||||
slug=slug,
|
|
||||||
filename=os.path.basename(filename).decode("utf-8"),
|
|
||||||
filehash=filehash,
|
|
||||||
filesize=filesize,
|
|
||||||
filesize_human=human_readable_filesize(filesize),
|
|
||||||
strings=strings
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.route("/<slug_candidate>/download")
|
|
||||||
def download(slug_candidate):
|
|
||||||
global filename, filesize, q, download_count
|
|
||||||
global REQUEST_DOWNLOAD, REQUEST_PROGRESS
|
|
||||||
|
|
||||||
if not constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
# each download has a unique id
|
|
||||||
download_id = download_count
|
|
||||||
download_count += 1
|
|
||||||
|
|
||||||
# prepare some variables to use inside generate() function below
|
|
||||||
# which is outsie of the request context
|
|
||||||
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
|
||||||
path = request.path
|
|
||||||
|
|
||||||
# tell GUI the download started
|
|
||||||
add_request(REQUEST_DOWNLOAD, path, { 'id':download_id })
|
|
||||||
|
|
||||||
dirname = os.path.dirname(filename)
|
|
||||||
basename = os.path.basename(filename)
|
|
||||||
|
|
||||||
def generate():
|
|
||||||
chunk_size = 102400 # 100kb
|
|
||||||
|
|
||||||
fp = open(filename, 'rb')
|
|
||||||
done = False
|
|
||||||
while not done:
|
|
||||||
chunk = fp.read(102400)
|
|
||||||
if chunk == '':
|
|
||||||
done = True
|
|
||||||
else:
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
# tell GUI the progress
|
|
||||||
downloaded_bytes = fp.tell()
|
|
||||||
percent = round((1.0 * downloaded_bytes / filesize) * 100, 2);
|
|
||||||
sys.stdout.write("\r{0}, {1}% ".format(human_readable_filesize(downloaded_bytes), percent))
|
|
||||||
sys.stdout.flush()
|
|
||||||
add_request(REQUEST_PROGRESS, path, { 'id':download_id, 'bytes':downloaded_bytes })
|
|
||||||
|
|
||||||
fp.close()
|
|
||||||
sys.stdout.write("\n")
|
|
||||||
|
|
||||||
# download is finished, close the server
|
|
||||||
global stay_open
|
|
||||||
if not stay_open:
|
|
||||||
print translated("closing_automatically")
|
|
||||||
if shutdown_func is None:
|
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
|
||||||
shutdown_func()
|
|
||||||
|
|
||||||
r = Response(generate())
|
|
||||||
r.headers.add('Content-Length', filesize)
|
|
||||||
r.headers.add('Content-Disposition', 'attachment', filename=basename)
|
|
||||||
# guess content type
|
|
||||||
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
|
||||||
if content_type is not None:
|
|
||||||
r.headers.add('Content-Type', content_type)
|
|
||||||
return r
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def page_not_found(e):
|
|
||||||
global REQUEST_OTHER, onionshare_dir
|
|
||||||
add_request(REQUEST_OTHER, request.path)
|
|
||||||
return render_template_string(open('{0}/404.html'.format(onionshare_dir)).read())
|
|
||||||
|
|
||||||
def is_root():
|
|
||||||
return os.geteuid() == 0
|
|
||||||
|
|
||||||
def load_strings(default="en"):
|
|
||||||
global strings
|
|
||||||
try:
|
|
||||||
translated = json.loads(open('{0}/strings.json'.format(os.getcwd())).read())
|
|
||||||
except IOError:
|
|
||||||
translated = json.loads(open('{0}/strings.json'.format(onionshare_dir)).read())
|
|
||||||
strings = translated[default]
|
|
||||||
lc, enc = locale.getdefaultlocale()
|
|
||||||
if lc:
|
|
||||||
lang = lc[:2]
|
|
||||||
if lang in translated:
|
|
||||||
# if a string doesn't exist, fallback to English
|
|
||||||
for key in translated[default]:
|
|
||||||
if key in translated[lang]:
|
|
||||||
strings[key] = translated[lang][key]
|
|
||||||
return strings
|
|
||||||
|
|
||||||
def translated(k):
|
|
||||||
return strings[k].encode("utf-8")
|
|
||||||
|
|
||||||
def file_crunching(filename):
|
|
||||||
# calculate filehash, file size
|
|
||||||
BLOCKSIZE = 65536
|
|
||||||
hasher = hashlib.sha1()
|
|
||||||
with open(filename, 'rb') as f:
|
|
||||||
buf = f.read(BLOCKSIZE)
|
|
||||||
while len(buf) > 0:
|
|
||||||
hasher.update(buf)
|
|
||||||
buf = f.read(BLOCKSIZE)
|
|
||||||
filehash = hasher.hexdigest()
|
|
||||||
filesize = os.path.getsize(filename)
|
|
||||||
return filehash, filesize
|
|
||||||
|
|
||||||
def choose_port():
|
|
||||||
# let the OS choose a port
|
# let the OS choose a port
|
||||||
tmpsock = socket.socket()
|
tmpsock = socket.socket()
|
||||||
tmpsock.bind(("127.0.0.1", 0))
|
tmpsock.bind(("127.0.0.1", 0))
|
||||||
port = tmpsock.getsockname()[1]
|
self.port = tmpsock.getsockname()[1]
|
||||||
tmpsock.close()
|
tmpsock.close()
|
||||||
return port
|
|
||||||
|
|
||||||
def start_hidden_service(port):
|
def start_hidden_service(self):
|
||||||
|
if helpers.get_platform() == 'Tails':
|
||||||
|
# in Tails, start the hidden service in a root process
|
||||||
|
p = subprocess.Popen(['/usr/bin/sudo', '--', '/usr/bin/onionshare', str(app.port)], stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
stdout = p.stdout.read(22) # .onion URLs are 22 chars long
|
||||||
|
|
||||||
|
if stdout:
|
||||||
|
self.onion_host = stdout
|
||||||
|
else:
|
||||||
|
if root_p.poll() == -1:
|
||||||
|
raise TailsError(o.stderr.read())
|
||||||
|
else:
|
||||||
|
raise TailsError(strings._("error_tails_unknown_root"))
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.local_only:
|
||||||
|
self.onion_host = '127.0.0.1:{0}'.format(self.port)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print strings._("connecting_ctrlport").format(self.port)
|
||||||
|
|
||||||
# come up with a hidden service directory name
|
# come up with a hidden service directory name
|
||||||
hidserv_dir_rand = random_string(8)
|
hidserv_dir_rand = helpers.random_string(8)
|
||||||
if get_platform() == "Windows":
|
if helpers.get_platform() == "Windows":
|
||||||
if 'Temp' in os.environ:
|
if 'Temp' in os.environ:
|
||||||
temp = os.environ['Temp'].replace('\\', '/')
|
temp = os.environ['Temp'].replace('\\', '/')
|
||||||
else:
|
else:
|
||||||
|
@ -262,7 +71,7 @@ def start_hidden_service(port):
|
||||||
else:
|
else:
|
||||||
hidserv_dir = "/tmp/onionshare_{0}".format(hidserv_dir_rand)
|
hidserv_dir = "/tmp/onionshare_{0}".format(hidserv_dir_rand)
|
||||||
|
|
||||||
register_cleanup_handler(hidserv_dir)
|
self.hidserv_dirs.append(hidserv_dir)
|
||||||
|
|
||||||
# connect to the tor controlport
|
# connect to the tor controlport
|
||||||
controlports = [9051, 9151]
|
controlports = [9051, 9151]
|
||||||
|
@ -273,32 +82,30 @@ def start_hidden_service(port):
|
||||||
except SocketError:
|
except SocketError:
|
||||||
pass
|
pass
|
||||||
if not controller:
|
if not controller:
|
||||||
raise NoTor(translated("cant_connect_ctrlport").format(controlports))
|
raise NoTor(strings._("cant_connect_ctrlport").format(controlports))
|
||||||
controller.authenticate()
|
controller.authenticate()
|
||||||
|
|
||||||
# set up hidden service
|
# set up hidden service
|
||||||
controller.set_options([
|
controller.set_options([
|
||||||
('HiddenServiceDir', hidserv_dir),
|
('HiddenServiceDir', hidserv_dir),
|
||||||
('HiddenServicePort', '80 127.0.0.1:{0}'.format(port))
|
('HiddenServicePort', '80 127.0.0.1:{0}'.format(self.port))
|
||||||
])
|
])
|
||||||
|
|
||||||
# figure out the .onion hostname
|
# figure out the .onion hostname
|
||||||
hostname_file = '{0}/hostname'.format(hidserv_dir)
|
hostname_file = '{0}/hostname'.format(hidserv_dir)
|
||||||
onion_host = open(hostname_file, 'r').read().strip()
|
self.onion_host = open(hostname_file, 'r').read().strip()
|
||||||
|
|
||||||
return onion_host
|
|
||||||
|
|
||||||
def tails_root():
|
def tails_root():
|
||||||
# if running in Tails and as root, do only the things that require root
|
# if running in Tails and as root, do only the things that require root
|
||||||
if get_platform() == 'Tails' and is_root():
|
if helpers.get_platform() == 'Tails' and helpers.is_root():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('port', nargs=1, help=translated("help_tails_port"))
|
parser.add_argument('port', nargs=1, help=strings._("help_tails_port"))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
port = int(args.port[0])
|
port = int(args.port[0])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
sys.stderr.write('{0}\n'.format(translated("error_tails_invalid_port")))
|
sys.stderr.write('{0}\n'.format(strings._("error_tails_invalid_port")))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# open hole in firewall
|
# open hole in firewall
|
||||||
|
@ -322,74 +129,50 @@ def tails_root():
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
load_strings()
|
strings.load_strings()
|
||||||
tails_root()
|
tails_root()
|
||||||
|
|
||||||
# parse arguments
|
# parse arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--local-only', action='store_true', dest='local_only', help=translated("help_local_only"))
|
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
|
||||||
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=translated("help_stay_open"))
|
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
|
||||||
parser.add_argument('--debug', action='store_true', dest='debug', help=translated("help_debug"))
|
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
|
||||||
parser.add_argument('filename', nargs=1, help=translated("help_filename"))
|
parser.add_argument('filename', nargs=1, help=strings._("help_filename"))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
filename = os.path.abspath(args.filename[0])
|
filename = os.path.abspath(args.filename[0])
|
||||||
local_only = bool(args.local_only)
|
local_only = bool(args.local_only)
|
||||||
debug = bool(args.debug)
|
debug = bool(args.debug)
|
||||||
|
|
||||||
if debug:
|
|
||||||
debug_mode()
|
|
||||||
|
|
||||||
global stay_open
|
|
||||||
stay_open = bool(args.stay_open)
|
stay_open = bool(args.stay_open)
|
||||||
|
|
||||||
if not (filename and os.path.isfile(filename)):
|
if not (filename and os.path.isfile(filename)):
|
||||||
sys.exit(translated("not_a_file").format(filename))
|
sys.exit(strings._("not_a_file").format(filename))
|
||||||
filename = os.path.abspath(filename)
|
filename = os.path.abspath(filename)
|
||||||
|
|
||||||
port = choose_port()
|
# start the onionshare app
|
||||||
local_host = "127.0.0.1:{0}".format(port)
|
|
||||||
|
|
||||||
if get_platform() == 'Tails':
|
|
||||||
# if this is tails, start the root process
|
|
||||||
root_p = subprocess.Popen(['/usr/bin/sudo', '--', '/usr/bin/onionshare', str(port)], stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
||||||
stdout = root_p.stdout.read(22) # .onion URLs are 22 chars long
|
|
||||||
|
|
||||||
if stdout:
|
|
||||||
onion_host = stdout
|
|
||||||
else:
|
|
||||||
if root_p.poll() == -1:
|
|
||||||
sys.exit(root_p.stderr.read())
|
|
||||||
else:
|
|
||||||
sys.exit(translated("error_tails_unknown_root"))
|
|
||||||
else:
|
|
||||||
# if not tails, start hidden service normally
|
|
||||||
if not local_only:
|
|
||||||
# try starting hidden service
|
|
||||||
print translated("connecting_ctrlport").format(port)
|
|
||||||
try:
|
try:
|
||||||
onion_host = start_hidden_service(port)
|
app = OnionShare(debug, local_only, stay_open)
|
||||||
|
app.start_hidden_service()
|
||||||
except NoTor as e:
|
except NoTor as e:
|
||||||
sys.exit(e.args[0])
|
sys.exit(e.args[0])
|
||||||
|
except TailsError as e:
|
||||||
|
sys.exit(e.args[0])
|
||||||
|
|
||||||
# startup
|
# startup
|
||||||
print translated("calculating_sha1")
|
print strings._("calculating_sha1")
|
||||||
filehash, filesize = file_crunching(filename)
|
filehash, filesize = helpers.file_crunching(filename)
|
||||||
set_file_info(filename, filehash, filesize)
|
web.set_file_info(filename, filehash, filesize)
|
||||||
print '\n' + translated("give_this_url")
|
print '\n' + strings._("give_this_url")
|
||||||
if local_only:
|
print 'http://{0}/{1}'.format(app.onion_host, web.slug)
|
||||||
print 'http://{0}/{1}'.format(local_host, slug)
|
|
||||||
else:
|
|
||||||
print 'http://{0}/{1}'.format(onion_host, slug)
|
|
||||||
print ''
|
print ''
|
||||||
print translated("ctrlc_to_stop")
|
print strings._("ctrlc_to_stop")
|
||||||
|
|
||||||
# start the web server
|
# start the web server
|
||||||
app.run(port=port)
|
web.start(app.port, app.stay_open)
|
||||||
print '\n'
|
print '\n'
|
||||||
|
|
||||||
# shutdown
|
# shutdown
|
||||||
execute_cleanup_handlers()
|
app.cleanup()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
22
onionshare/strings.py
Normal file
22
onionshare/strings.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import json, locale
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
strings = {}
|
||||||
|
|
||||||
|
def load_strings(default="en"):
|
||||||
|
global strings
|
||||||
|
translated = json.loads(open('{0}/strings.json'.format(helpers.get_onionshare_dir())).read())
|
||||||
|
strings = translated[default]
|
||||||
|
lc, enc = locale.getdefaultlocale()
|
||||||
|
if lc:
|
||||||
|
lang = lc[:2]
|
||||||
|
if lang in translated:
|
||||||
|
# if a string doesn't exist, fallback to English
|
||||||
|
for key in translated[default]:
|
||||||
|
if key in translated[lang]:
|
||||||
|
strings[key] = translated[lang][key]
|
||||||
|
|
||||||
|
def translated(k):
|
||||||
|
return strings[k].encode("utf-8")
|
||||||
|
|
||||||
|
_ = translated
|
134
onionshare/web.py
Normal file
134
onionshare/web.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import Queue, mimetypes, platform, os, sys
|
||||||
|
from flask import Flask, Response, request, render_template_string, abort
|
||||||
|
|
||||||
|
import strings, helpers
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# information about the file
|
||||||
|
filename = filesize = filehash = None
|
||||||
|
def set_file_info(new_filename, new_filehash, new_filesize):
|
||||||
|
global filename, filehash, filesize
|
||||||
|
filename = new_filename
|
||||||
|
filehash = new_filehash
|
||||||
|
filesize = new_filesize
|
||||||
|
|
||||||
|
REQUEST_LOAD = 0
|
||||||
|
REQUEST_DOWNLOAD = 1
|
||||||
|
REQUEST_PROGRESS = 2
|
||||||
|
REQUEST_OTHER = 3
|
||||||
|
q = Queue.Queue()
|
||||||
|
|
||||||
|
def add_request(type, path, data=None):
|
||||||
|
global q
|
||||||
|
q.put({
|
||||||
|
'type': type,
|
||||||
|
'path': path,
|
||||||
|
'data': data
|
||||||
|
})
|
||||||
|
|
||||||
|
slug = helpers.random_string(16)
|
||||||
|
download_count = 0
|
||||||
|
|
||||||
|
stay_open = False
|
||||||
|
def set_stay_open(new_stay_open):
|
||||||
|
global stay_open
|
||||||
|
stay_open = new_stay_open
|
||||||
|
def get_stay_open():
|
||||||
|
return stay_open
|
||||||
|
|
||||||
|
def debug_mode():
|
||||||
|
import logging
|
||||||
|
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
temp_dir = os.environ['Temp'].replace('\\', '/')
|
||||||
|
else:
|
||||||
|
temp_dir = '/tmp/'
|
||||||
|
|
||||||
|
log_handler = logging.FileHandler('{0}/onionshare_server.log'.format(temp_dir))
|
||||||
|
log_handler.setLevel(logging.WARNING)
|
||||||
|
app.logger.addHandler(log_handler)
|
||||||
|
|
||||||
|
@app.route("/<slug_candidate>")
|
||||||
|
def index(slug_candidate):
|
||||||
|
if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
add_request(REQUEST_LOAD, request.path)
|
||||||
|
return render_template_string(
|
||||||
|
open('{0}/index.html'.format(helpers.get_onionshare_dir())).read(),
|
||||||
|
slug=slug,
|
||||||
|
filename=os.path.basename(filename).decode("utf-8"),
|
||||||
|
filehash=filehash,
|
||||||
|
filesize=filesize,
|
||||||
|
filesize_human=helpers.human_readable_filesize(filesize),
|
||||||
|
strings=strings.strings
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route("/<slug_candidate>/download")
|
||||||
|
def download(slug_candidate):
|
||||||
|
global download_count
|
||||||
|
if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# each download has a unique id
|
||||||
|
download_id = download_count
|
||||||
|
download_count += 1
|
||||||
|
|
||||||
|
# prepare some variables to use inside generate() function below
|
||||||
|
# which is outsie of the request context
|
||||||
|
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
||||||
|
path = request.path
|
||||||
|
|
||||||
|
# tell GUI the download started
|
||||||
|
add_request(REQUEST_DOWNLOAD, path, { 'id':download_id })
|
||||||
|
|
||||||
|
dirname = os.path.dirname(filename)
|
||||||
|
basename = os.path.basename(filename)
|
||||||
|
|
||||||
|
def generate():
|
||||||
|
chunk_size = 102400 # 100kb
|
||||||
|
|
||||||
|
fp = open(filename, 'rb')
|
||||||
|
done = False
|
||||||
|
while not done:
|
||||||
|
chunk = fp.read(102400)
|
||||||
|
if chunk == '':
|
||||||
|
done = True
|
||||||
|
else:
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
# tell GUI the progress
|
||||||
|
downloaded_bytes = fp.tell()
|
||||||
|
percent = round((1.0 * downloaded_bytes / filesize) * 100, 2);
|
||||||
|
sys.stdout.write("\r{0}, {1}% ".format(helpers.human_readable_filesize(downloaded_bytes), percent))
|
||||||
|
sys.stdout.flush()
|
||||||
|
add_request(REQUEST_PROGRESS, path, { 'id':download_id, 'bytes':downloaded_bytes })
|
||||||
|
|
||||||
|
fp.close()
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
|
# download is finished, close the server
|
||||||
|
if not stay_open:
|
||||||
|
print strings._("closing_automatically")
|
||||||
|
if shutdown_func is None:
|
||||||
|
raise RuntimeError('Not running with the Werkzeug Server')
|
||||||
|
shutdown_func()
|
||||||
|
|
||||||
|
r = Response(generate())
|
||||||
|
r.headers.add('Content-Length', filesize)
|
||||||
|
r.headers.add('Content-Disposition', 'attachment', filename=basename)
|
||||||
|
# guess content type
|
||||||
|
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
||||||
|
if content_type is not None:
|
||||||
|
r.headers.add('Content-Type', content_type)
|
||||||
|
return r
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
add_request(REQUEST_OTHER, request.path)
|
||||||
|
return render_template_string(open('{0}/404.html'.format(helpers.get_onionshare_dir())).read())
|
||||||
|
|
||||||
|
def start(port, stay_open=False):
|
||||||
|
set_stay_open(stay_open)
|
||||||
|
app.run(port=port)
|
Loading…
Add table
Add a link
Reference in a new issue