mirror of
https://github.com/onionshare/onionshare.git
synced 2025-04-27 02:39:18 -04:00
Add website sharing and directory listing cli-only
This commit is contained in:
parent
925cd47187
commit
2d43588a3b
@ -59,6 +59,7 @@ def main():
|
|||||||
files_in(dir, 'onionshare_gui/mode') + \
|
files_in(dir, 'onionshare_gui/mode') + \
|
||||||
files_in(dir, 'onionshare_gui/mode/share_mode') + \
|
files_in(dir, 'onionshare_gui/mode/share_mode') + \
|
||||||
files_in(dir, 'onionshare_gui/mode/receive_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, 'install/scripts') + \
|
||||||
files_in(dir, 'tests')
|
files_in(dir, 'tests')
|
||||||
pysrc = [p for p in src if p.endswith('.py')]
|
pysrc = [p for p in src if p.endswith('.py')]
|
||||||
|
@ -3,6 +3,7 @@ certifi==2019.3.9
|
|||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
Click==7.0
|
Click==7.0
|
||||||
Flask==1.0.2
|
Flask==1.0.2
|
||||||
|
Flask-HTTPAuth
|
||||||
future==0.17.1
|
future==0.17.1
|
||||||
idna==2.8
|
idna==2.8
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
|
@ -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('--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('--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('--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('--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('-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")
|
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)
|
connect_timeout = int(args.connect_timeout)
|
||||||
stealth = bool(args.stealth)
|
stealth = bool(args.stealth)
|
||||||
receive = bool(args.receive)
|
receive = bool(args.receive)
|
||||||
|
website = bool(args.website)
|
||||||
config = args.config
|
config = args.config
|
||||||
|
|
||||||
if receive:
|
if receive:
|
||||||
mode = 'receive'
|
mode = 'receive'
|
||||||
|
elif website:
|
||||||
|
mode = 'website'
|
||||||
else:
|
else:
|
||||||
mode = 'share'
|
mode = 'share'
|
||||||
|
|
||||||
@ -168,6 +172,15 @@ def main(cwd=None):
|
|||||||
print(e.args[0])
|
print(e.args[0])
|
||||||
sys.exit()
|
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':
|
if mode == 'share':
|
||||||
# Prepare files to share
|
# Prepare files to share
|
||||||
print("Compressing files.")
|
print("Compressing files.")
|
||||||
@ -206,6 +219,8 @@ def main(cwd=None):
|
|||||||
# Build the URL
|
# Build the URL
|
||||||
if common.settings.get('public_mode'):
|
if common.settings.get('public_mode'):
|
||||||
url = 'http://{0:s}'.format(app.onion_host)
|
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:
|
else:
|
||||||
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
|
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from .. import strings
|
|||||||
|
|
||||||
from .share_mode import ShareModeWeb
|
from .share_mode import ShareModeWeb
|
||||||
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
|
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
|
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
||||||
# are not applicable to OnionShare
|
# are not applicable to OnionShare
|
||||||
@ -111,13 +111,15 @@ class Web(object):
|
|||||||
self.receive_mode = None
|
self.receive_mode = None
|
||||||
if self.mode == 'receive':
|
if self.mode == 'receive':
|
||||||
self.receive_mode = ReceiveModeWeb(self.common, self)
|
self.receive_mode = ReceiveModeWeb(self.common, self)
|
||||||
|
elif self.mode == 'website':
|
||||||
|
self.website_mode = WebsiteModeWeb(self.common, self)
|
||||||
elif self.mode == 'share':
|
elif self.mode == 'share':
|
||||||
self.share_mode = ShareModeWeb(self.common, self)
|
self.share_mode = ShareModeWeb(self.common, self)
|
||||||
|
|
||||||
|
|
||||||
def define_common_routes(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)
|
@self.app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
|
154
onionshare/web/website_mode.py
Normal file
154
onionshare/web/website_mode.py
Normal 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
|
40
share/templates/listing.html
Normal file
40
share/templates/listing.html
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user