Merge branch 'broken-uploads' into 'dev'

Fixed upload streams

See merge request crafty-controller/crafty-commander!53
This commit is contained in:
Andrew 2021-08-22 20:37:02 +00:00
commit b24fd93487
7 changed files with 217 additions and 83 deletions

View File

@ -578,7 +578,16 @@ class Helpers:
return output
@staticmethod
def in_path(x, y):
def in_path(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
parent_path = os.path.abspath(parent_path)
child_path = os.path.abspath(child_path)
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
@staticmethod
def in_path_old(x, y):
return os.path.abspath(y).__contains__(os.path.abspath(x))
@staticmethod

View File

@ -197,48 +197,6 @@ class AjaxHandler(BaseHandler):
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
return
elif page == "upload_files":
server_id = self.get_argument('id', None)
path = self.get_argument('path', None)
files = self.request.files['files']
upload_thread = threading.Thread(target=self.do_upload, daemon=True, name=files[0]['filename'],
args=(server_id, path, files))
upload_thread.start()
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
def do_upload(self, server_id, path, files):
if helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], path):
try:
for file in files:
if file['filename'].split('.') is not None:
self._upload_file(file['body'], path, file['filename'])
else:
logger.error("Directory Detected. Skipping")
except Exception as e:
logger.error("Error while uploading files: {}".format(e))
else:
logger.error("Invalid directory requested. Canceling upload")
return
def _upload_file(self, file_data, file_path, file_name):
error = ""
file_full_path = os.path.join(file_path, file_name)
if os.path.exists(file_full_path):
error = "A file with this name already exists."
if not helper.check_writeable(file_path):
error = "Unwritable Path"
if error != "":
logger.error("Unable to save uploaded file due to: {}".format(error))
return False
output_file = open(file_full_path, 'wb')
output_file.write(file_data)
logger.info('Saving File: {}'.format(file_full_path))
return True
@tornado.web.authenticated
def delete(self, page):
if page == "del_file":

View File

@ -27,6 +27,7 @@ try:
from app.classes.web.websocket_handler import SocketHandler
from app.classes.web.static_handler import CustomStaticHandler
from app.classes.shared.translation import translation
from app.classes.web.upload_handler import UploadHandler
except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
@ -129,6 +130,7 @@ class Webserver:
(r'/api/stats/servers', ServersStats, handler_args),
(r'/api/stats/node', NodeStats, handler_args),
(r'/ws', SocketHandler, handler_args),
(r'/upload', UploadHandler),
]
app = tornado.web.Application(

View File

@ -0,0 +1,79 @@
import tornado.options
import tornado.web
import tornado.httpserver
from tornado.options import options
from app.classes.shared.models import db_helper, Enum_Permissions
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.console import console
import logging
import os
import json
import time
logger = logging.getLogger(__name__)
# Class&Function Defination
MAX_STREAMED_SIZE = 1024 * 1024 * 1024
@tornado.web.stream_request_body
class UploadHandler(tornado.web.RequestHandler):
def prepare(self):
self.do_upload = True
user_data = json.loads(self.get_secure_cookie('user_data'))
user_id = user_data['user_id']
server_id = self.request.headers.get('X-ServerId', None)
if user_id is None:
logger.warning('User ID not found in upload handler call')
console.warning('User ID not found in upload handler call')
self.do_upload = False
if server_id is None:
logger.warning('Server ID not found in upload handler call')
console.warning('Server ID not found in upload handler call')
self.do_upload = False
user_permissions = db_helper.get_user_permissions_list(user_id, server_id)
if Enum_Permissions.Files not in user_permissions:
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
self.do_upload = False
path = self.request.headers.get('X-Path', None)
filename = self.request.headers.get('X-FileName', None)
full_path = os.path.join(path, filename)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], full_path):
print(user_id, server_id, db_helper.get_server_data_by_id(server_id)['path'], full_path)
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
self.do_upload = False
if self.do_upload:
try:
self.f = open(full_path, "wb")
except Exception as e:
logger.error("Upload failed with error: {}".format(e))
self.do_upload = False
# If max_body_size is not set, you cannot upload files > 100MB
self.request.connection.set_max_body_size(MAX_STREAMED_SIZE)
def post(self):
logger.info("Upload completed")
if self.do_upload:
time.sleep(5)
websocket_helper.broadcast('close_upload_box', 'success')
self.finish('success') # Nope, I'm sending "success"
self.f.close()
else:
time.sleep(5)
websocket_helper.broadcast('close_upload_box', 'error')
self.finish('error')
def data_received(self, data):
if self.do_upload:
self.f.write(data)

View File

