Add website sharing and directory listing cli-only

This commit is contained in:
hiro 2019-04-19 14:25:42 +02:00
parent 925cd47187
commit 2d43588a3b
6 changed files with 215 additions and 2 deletions

View File

@ -59,6 +59,7 @@ def main():
files_in(dir, 'onionshare_gui/mode') + \
files_in(dir, 'onionshare_gui/mode/share_mode') + \
files_in(dir, 'onionshare_gui/mode/receive_mode') + \
files_in(dir, 'onionshare_gui/mode/website_mode') + \
files_in(dir, 'install/scripts') + \
files_in(dir, 'tests')
pysrc = [p for p in src if p.endswith('.py')]

View File

@ -3,6 +3,7 @@ certifi==2019.3.9
chardet==3.0.4
Click==7.0
Flask==1.0.2
Flask-HTTPAuth
future==0.17.1
idna==2.8
itsdangerous==1.1.0

View File

@ -51,6 +51,7 @@ def main(cwd=None):
parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)")
parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)")
parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them")
parser.add_argument('--website', action='store_true', dest='website', help=strings._("help_website"))
parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share")
@ -68,10 +69,13 @@ def main(cwd=None):
connect_timeout = int(args.connect_timeout)
stealth = bool(args.stealth)
receive = bool(args.receive)
website = bool(args.website)
config = args.config
if receive:
mode = 'receive'
elif website:
mode = 'website'
else:
mode = 'share'
@ -168,6 +172,15 @@ def main(cwd=None):
print(e.args[0])
sys.exit()
if mode == 'website':
# Prepare files to share
print(strings._("preparing_website"))
try:
web.website_mode.set_file_info(filenames)
except OSError as e:
print(e.strerror)
sys.exit(1)
if mode == 'share':
# Prepare files to share
print("Compressing files.")
@ -206,6 +219,8 @@ def main(cwd=None):
# Build the URL
if common.settings.get('public_mode'):
url = 'http://{0:s}'.format(app.onion_host)
elif mode == 'website':
url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host)
else:
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)

View File

@ -15,7 +15,7 @@ from .. import strings
from .share_mode import ShareModeWeb
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
from .website_mode import WebsiteModeWeb
# Stub out flask's show_server_banner function, to avoiding showing warnings that
# are not applicable to OnionShare
@ -111,13 +111,15 @@ class Web(object):
self.receive_mode = None
if self.mode == 'receive':
self.receive_mode = ReceiveModeWeb(self.common, self)
elif self.mode == 'website':
self.website_mode = WebsiteModeWeb(self.common, self)
elif self.mode == 'share':
self.share_mode = ShareModeWeb(self.common, self)
def define_common_routes(self):
"""
Common web app routes between sending and receiving
Common web app routes between sending, receiving and website modes.
"""
@self.app.errorhandler(404)
def page_not_found(e):

View File

@ -0,0 +1,154 @@
import os
import sys
import tempfile
import mimetypes
from flask import Response, request, render_template, make_response, send_from_directory
from flask_httpauth import HTTPBasicAuth
from .. import strings
class WebsiteModeWeb(object):
"""
All of the web logic for share mode
"""
def __init__(self, common, web):
self.common = common
self.common.log('WebsiteModeWeb', '__init__')
self.web = web
self.auth = HTTPBasicAuth()
# Information about the file to be shared
self.file_info = []
self.website_folder = ''
self.download_filesize = 0
self.visit_count = 0
self.users = { }
self.define_routes()
def define_routes(self):
"""
The web app routes for sharing a website
"""
@self.auth.get_password
def get_pw(username):
self.users['onionshare'] = self.web.slug
if self.common.settings.get('public_mode'):
return True # let the request through, no questions asked!
elif username in self.users:
return self.users.get(username)
else:
return None
@self.web.app.route('/download/<path:page_path>')
@self.auth.login_required
def path_download(page_path):
return path_download(page_path)
@self.web.app.route('/<path:page_path>')
@self.auth.login_required
def path_public(page_path):
return path_logic(page_path)
@self.web.app.route("/")
@self.auth.login_required
def index_public():
return path_logic('')
def path_download(file_path=''):
"""
Render the download links.
"""
self.web.add_request(self.web.REQUEST_LOAD, request.path)
if not os.path.isfile(os.path.join(self.website_folder, file_path)):
return self.web.error404()
return send_from_directory(self.website_folder, file_path)
def path_logic(page_path=''):
"""
Render the onionshare website.
"""
self.web.add_request(self.web.REQUEST_LOAD, request.path)
if self.file_info['files']:
self.website_folder = os.path.dirname(self.file_info['files'][0]['filename'])
elif self.file_info['dirs']:
self.website_folder = self.file_info['dirs'][0]['filename']
else:
return self.web.error404()
if any((fname == 'index.html') for fname in os.listdir(self.website_folder)):
self.web.app.static_url_path = self.website_folder
self.web.app.static_folder = self.website_folder
if not os.path.isfile(os.path.join(self.website_folder, page_path)):
page_path = os.path.join(page_path, 'index.html')
return send_from_directory(self.website_folder, page_path)
elif any(os.path.isfile(os.path.join(self.website_folder, i)) for i in os.listdir(self.website_folder)):
filenames = []
for i in os.listdir(self.website_folder):
filenames.append(os.path.join(self.website_folder, i))
self.set_file_info(filenames)
r = make_response(render_template(
'listing.html',
file_info=self.file_info,
filesize=self.download_filesize,
filesize_human=self.common.human_readable_filesize(self.download_filesize)))
return self.web.add_security_headers(r)
else:
return self.web.error404()
def set_file_info(self, filenames, processed_size_callback=None):
"""
Using the list of filenames being shared, fill in details that the web
page will need to display. This includes zipping up the file in order to
get the zip file's name and size.
"""
self.common.log("WebsiteModeWeb", "set_file_info")
self.web.cancel_compression = True
self.cleanup_filenames = []
# build file info list
self.file_info = {'files': [], 'dirs': []}
for filename in filenames:
info = {
'filename': filename,
'basename': os.path.basename(filename.rstrip('/'))
}
if os.path.isfile(filename):
info['size'] = os.path.getsize(filename)
info['size_human'] = self.common.human_readable_filesize(info['size'])
self.file_info['files'].append(info)
if os.path.isdir(filename):
info['size'] = self.common.dir_size(filename)
info['size_human'] = self.common.human_readable_filesize(info['size'])
self.file_info['dirs'].append(info)
self.download_filesize += info['size']
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
# Check if there's only 1 file and no folders
if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0:
self.download_filename = self.file_info['files'][0]['filename']
self.download_filesize = self.file_info['files'][0]['size']
self.download_filesize = os.path.getsize(self.download_filename)
return True

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>OnionShare</title>
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
<link href="/static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<header class="clearfix">
<div class="right">
<ul>
<li>Total size: <strong>{{ filesize_human }}</strong></li>
</ul>
</div>
<img class="logo" src="/static/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
<table class="file-list" id="file-list">
<tr>
<th id="filename-header">Filename</th>
<th id="size-header">Size</th>
<th></th>
</tr>
{% for info in file_info.files %}
<tr>
<td>
<img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
{{ info.basename }}
</td>
<td>{{ info.size_human }}</td>
<td><a href="/download/{{ info.basename }}">download</td>
</tr>
{% endfor %}
</table>
<script src="/static/js/send.js"></script>
</body>
</html>