[Pkg-anonymity-tools] [onionshare] 26/140: Split the increasingly-sprawly onionshare module into different modules:

Ulrike Uhlig u-guest at moszumanska.debian.org
Mon Sep 29 20:33:44 UTC 2014


This is an automated email from the git hooks/post-receive script.

u-guest pushed a commit to branch master
in repository onionshare.

commit 54a37ee28e1fd4c61e6cc18e2c7caf407b1cee24
Author: Micah Lee <micah at micahflee.com>
Date:   Tue Aug 26 18:22:59 2014 -0700

    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
---
 onionshare/helpers.py    |  66 +++++++
 onionshare/onionshare.py | 439 ++++++++++++-----------------------------------
 onionshare/strings.py    |  22 +++
 onionshare/web.py        | 134 +++++++++++++++
 4 files changed, 333 insertions(+), 328 deletions(-)

diff --git a/onionshare/helpers.py b/onionshare/helpers.py
new file mode 100644
index 0000000..83e04d7
--- /dev/null
+++ b/onionshare/helpers.py
@@ -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
+
+
diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py
index def2bd9..f47a4c4 100644
--- a/onionshare/onionshare.py
+++ b/onionshare/onionshare.py
@@ -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
-
-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 get_platform():
-    p = platform.system()
-    if p == 'Linux' and platform.uname()[0:2] == ('Linux', 'amnesia'):
-        p = 'Tails'
-    return p
-
-# 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
-
-# automatically close
-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
-
-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])
-
- at 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
-    )
-
- at 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
+class TailsError(Exception): pass
+
+class OnionShare(object):
+    def __init__(self, debug=False, local_only=False, stay_open=False):
+        # debug mode
+        if debug:
+            web.debug_mode()
+
+        # do not use tor -- for development
+        self.local_only = local_only
+
+        # automatically close when download is finished
+        self.stay_open = stay_open
+
+        # list of hidden service dirs to cleanup
+        self.hidserv_dirs = []
+
+        # choose a random port
+        self.choose_port()
+        self.local_host = "127.0.0.1:{0}".format(self.port)
+
+    def cleanup(self):
+        for d in self.hidserv_dirs:
+            shutil.rmtree(d)
+
+    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()
+
+    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:
-                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
-
- at 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)
-
-    register_cleanup_handler(hidserv_dir)
-
-    # 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()
-
-    # set up hidden service
-    controller.set_options([
-        ('HiddenServiceDir', hidserv_dir),
-        ('HiddenServicePort', '80 127.0.0.1:{0}'.format(port))
-    ])
+                if root_p.poll() == -1:
+                    raise TailsError(o.stderr.read())
+                else:
+                    raise TailsError(strings._("error_tails_unknown_root"))
 
-    # figure out the .onion hostname
-    hostname_file = '{0}/hostname'.format(hidserv_dir)
-    onion_host = open(hostname_file, 'r').read().strip()
+        else:
+            if self.local_only:
+                self.onion_host = '127.0.0.1:{0}'.format(self.port)
 
-    return onion_host
+            else:
+                print strings._("connecting_ctrlport").format(self.port)
+
+                # 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)
+
+                self.hidserv_dirs.append(hidserv_dir)
+
+                # 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()
+
+                # 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()
diff --git a/onionshare/strings.py b/onionshare/strings.py
new file mode 100644
index 0000000..3808f93
--- /dev/null
+++ b/onionshare/strings.py
@@ -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
diff --git a/onionshare/web.py b/onionshare/web.py
new file mode 100644
index 0000000..9f16cdc
--- /dev/null
+++ b/onionshare/web.py
@@ -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)
+
+ at 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
+    )
+
+ at 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
+
+ at 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)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/onionshare.git



More information about the Pkg-anonymity-tools mailing list