[Pkg-anonymity-tools] [onionshare] 31/140: support for multiple files and folders (#66)

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 c5ced60f8bafbbc0a5eff687927f8b2ddb0341ad
Author: Micah Lee <micah at micahflee.com>
Date:   Wed Aug 27 13:51:39 2014 -0700

    support for multiple files and folders (#66)
---
 onionshare/helpers.py    |  62 ++++++++++++-----
 onionshare/index.html    | 171 +++++++++++++++++++++++++++--------------------
 onionshare/onionshare.py |  49 +++++++-------
 onionshare/strings.json  |   6 +-
 onionshare/web.py        |  57 +++++++++++-----
 5 files changed, 216 insertions(+), 129 deletions(-)

diff --git a/onionshare/helpers.py b/onionshare/helpers.py
index 83e04d7..9d9193b 100644
--- a/onionshare/helpers.py
+++ b/onionshare/helpers.py
@@ -1,4 +1,4 @@
-import os, inspect, hashlib, base64, hmac, platform
+import os, inspect, hashlib, base64, hmac, platform, zipfile
 from itertools import izip
 
 def get_platform():
@@ -30,10 +30,13 @@ def constant_time_compare(val1, val2):
         result |= x ^ y
     return result == 0
 
-def random_string(num_bytes):
+def random_string(num_bytes, output_len=None):
     b = os.urandom(num_bytes)
     h = hashlib.sha256(b).digest()[:16]
-    return base64.b32encode(h).lower().replace('=','')
+    s = base64.b32encode(h).lower().replace('=','')
+    if not output_len:
+        return s
+    return s[:output_len]
 
 def human_readable_filesize(b):
     thresh = 1024.0
@@ -50,17 +53,46 @@ def human_readable_filesize(b):
 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
+def dir_size(start_path):
+    total_size = 0
+    for dirpath, dirnames, filenames in os.walk(start_path):
+        for f in filenames:
+            fp = os.path.join(dirpath, f)
+            if not os.path.islink(fp):
+                total_size += os.path.getsize(fp)
+    return total_size
 
+def get_tmp_dir():
+    if get_platform() == "Windows":
+        if 'Temp' in os.environ:
+            temp = os.environ['Temp'].replace('\\', '/')
+        else:
+            temp = 'C:/tmp'
+    else:
+        temp = '/tmp'
+    return temp
+
+class ZipWriter(object):
+    def __init__(self, zip_filename=None):
+        if zip_filename:
+            self.zip_filename = zip_filename
+        else:
+            self.zip_filename = '{0}/onionshare_{1}.zip'.format(get_tmp_dir(), random_string(4, 6))
+
+        self.z = zipfile.ZipFile(self.zip_filename, 'w')
+
+    def add_file(self, filename):
+        self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
+
+    def add_dir(self, filename):
+        dir_to_strip = os.path.dirname(filename)+'/'
+        for dirpath, dirnames, filenames in os.walk(filename):
+            for f in filenames:
+                full_filename = os.path.join(dirpath, f)
+                if not os.path.islink(full_filename):
+                    arc_filename = full_filename[len(dir_to_strip):]
+                    self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
+
+    def close(self):
+        self.z.close()
 
diff --git a/onionshare/index.html b/onionshare/index.html
index e6d423b..3d5cde2 100644
--- a/onionshare/index.html
+++ b/onionshare/index.html
@@ -1,76 +1,103 @@
 <!DOCTYPE html>
 <html>
-    <head>
-        <title>OnionShare</title>
-        <style type="text/css">
-            body {
-                background-color: #222222;
-                color: #ffffff;
-                text-align: center;
-                font-family: arial;
-                padding: 5em 1em;
-            }
-            .metadata {
-                position: absolute;
-                bottom: 0;
-                color: #999999;
-                text-align: left;
-            }
-            .button {
-                -moz-box-shadow:inset 0px 1px 0px 0px #cae3fc;
-                -webkit-box-shadow:inset 0px 1px 0px 0px #cae3fc;
-                box-shadow:inset 0px 1px 0px 0px #cae3fc;
-                background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #4197ee) );
-                background:-moz-linear-gradient( center top, #79bbff 5%, #4197ee 100% );
-                filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#4197ee');
-                background-color:#79bbff;
-                -webkit-border-top-left-radius:12px;
-                -moz-border-radius-topleft:12px;
-                border-top-left-radius:12px;
-                -webkit-border-top-right-radius:12px;
-                -moz-border-radius-topright:12px;
-                border-top-right-radius:12px;
-                -webkit-border-bottom-right-radius:12px;
-                -moz-border-radius-bottomright:12px;
-                border-bottom-right-radius:12px;
-                -webkit-border-bottom-left-radius:12px;
-                -moz-border-radius-bottomleft:12px;
-                border-bottom-left-radius:12px;
-                text-indent:0;
-                border:1px solid #469df5;
-                display:inline-block;
-                color:#ffffff;
-                font-family:Arial;
-                font-size:29px;
-                font-weight:bold;
-                font-style:normal;
-                height:50px;
-                line-height:50px;
-                text-decoration:none;
-                text-align:center;
-                text-shadow:1px 1px 0px #287ace;
-                padding: 0 20px;
-            }
-            .button:hover {
-                background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #4197ee), color-stop(1, #79bbff) );
-                background:-moz-linear-gradient( center top, #4197ee 5%, #79bbff 100% );
-                filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4197ee', endColorstr='#79bbff');
-                background-color:#4197ee;
-            }.button:active {
-                position:relative;
-                top:1px;
-            }
-        </style>
-        <meta name="onionshare-filename" content="{{ filename }}">
-        <meta name="onionshare-filesize" content="{{ filesize }}">
-        <meta name="onionshare-filehash" content="{{ filehash }}">
-    </head>
-    <body>
-    <p><a class="button" href='/{{ slug }}/download'>{{ filename }} &#x25BC;</a></p>
+  <head>
+    <title>OnionShare</title>
+    <style type="text/css">
+      body {
+        background-color: #222222;
+        color: #ffffff;
+        text-align: center;
+        font-family: sans-serif;
+        padding: 5em 1em;
+      }
+      .button {
+        -moz-box-shadow:inset 0px 1px 0px 0px #cae3fc;
+        -webkit-box-shadow:inset 0px 1px 0px 0px #cae3fc;
+        box-shadow:inset 0px 1px 0px 0px #cae3fc;
+        background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #4197ee) );
+        background:-moz-linear-gradient( center top, #79bbff 5%, #4197ee 100% );
+        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#4197ee');
+        background-color:#79bbff;
+        -webkit-border-top-left-radius:12px;
+        -moz-border-radius-topleft:12px;
+        border-top-left-radius:12px;
+        -webkit-border-top-right-radius:12px;
+        -moz-border-radius-topright:12px;
+        border-top-right-radius:12px;
+        -webkit-border-bottom-right-radius:12px;
+        -moz-border-radius-bottomright:12px;
+        border-bottom-right-radius:12px;
+        -webkit-border-bottom-left-radius:12px;
+        -moz-border-radius-bottomleft:12px;
+        border-bottom-left-radius:12px;
+        text-indent:0;
+        border:1px solid #469df5;
+        display:inline-block;
+        color:#ffffff;
+        font-size:29px;
+        font-weight:bold;
+        font-style:normal;
+        height:50px;
+        line-height:50px;
+        text-decoration:none;
+        text-align:center;
+        text-shadow:1px 1px 0px #287ace;
+        padding: 0 20px;
+      }
+      .button:hover {
+        background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #4197ee), color-stop(1, #79bbff) );
+        background:-moz-linear-gradient( center top, #4197ee 5%, #79bbff 100% );
+        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4197ee', endColorstr='#79bbff');
+        background-color:#4197ee;
+      }.button:active {
+        position:relative;
+        top:1px;
+      }
+
+      .download-size {
+        color: #999999;
+      }
 
-        <div class="metadata">
-            <p>{{strings.filesize}}: <strong title="{{ filesize }} bytes">{{ filesize_human }}</strong></p>
-            <p>{{strings.sha1_checksum}}: <strong>{{ filehash }}</strong></p>
-        </div>
-    </body>
+      .file-list {
+        margin: 50px auto 0 auto;
+        padding: 10px;
+        text-align: left;
+        background-color: #333333;
+      }
+      .file-list th {
+        padding: 5px;
+        font-weight: bold;
+      }
+      .file-list td {
+        padding: 5px;
+      }
+    </style>
+    <meta name="onionshare-filename" content="{{ filename }}">
+    <meta name="onionshare-filesize" content="{{ filesize }}">
+  </head>
+  <body>
+    <p><a class="button" href='/{{ slug }}/download'>{{ filename }} &#x25BC;</a></p>
+    <p class="download-size">{{strings.download_size}}: <strong title="{{ filesize }} bytes">{{ filesize_human }}</strong></p>
+    <table class="file-list">
+      <tr>
+        <th></th>
+        <th>{{strings.filename}}</th>
+        <th>{{strings.size}}</th>
+      </tr>
+      {% for info in file_info.dirs %}
+      <tr>
+        <td><img width="30" height="30" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3ggbFCQXOpwjbQAAAM5JREFUSMftlksOwyAMRDFCbNjkDL1kz9K75Cq9AZF3kYIM7oqoP/KpcFK1mRUrnsbW2Fbq0N+JiPhZRMS1OZAfzLzocwCAGmC9V2Vhjduarj8CzynGqIwxsDl4SXV267E4uFTN33X8deCxDyV1XXeRYJo1UUDEa9M0pxoRK+b4HiqRcz3nVAJKRK+TSxqaGbrkNKUkBn0oNSK2+T0MA1dau9NzO4QwuvPen1lAxQtEsq/vNpSWhvZ9v/3IRMTWOQezkyuEoKy14tfHoU11A6Mr5Axrp [...]
+        <td>{{ info.basename }}</td>
+        <td>{{ info.size_human }}</td>
+      </tr>
+      {% endfor %}
+      {% for info in file_info.files %}
+      <tr>
+        <td><img width="30" height="30" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3ggbFCQqYvRvfAAAAJFJREFUSMftlUkOwCAIAEWJ//+uIUivtYmtG7VN8KYHRhlA5zYtuB6klCTG2BcEAHrBeN4ws3jvX3lxQZmFiogMgVesVrhKXlvgakKf4KqVdAdXL+Ea3O8aILh0GnUMEhjtw9mLbUv1tx33fgItynBVoN+k2hybY3Nsjg08Bs45q4GYuQ4OIQARLYcSkUPEoiUP4j5IFasKOnUAAAAASUVORK5CYII=" /></td>
+        <td>{{ info.basename }}</td>
+        <td>{{ info.size_human }}</td>
+      </tr>
+      {% endfor %}
+    <table>
+  </body>
 </html>
diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py
index ca000c7..69b4e50 100644
--- a/onionshare/onionshare.py
+++ b/onionshare/onionshare.py
@@ -21,16 +21,19 @@ class OnionShare(object):
         # automatically close when download is finished
         self.stay_open = stay_open
 
-        # list of hidden service dirs to cleanup
-        self.hidserv_dirs = []
+        # files and dirs to delete on shutdown
+        self.cleanup_filenames = []
 
         # 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)
+        for filename in self.cleanup_filenames:
+            if os.path.isfile(filename):
+                os.remove(filename)
+            elif os.path.isdir(filename):
+                shutil.rmtree(filename)
 
     def choose_port(self):
         # let the OS choose a port
@@ -65,17 +68,8 @@ class OnionShare(object):
                 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)
+                hidserv_dir = '{0}/onionshare_{1}'.format(helpers.get_tmp_dir(), helpers.random_string(8))
+                self.cleanup_filenames.append(hidserv_dir)
 
                 # connect to the tor controlport
                 controlports = [9051, 9151]
@@ -141,17 +135,25 @@ def main():
     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"))
+    parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
     args = parser.parse_args()
 
-    filename = os.path.abspath(args.filename[0])
+    filenames = args.filename
+    for i in range(len(filenames)):
+        filenames[i] = os.path.abspath(filenames[i])
+
     local_only = bool(args.local_only)
     debug = bool(args.debug)
     stay_open = bool(args.stay_open)
 
-    if not (filename and os.path.isfile(filename)):
-        sys.exit(strings._("not_a_file").format(filename))
-    filename = os.path.abspath(filename)
+    # validation
+    valid = True
+    for filename in filenames:
+        if not os.path.exists(filename):
+            print(strings._("not_a_file").format(filename))
+            valid = False
+    if not valid:
+        sys.exit()
 
     # start the onionshare app
     try:
@@ -163,10 +165,9 @@ def main():
         sys.exit(e.args[0])
 
     # startup
-    print strings._("calculating_sha1")
-    filehash, filesize = helpers.file_crunching(filename)
-    web.set_file_info(filename, filehash, filesize)
-    print '\n' + strings._("give_this_url")
+    web.set_file_info(filenames)
+    app.cleanup_filenames.append(web.zip_filename)
+    print strings._("give_this_url")
     print 'http://{0}/{1}'.format(app.onion_host, web.slug)
     print ''
     print strings._("ctrlc_to_stop")
diff --git a/onionshare/strings.json b/onionshare/strings.json
index f98a366..312e86b 100644
--- a/onionshare/strings.json
+++ b/onionshare/strings.json
@@ -5,7 +5,9 @@
     "give_this_url": "Give this URL to the person you're sending the file to:",
     "ctrlc_to_stop":  "Press Ctrl-C to stop server",
     "not_a_file": "{0} is not a file.",
-    "filesize": "File size",
+    "download_size": "Download size",
+    "filename": "Filename",
+    "size": "Size",
     "sha1_checksum": "SHA1 checksum",
     "copied_url": "Copied URL to clipboard",
     "download_page_loaded": "Download page loaded",
@@ -24,7 +26,7 @@
     "help_local_only": "Do not attempt to use tor: for development only",
     "help_stay_open": "Keep hidden service running after download has finished",
     "help_debug": "Log errors to disk",
-    "help_filename": "File to share"
+    "help_filename": "List of files or folders to share"
 }, "no": {
     "calculating_sha1": "Kalkulerer SHA1 sjekksum.",
     "connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0}.",
diff --git a/onionshare/web.py b/onionshare/web.py
index 9f16cdc..c9e2a6a 100644
--- a/onionshare/web.py
+++ b/onionshare/web.py
@@ -1,4 +1,4 @@
-import Queue, mimetypes, platform, os, sys
+import Queue, mimetypes, platform, os, sys, zipfile
 from flask import Flask, Response, request, render_template_string, abort
 
 import strings, helpers
@@ -6,12 +6,37 @@ 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
+file_info = []
+zip_filename = None
+zip_filesize = None
+def set_file_info(filenames):
+    global file_info, zip_filename, zip_filesize
+
+    # build file info list
+    file_info = {'files':[], 'dirs':[]}
+    for filename in filenames:
+        info = {
+            'filename': filename,
+            'basename': os.path.basename(filename)
+        }
+        if os.path.isfile(filename):
+            info['size'] = os.path.getsize(filename)
+            info['size_human'] = helpers.human_readable_filesize(info['size'])
+            file_info['files'].append(info)
+        if os.path.isdir(filename):
+            info['size'] = helpers.dir_size(filename)
+            info['size_human'] = helpers.human_readable_filesize(info['size'])
+            file_info['dirs'].append(info)
+
+    # zip up the files and folders
+    z = helpers.ZipWriter()
+    for info in file_info['files']:
+        z.add_file(info['filename'])
+    for info in file_info['dirs']:
+        z.add_dir(info['filename'])
+    z.close()
+    zip_filename = z.zip_filename
+    zip_filesize = os.path.getsize(zip_filename)
 
 REQUEST_LOAD = 0
 REQUEST_DOWNLOAD = 1
@@ -58,10 +83,10 @@ def index(slug_candidate):
     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),
+        file_info=file_info,
+        filename=os.path.basename(zip_filename).decode("utf-8"),
+        filesize=zip_filesize,
+        filesize_human=helpers.human_readable_filesize(zip_filesize),
         strings=strings.strings
     )
 
@@ -83,13 +108,13 @@ def download(slug_candidate):
     # tell GUI the download started
     add_request(REQUEST_DOWNLOAD, path, { 'id':download_id })
 
-    dirname = os.path.dirname(filename)
-    basename = os.path.basename(filename)
+    dirname = os.path.dirname(zip_filename)
+    basename = os.path.basename(zip_filename)
 
     def generate():
         chunk_size = 102400 # 100kb
 
-        fp = open(filename, 'rb')
+        fp = open(zip_filename, 'rb')
         done = False
         while not done:
             chunk = fp.read(102400)
@@ -100,7 +125,7 @@ def download(slug_candidate):
 
                 # tell GUI the progress
                 downloaded_bytes = fp.tell()
-                percent = round((1.0 * downloaded_bytes / filesize) * 100, 2);
+                percent = round((1.0 * downloaded_bytes / zip_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 })
@@ -116,7 +141,7 @@ def download(slug_candidate):
             shutdown_func()
 
     r = Response(generate())
-    r.headers.add('Content-Length', filesize)
+    r.headers.add('Content-Length', zip_filesize)
     r.headers.add('Content-Disposition', 'attachment', filename=basename)
     # guess content type
     (content_type, _) = mimetypes.guess_type(basename, strict=False)

-- 
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