mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'broken-uploads' into 'dev'
Fixed upload streams See merge request crafty-controller/crafty-commander!53
This commit is contained in:
commit
b24fd93487
@ -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
|
||||
|
@ -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":
|
||||
|
@ -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(
|
||||
|
79
app/classes/web/upload_handler.py
Normal file
79
app/classes/web/upload_handler.py
Normal 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)
|
@ -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) {
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user