@ -321,6 +321,10 @@
regex: /^properties$/,
replaceWith: 'ace/mode/properties'
},
{
regex: /^log$/,
replaceWith: 'ace/mode/txt'
},
];
var filePath = '';
@ -520,36 +524,115 @@
});
window.location.href = "/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files"
}
function uploadFilesE(event){
function sendFile(file, path, server_id, onProgress){
var xmlHttpRequest = new XMLHttpRequest();
var token = getCookie("_xsrf")
var fileName = file.name
var target = '/upload?server_id=' + server_id
var mimeType = file.type
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Path', path);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.setRequestHeader('X-ServerId', "{{ data['server_stats']['server_id']['server_id'] }}");
xmlHttpRequest.upload.addEventListener('progress', (event) =>
onProgress(Math.floor(event.loaded / event.total * 100)), false);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
var uploadWaitDialog;
var doUpload = true;
function uploadFilesE(event){
path = event.target.parentElement.getAttribute('data-path');
console.log("PATH: " + path);
$(function () {
server_id = {{ data['server_stats']['server_id']['server_id'] }};
var uploadHtml = "<div>" +
'<form id="upload_file" enctype="multipart/form-data" action="/ajax/upload_files?id=' + server_id +'&path='+ path +'"method="post">{% raw xsrf_form_html() %}'+"<label class='upload-area' style='width:100%;text-align:center;' for='files'>" +
"<input id='files' name='files' type='file' style='display:none;' multiple='true'>" +
"<i class='fa fa-cloud-upload fa-3x'></i>" +
"<br />" +
"Click Here To Upload" +
"</label></form>" +
"<br />" +
"<ul style='margin-left:5px !important;' id='fileList'></ul>" +
"</div><div class='clearfix'></div>";
bootbox.dialog({
message: uploadHtml,
title: "File Upload",
buttons: {
success: {
label: "Upload",
className: "btn-default",
callback: function () {
$('#upload_file').submit(); //.trigger('submit');
}
}
}
$(function () {
server_id = {{ data['server_stats']['server_id']['server_id'] }};
var uploadHtml = "<div>" +
'<form id="upload_file" enctype="multipart/form-data">'+"<label class='upload-area' style='width:100%;text-align:center;' for='files'>" +
"<input id='files' name='files' type='file' style='display:none;' multiple='true'>" +
"<i class='fa fa-cloud-upload fa-3x'></i>" +
"<br />" +
"Click Here To Select Files" +
"</label></form>" +
"<br />" +
"<ul style='margin-left:5px !important;' id='fileList'></ul>" +
"</div><div class='clearfix'></div>";
bootbox.dialog({
message: uploadHtml,
title: "Upload Files to : "+path,
buttons: {
success: {
label: "Upload",
className: "btn-default",
callback: async function () {
files = document.getElementById("files");
uploadWaitDialog = bootbox.dialog({
message: `
<p class="text-center mb-0">
<i class="fa fa-spin fa-cog"></i>
Please wait while we upload your files... This may take a while.<br>
<strong>DO NOT CLOSE THIS PAGE.</strong>
</p>
<div class="progress" id="upload-progress-bar-parent">
</div>
`,
closeButton: false
});
let nFiles = files.files.length;
for(i=0; i < files.files.length; i++) {
if (!doUpload) {
doUpload = true;
hideUploadBox();
break;
}
console.log(files.files[i]);
const progressHtml = `
<div>
${path + '/' + files.files[i]}:
<div
id="upload-progress-bar-${i + 1}"
class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
style="width: 0%"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
`;
$('#upload-progress-bar-parent').append(progressHtml);
sendFile(files.files[i], path, server_id, (progress) => {
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%')
});
}
hideUploadBox();
//$('#upload_file').submit(); //.trigger('submit');
}
}
}
});
var fileList = document.getElementById("files");
fileList.addEventListener("change", function (e) {
var list = "";
@ -561,18 +644,6 @@
}, false);
});
}
function uploadFiles(e){
path = event.target.parentElement.getAttribute('data-path');
server_id = {{ data['server_stats']['server_id']['server_id'] }};
var uploadHtml = '<form enctype="multipart/form-data" action="/ajax/upload_files?id=' + server_id +'&path='+ path +'"method="post">{% raw xsrf_form_html() %}<div class="form-group">'+"<label class='upload-area' style='width:100%;text-align:center;' for='fupload'>" +'<input id="files" type="file" name="files" multiple>' +"<i class='fa fa-cloud-upload fa-3x'></i>" +"<br />" +"Upload Files Here" +"</label>" +"<br />" +"<span style='margin-left:5px !important;' id='fileList'></span>"+'</div><br><br><input id="upload_file" type="submit"value="Upload File" class="btn btn-success hidden"/></form>';
bootbox.dialog({
message: uploadHtml,
title: "Upload Files To "+path,
});
}
function getTreeView() {
$.ajax({
type: "GET",
@ -672,12 +743,25 @@
document.addEventListener('click', function(e){
let inside = (e.target.closest('#files-tree-nav'));
let contextMenu = document.getElementById('files-tree-nav');
if(!inside){
let contextMenu = document.getElementById('files-tree-nav');
contextMenu.setAttribute('style', 'display:none');
}else{
contextMenu.setAttribute('style', 'display:none');
}
});
if (webSocket) {
webSocket.on('close_upload_box', function (close_upload_box) {
hideUploadBox();
});
}
function hideUploadBox(){
if (!uploadWaitDialog) return;
uploadWaitDialog.modal('hide');
getTreeView();
}
function createFileE(event) {
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion') %}", function(result) {

1
chat.txt Normal file
View File

@ -0,0 +1 @@
Sure

View File

@ -16,10 +16,11 @@ pyminifier==2.1
pyOpenSSL==19.1.0
pyparsing==2.4.7
PyYAML==5.3.1
requests==2.24.0
requests~=2.26.0
schedule==0.6.0
six==1.15.0
termcolor==1.1.0
tornado==6.0.4
urllib3==1.25.10
webencodings==0.5.1
toro~=1.0.1