mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-13 08:19:28 -05: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
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,304 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os, sys, subprocess, time, hashlib, platform, json, locale, socket, argparse, Queue, inspect, base64, mimetypes, hmac, shutil
|
||||
from itertools import izip
|
||||
import os, sys, subprocess, time, argparse, inspect, shutil, socket
|
||||
|
||||
from stem.control import Controller
|
||||
from stem import SocketError
|
||||
|
||||
from flask import Flask, Response, request, render_template_string, abort
|
||||
import strings, helpers, web
|
||||
|
||||
class NoTor(Exception): pass
|
||||
class TailsError(Exception): pass
|
||||
|
||||
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)
|
||||
class OnionShare(object):
|
||||
def __init__(self, debug=False, local_only=False, stay_open=False):
|
||||
# debug mode
|
||||
if debug:
|
||||
web.debug_mode()
|
||||
|
||||
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
|
||||
# do not use tor -- for development
|
||||
self.local_only = local_only
|
||||
|
||||
def random_string(num_bytes):
|
||||
b = os.urandom(num_bytes)
|
||||
h = hashlib.sha256(b).digest()[:16]
|
||||
return base64.b32encode(h).lower().replace('=','')
|
||||
# automatically close when download is finished
|
||||
self.stay_open = stay_open
|
||||
|
||||
def get_platform():
|
||||
p = platform.system()
|
||||
if p == 'Linux' and platform.uname()[0:2] == ('Linux', 'amnesia'):
|
||||
p = 'Tails'
|
||||
return p
|
||||
# list of hidden service dirs to cleanup
|
||||
self.hidserv_dirs = []
|
||||
|
||||
# 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
|
||||
# choose a random port
|
||||
self.choose_port()
|
||||
self.local_host = "127.0.0.1:{0}".format(self.port)
|
||||
|
||||
# automatically close
|
||||
stay_open = False
|
||||
def set_stay_open(new_stay_open):
|
||||
global stay_open
|
||||
stay_open = new_stay_open
|
||||
def cleanup(self):
|
||||
for d in self.hidserv_dirs:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def get_stay_open():
|
||||
return stay_open
|
||||
def choose_port(self):
|
||||
# let the OS choose a port
|
||||
tmpsock = socket.socket()
|
||||
tmpsock.bind(("127.0.0.1", 0))
|
||||
self.port = tmpsock.getsockname()[1]
|
||||
tmpsock.close()
|
||||
|
||||
app = Flask(__name__)
|
||||
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
|
||||
|
||||
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
|
||||
if stdout:
|
||||
self.onion_host = stdout
|
||||
else:
|
||||
yield chunk
|
||||
if root_p.poll() == -1:
|
||||
raise TailsError(o.stderr.read())
|
||||
else:
|
||||
raise TailsError(strings._("error_tails_unknown_root"))
|
||||
|
||||
# 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
|
||||
tmpsock = socket.socket()
|
||||
tmpsock.bind(("127.0.0.1", 0))
|
||||
port = tmpsock.getsockname()[1]
|
||||
tmpsock.close()
|
||||
return port
|
||||
|
||||
def start_hidden_service(port):
|
||||
# come up with a hidden service directory name
|
||||
hidserv_dir_rand = random_string(8)
|
||||
if get_platform() == "Windows":
|
||||
if 'Temp' in os.environ:
|
||||
temp = os.environ['Temp'].replace('\\', '/')
|
||||
else:
|
||||
temp = 'C:/tmp'
|
||||
hidserv_dir = "{0}/onionshare_{1}".format(temp, hidserv_dir_rand)
|
||||
else:
|
||||
hidserv_dir = "/tmp/onionshare_{0}".format(hidserv_dir_rand)
|
||||
if self.local_only:
|
||||
self.onion_host = '127.0.0.1:{0}'.format(self.port)
|
||||
|
||||
register_cleanup_handler(hidserv_dir)
|
||||
else:
|
||||
print strings._("connecting_ctrlport").format(self.port)
|
||||
|
||||
# connect to the tor controlport
|
||||
controlports = [9051, 9151]
|
||||
controller = False
|
||||
for controlport in controlports:
|
||||
try:
|
||||
controller = Controller.from_port(port=controlport)
|
||||
except SocketError:
|
||||
pass
|
||||
if not controller:
|
||||
raise NoTor(translated("cant_connect_ctrlport").format(controlports))
|
||||
controller.authenticate()
|
||||
# come up with a hidden service directory name
|
||||
hidserv_dir_rand = helpers.random_string(8)
|
||||
if helpers.get_platform() == "Windows":
|
||||
if 'Temp' in os.environ:
|
||||
temp = os.environ['Temp'].replace('\\', '/')
|
||||
else:
|
||||
temp = 'C:/tmp'
|
||||
hidserv_dir = "{0}/onionshare_{1}".format(temp, hidserv_dir_rand)
|
||||
else:
|
||||
hidserv_dir = "/tmp/onionshare_{0}".format(hidserv_dir_rand)
|
||||
|
||||
# set up hidden service
|
||||
controller.set_options([
|
||||
('HiddenServiceDir', hidserv_dir),
|
||||
('HiddenServicePort', '80 127.0.0.1:{0}'.format(port))
|
||||
])
|
||||
self.hidserv_dirs.append(hidserv_dir)
|
||||
|
||||
# figure out the .onion hostname
|
||||
hostname_file = '{0}/hostname'.format(hidserv_dir)
|
||||
onion_host = open(hostname_file, 'r').read().strip()
|
||||
# connect to the tor controlport
|
||||
controlports = [9051, 9151]
|
||||
controller = False
|
||||
for controlport in controlports:
|
||||
try:
|
||||
controller = Controller.from_port(port=controlport)
|
||||
except SocketError:
|
||||
pass
|
||||
if not controller:
|
||||
raise NoTor(strings._("cant_connect_ctrlport").format(controlports))
|
||||
controller.authenticate()
|
||||
|
||||
return onion_host
|
||||
# set up hidden service
|
||||
controller.set_options([
|
||||
('HiddenServiceDir', hidserv_dir),
|
||||
('HiddenServicePort', '80 127.0.0.1:{0}'.format(self.port))
|
||||
])
|
||||
|
||||
# figure out the .onion hostname
|
||||
hostname_file = '{0}/hostname'.format(hidserv_dir)
|
||||
self.onion_host = open(hostname_file, 'r').read().strip()
|
||||
|
||||
def tails_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.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()
|
||||
|
||||
try:
|
||||
port = int(args.port[0])
|
||||
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)
|
||||
|
||||
# open hole in firewall
|
||||
@ -322,74 +129,50 @@ def tails_root():
|
||||
time.sleep(1)
|
||||
|
||||
def main():
|
||||
load_strings()
|
||||
strings.load_strings()
|
||||
tails_root()
|
||||
|
||||
# parse arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--local-only', action='store_true', dest='local_only', help=translated("help_local_only"))
|
||||
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=translated("help_stay_open"))
|
||||
parser.add_argument('--debug', action='store_true', dest='debug', help=translated("help_debug"))
|
||||
parser.add_argument('filename', nargs=1, help=translated("help_filename"))
|
||||
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=strings._("help_stay_open"))
|
||||
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
|
||||
parser.add_argument('filename', nargs=1, help=strings._("help_filename"))
|
||||
args = parser.parse_args()
|
||||
|
||||
filename = os.path.abspath(args.filename[0])
|
||||
local_only = bool(args.local_only)
|
||||
debug = bool(args.debug)
|
||||
|
||||
if debug:
|
||||
debug_mode()
|
||||
|
||||
global stay_open
|
||||
stay_open = bool(args.stay_open)
|
||||
|
||||
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)
|
||||
|
||||
port = choose_port()
|
||||
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:
|
||||
onion_host = start_hidden_service(port)
|
||||
except NoTor as e:
|
||||
sys.exit(e.args[0])
|
||||
# start the onionshare app
|
||||
try:
|
||||
app = OnionShare(debug, local_only, stay_open)
|
||||
app.start_hidden_service()
|
||||
except NoTor as e:
|
||||
sys.exit(e.args[0])
|
||||
except TailsError as e:
|
||||
sys.exit(e.args[0])
|
||||
|
||||
# startup
|
||||
print translated("calculating_sha1")
|
||||
filehash, filesize = file_crunching(filename)
|
||||
set_file_info(filename, filehash, filesize)
|
||||
print '\n' + translated("give_this_url")
|
||||
if local_only:
|
||||
print 'http://{0}/{1}'.format(local_host, slug)
|
||||
else:
|
||||
print 'http://{0}/{1}'.format(onion_host, slug)
|
||||
print strings._("calculating_sha1")
|
||||
filehash, filesize = helpers.file_crunching(filename)
|
||||
web.set_file_info(filename, filehash, filesize)
|
||||
print '\n' + strings._("give_this_url")
|
||||
print 'http://{0}/{1}'.format(app.onion_host, web.slug)
|
||||
print ''
|
||||
print translated("ctrlc_to_stop")
|
||||
print strings._("ctrlc_to_stop")
|
||||
|
||||
# start the web server
|
||||
app.run(port=port)
|
||||
web.start(app.port, app.stay_open)
|
||||
print '\n'
|
||||
|
||||
# shutdown
|
||||
execute_cleanup_handlers()
|
||||
app.cleanup()
|
||||
|
||||
if __name__ == '__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…
Reference in New Issue
Block a user