Allow sending messages as well as uploading files in receive mode

This commit is contained in:
Micah Lee 2021-04-25 20:46:03 -04:00
parent 8c9d101f31
commit b343d391b3
5 changed files with 218 additions and 108 deletions

View file

@ -199,6 +199,18 @@ def main(cwd=None):
default=None, default=None,
help="Receive files: URL to receive webhook notifications", help="Receive files: URL to receive webhook notifications",
) )
parser.add_argument(
"--disable-text",
action="store_true",
dest="disable_text",
help="Receive files: Disable receiving text messages",
)
parser.add_argument(
"--disable-files",
action="store_true",
dest="disable_files",
help="Receive files: Disable receiving files",
)
# Website args # Website args
parser.add_argument( parser.add_argument(
"--disable_csp", "--disable_csp",
@ -242,6 +254,8 @@ def main(cwd=None):
autostop_sharing = not bool(args.no_autostop_sharing) autostop_sharing = not bool(args.no_autostop_sharing)
data_dir = args.data_dir data_dir = args.data_dir
webhook_url = args.webhook_url webhook_url = args.webhook_url
disable_text = args.disable_text
disable_files = args.disable_files
disable_csp = bool(args.disable_csp) disable_csp = bool(args.disable_csp)
verbose = bool(args.verbose) verbose = bool(args.verbose)
@ -292,6 +306,8 @@ def main(cwd=None):
mode_settings.set("receive", "data_dir", data_dir) mode_settings.set("receive", "data_dir", data_dir)
if webhook_url: if webhook_url:
mode_settings.set("receive", "webhook_url", webhook_url) mode_settings.set("receive", "webhook_url", webhook_url)
mode_settings.set("receive", "disable_text", disable_text)
mode_settings.set("receive", "disable_files", disable_files)
if mode == "website": if mode == "website":
mode_settings.set("website", "disable_csp", disable_csp) mode_settings.set("website", "disable_csp", disable_csp)
else: else:
@ -334,6 +350,11 @@ def main(cwd=None):
if persistent_filename: if persistent_filename:
mode_settings.set(mode, "filenames", filenames) mode_settings.set(mode, "filenames", filenames)
# In receive mode, you must allows either text, files, or both
if mode == "receive" and disable_text and disable_files:
print(f"You cannot disable both text and files")
sys.exit()
# Create the Web object # Create the Web object
web = Web(common, False, mode_settings, mode) web = Web(common, False, mode_settings, mode)

View file

@ -285,6 +285,13 @@ ul.breadcrumbs li a:link, ul.breadcrumbs li a:visited {
margin: 0 0 20px 0; margin: 0 0 20px 0;
} }
.upload-wrapper textarea {
max-width: 95%;
width: 600px;
height: 150px;
padding: 10px;
}
div#uploads { div#uploads {
width: 800px; width: 800px;
max-width: 90%; max-width: 90%;

View file

@ -1,24 +1,35 @@
$(function(){ $(function () {
// Add a flash message // Add a flash message
var flash = function(category, message) { var flash = function (category, message) {
$('#flashes').append($('<li>').addClass(category).text(message)); $('#flashes').append($('<li>').addClass(category).text(message));
}; };
var scriptSrc = document.getElementById('receive-script').src; var scriptSrc = document.getElementById('receive-script').src;
var staticImgPath = scriptSrc.substr(0, scriptSrc.lastIndexOf( '/' )+1).replace('js', 'img'); var staticImgPath = scriptSrc.substr(0, scriptSrc.lastIndexOf('/') + 1).replace('js', 'img');
// Intercept submitting the form // Intercept submitting the form
$('#send').submit(function(event){ $('#send').submit(function (event) {
event.preventDefault(); event.preventDefault();
// Create form data, and list of filenames // Build the form data
var files = $('#file-select').get(0).files;
var filenames = [];
var formData = new FormData(); var formData = new FormData();
for(var i = 0; i < files.length; i++) {
var file = files[i]; // Files
filenames.push(file.name); var $fileSelect = $('#file-select');
formData.append('file[]', file, file.name); if ($fileSelect.length > 0) {
var files = $fileSelect.get(0).files;
var filenames = [];
for (var i = 0; i < files.length; i++) {
var file = files[i];
filenames.push(file.name);
formData.append('file[]', file, file.name);
}
}
// Text message
var $text = $('#text');
if ($text.length > 0) {
formData.append("text", $text.val())
} }
// Reset the upload form // Reset the upload form
@ -28,9 +39,9 @@ $(function(){
// have access to the the XMLHttpRequest object // have access to the the XMLHttpRequest object
var ajax = new XMLHttpRequest(); var ajax = new XMLHttpRequest();
ajax.upload.addEventListener('progress', function(event){ ajax.upload.addEventListener('progress', function (event) {
// Update progress bar for this specific upload // Update progress bar for this specific upload
if(event.lengthComputable) { if (event.lengthComputable) {
$('progress', ajax.$upload_div).attr({ $('progress', ajax.$upload_div).attr({
value: event.loaded, value: event.loaded,
max: event.total, max: event.total,
@ -39,13 +50,13 @@ $(function(){
// If it's finished sending all data to the first Tor node, remove cancel button // If it's finished sending all data to the first Tor node, remove cancel button
// and update the status // and update the status
if(event.loaded == event.total) { if (event.loaded == event.total) {
$('.cancel', ajax.$upload_div).remove(); $('.cancel', ajax.$upload_div).remove();
$('.upload-status', ajax.$upload_div).html('<img src="' + staticImgPath + '/ajax.gif" alt="" /> Waiting for data to finish traversing Tor network ...'); $('.upload-status', ajax.$upload_div).html('<img src="' + staticImgPath + '/ajax.gif" alt="" /> Waiting for data to finish traversing Tor network ...');
} }
}, false); }, false);
ajax.addEventListener('load', function(event){ ajax.addEventListener('load', function (event) {
// Remove the upload div // Remove the upload div
ajax.$upload_div.remove(); ajax.$upload_div.remove();
@ -54,38 +65,38 @@ $(function(){
var response = JSON.parse(ajax.response); var response = JSON.parse(ajax.response);
// The 'new_body' response replaces the whole HTML document and ends // The 'new_body' response replaces the whole HTML document and ends
if('new_body' in response) { if ('new_body' in response) {
$('body').html(response['new_body']); $('body').html(response['new_body']);
return; return;
} }
// Show error flashes // Show error flashes
if('error_flashes' in response) { if ('error_flashes' in response) {
for(var i=0; i<response['error_flashes'].length; i++) { for (var i = 0; i < response['error_flashes'].length; i++) {
flash('error', response['error_flashes'][i]); flash('error', response['error_flashes'][i]);
} }
} }
// Show info flashes // Show info flashes
if('info_flashes' in response) { if ('info_flashes' in response) {
for(var i=0; i<response['info_flashes'].length; i++) { for (var i = 0; i < response['info_flashes'].length; i++) {
flash('info', response['info_flashes'][i]); flash('info', response['info_flashes'][i]);
} }
} }
} catch(e) { } catch (e) {
flash('error', 'Invalid response from server: '+data); flash('error', 'Invalid response from server: ' + data);
} }
}, false); }, false);
ajax.addEventListener('error', function(event){ ajax.addEventListener('error', function (event) {
flash('error', 'Error uploading: '+filenames.join(', ')); flash('error', 'Error uploading: ' + filenames.join(', '));
// Remove the upload div // Remove the upload div
ajax.$upload_div.remove() ajax.$upload_div.remove()
}, false); }, false);
ajax.addEventListener('abort', function(event){ ajax.addEventListener('abort', function (event) {
flash('error', 'Upload aborted: '+filenames.join(', ')); flash('error', 'Upload aborted: ' + filenames.join(', '));
}, false); }, false);
// Make the upload div // Make the upload div
@ -114,7 +125,7 @@ $(function(){
) )
.append($progress); .append($progress);
$cancel_button.click(function(){ $cancel_button.click(function () {
// Abort the upload, and remove the upload div // Abort the upload, and remove the upload div
ajax.abort(); ajax.abort();
$upload_div.remove() $upload_div.remove()

View file

@ -1,46 +1,64 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<title>OnionShare</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<header class="clearfix"> <head>
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare"> <title>OnionShare</title>
<h1>OnionShare</h1> <meta charset="utf-8" />
</header> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<div class="upload-wrapper"> <body>
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<p class="upload-header">Send Files</p> <header class="clearfix">
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p> <img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
<div id="uploads"></div> <div class="upload-wrapper">
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<div> {% if not disable_text and not disable_files %}
<ul id="flashes" class="flashes"> <p class="upload-header">Submit Files or Messages</p>
{% with messages = get_flashed_messages(with_categories=true) %} <p class="upload-description">You can submit files, a message, or both</p>
{% if messages %} {% endif %}
{% for category, message in messages %} {% if not disable_text and disable_files %}
<li class="{{ category }}">{{ message }}</li> <p class="upload-header">Submit Messages</p>
{% endfor %} <p class="upload-description">You can submit a message</p>
{% endif %} {% endif %}
{% endwith %} {% if disable_text and not disable_files %}
</ul> <p class="upload-header">Submit Files</p>
</div> <p class="upload-description">You can submit files</p>
{% endif %}
<form id="send" method="post" enctype="multipart/form-data" action="/upload"> <div id="uploads"></div>
<p><input type="file" id="file-select" name="file[]" multiple /></p>
<p><button type="submit" id="send-button" class="button">Send Files</button></p>
</form>
<div>
<ul id="flashes" class="flashes">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
{% endif %}
{% endwith %}
</ul>
</div> </div>
<script src="{{ static_url_path }}/js/jquery-3.5.1.min.js"></script>
<script async src="{{ static_url_path }}/js/receive.js" id="receive-script"></script> <form id="send" method="post" enctype="multipart/form-data" action="/upload">
</body> {% if not disable_files %}
</html> <p><input type="file" id="file-select" name="file[]" multiple /></p>
{% endif %}
{% if not disable_text %}
<p><textarea id="text" name="text" placeholder="Write a message"></textarea></p>
{% endif %}
<p><button type="submit" id="send-button" class="button">Submit</button></p>
</form>
</div>
<script src="{{ static_url_path }}/js/jquery-3.5.1.min.js"></script>
<script async src="{{ static_url_path }}/js/receive.js" id="receive-script"></script>
</body>
</html>

View file

@ -64,7 +64,10 @@ class ReceiveModeWeb:
self.web.add_request(self.web.REQUEST_LOAD, request.path) self.web.add_request(self.web.REQUEST_LOAD, request.path)
r = make_response( r = make_response(
render_template( render_template(
"receive.html", static_url_path=self.web.static_url_path "receive.html",
static_url_path=self.web.static_url_path,
disable_text=self.web.settings.get("receive", "disable_text"),
disable_files=self.web.settings.get("receive", "disable_files"),
) )
) )
return self.web.add_security_headers(r) return self.web.add_security_headers(r)
@ -75,44 +78,88 @@ class ReceiveModeWeb:
Handle the upload files POST request, though at this point, the files have Handle the upload files POST request, though at this point, the files have
already been uploaded and saved to their correct locations. already been uploaded and saved to their correct locations.
""" """
files = request.files.getlist("file[]") text_received = False
filenames = [] if not self.web.settings.get("receive", "disable_text"):
for f in files: text_message = request.form.get("text")
if f.filename != "": if text_message:
filename = secure_filename(f.filename) if text_message.strip() != "":
filenames.append(filename) text_received = True
local_path = os.path.join(request.receive_mode_dir, filename) filename = "message.txt"
basename = os.path.basename(local_path) local_path = os.path.join(request.receive_mode_dir, filename)
# Tell the GUI the receive mode directory for this file with open(local_path, "w") as f:
self.web.add_request( f.write(text_message)
self.web.REQUEST_UPLOAD_SET_DIR,
request.path,
{
"id": request.history_id,
"filename": basename,
"dir": request.receive_mode_dir,
},
)
self.common.log( basename = os.path.basename(local_path)
"ReceiveModeWeb",
"define_routes", # TODO: possibly change this
f"/upload, uploaded {f.filename}, saving to {local_path}", self.web.add_request(
) self.web.REQUEST_UPLOAD_SET_DIR,
print(f"\nReceived: {local_path}") request.path,
{
"id": request.history_id,
"filename": basename,
"dir": request.receive_mode_dir,
},
)
self.common.log(
"ReceiveModeWeb",
"define_routes",
f"/upload, sent text message, saving to {local_path}",
)
print(f"\nReceived: {local_path}")
files_received = 0
if not self.web.settings.get("receive", "disable_files"):
files = request.files.getlist("file[]")
filenames = []
for f in files:
if f.filename != "":
filename = secure_filename(f.filename)
filenames.append(filename)
local_path = os.path.join(request.receive_mode_dir, filename)
basename = os.path.basename(local_path)
# Tell the GUI the receive mode directory for this file
self.web.add_request(
self.web.REQUEST_UPLOAD_SET_DIR,
request.path,
{
"id": request.history_id,
"filename": basename,
"dir": request.receive_mode_dir,
},
)
self.common.log(
"ReceiveModeWeb",
"define_routes",
f"/upload, uploaded {f.filename}, saving to {local_path}",
)
print(f"\nReceived: {local_path}")
files_received = len(filenames)
# Send webhook if configured # Send webhook if configured
if ( if (
self.web.settings.get("receive", "webhook_url") self.web.settings.get("receive", "webhook_url") is not None
and not request.upload_error and not request.upload_error
and len(files) > 0 and (text_received or files_received)
): ):
if len(files) == 1: msg = ""
file_msg = "1 file" if files_received > 0:
else: if files_received == 1:
file_msg = f"{len(files)} files" msg += "1 file"
self.send_webhook_notification(f"{file_msg} uploaded to OnionShare") else:
msg += f"{files_received} files"
if text_received:
if msg == "":
msg = "A text message"
else:
msg += " and a text message"
self.send_webhook_notification(f"{msg} submitted to OnionShare")
if request.upload_error: if request.upload_error:
self.common.log( self.common.log(
@ -140,21 +187,27 @@ class ReceiveModeWeb:
if ajax: if ajax:
info_flashes = [] info_flashes = []
if len(filenames) == 0: if files_received > 0:
msg = "No files uploaded" files_msg = ""
if ajax:
info_flashes.append(msg)
else:
flash(msg, "info")
else:
msg = "Sent "
for filename in filenames: for filename in filenames:
msg += f"{filename}, " files_msg += f"{filename}, "
msg = msg.rstrip(", ") files_msg = files_msg.rstrip(", ")
if ajax:
info_flashes.append(msg) if text_received:
if files_received > 0:
msg = f"Message submitted, uploaded {files_msg}"
else: else:
flash(msg, "info") msg = "Message submitted"
else:
if files_received > 0:
msg = f"Uploaded {files_msg}"
else:
msg = "Nothing submitted"
if ajax:
info_flashes.append(msg)
else:
flash(msg, "info")
if self.can_upload: if self.can_upload:
if ajax: if ajax: