mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-13 16:29:31 -05: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/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')]
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
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…
Reference in New Issue
Block a user