diff --git a/app/classes/web/routes/api/crafty/upload/index.py b/app/classes/web/routes/api/crafty/upload/index.py index 7797df60..8513f719 100644 --- a/app/classes/web/routes/api/crafty/upload/index.py +++ b/app/classes/web/routes/api/crafty/upload/index.py @@ -37,7 +37,7 @@ class ApiFilesUploadHandler(BaseApiHandler): 400, {"status": "error", "error": "NOT_AUTHORIZED"} ) u_type = "server_upload" - elif auth_data[4]["superuser"] and upload_type != "import": + elif auth_data[4]["superuser"] and upload_type == "background": u_type = "admin_config" self.upload_dir = os.path.join( self.controller.project_root, @@ -62,6 +62,15 @@ class ApiFilesUploadHandler(BaseApiHandler): self.controller.project_root, "import", "upload" ) u_type = "server_import" + else: + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT_AUTHORIZED", + "data": {"message": ""}, + }, + ) # Get the headers from the request fileHash = self.request.headers.get("fileHash", 0) chunkHash = self.request.headers.get("chunk-hash", 0) @@ -145,7 +154,6 @@ class ApiFilesUploadHandler(BaseApiHandler): # File paths file_path = os.path.join(self.upload_dir, self.filename) - print(file_path) chunk_path = os.path.join( self.temp_dir, f"{self.filename}.part{self.chunk_index}" ) diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index 6285edfc..0b8140e3 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -24,7 +24,6 @@ from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers from app.classes.web.server_handler import ServerHandler from app.classes.web.websocket_handler import WebSocketHandler from app.classes.web.static_handler import CustomStaticHandler -from app.classes.web.upload_handler import UploadHandler from app.classes.web.status_handler import StatusHandler @@ -142,7 +141,6 @@ class Webserver: (r"/panel/(.*)", PanelHandler, handler_args), (r"/server/(.*)", ServerHandler, handler_args), (r"/ws", WebSocketHandler, handler_args), - (r"/upload", UploadHandler, handler_args), (r"/status", StatusHandler, handler_args), # API Routes V2 *api_handlers(handler_args), diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py deleted file mode 100644 index 747fa63b..00000000 --- a/app/classes/web/upload_handler.py +++ /dev/null @@ -1,331 +0,0 @@ -import logging -import os -import time -import urllib.parse -import tornado.web -import tornado.options -import tornado.httpserver -from app.classes.models.crafty_permissions import EnumPermissionsCrafty - -from app.classes.models.server_permissions import EnumPermissionsServer -from app.classes.shared.console import Console -from app.classes.shared.helpers import Helpers -from app.classes.shared.main_controller import Controller -from app.classes.web.base_handler import BaseHandler -from app.classes.shared.websocket_manager import WebSocketManager - -logger = logging.getLogger(__name__) - - -@tornado.web.stream_request_body -class UploadHandler(BaseHandler): - # noinspection PyAttributeOutsideInit - def initialize( - self, - helper: Helpers = None, - controller: Controller = None, - tasks_manager=None, - translator=None, - file_helper=None, - ): - self.helper = helper - self.controller = controller - self.tasks_manager = tasks_manager - self.translator = translator - self.file_helper = file_helper - - def prepare(self): - # Class & Function Defination - api_key, _token_data, exec_user = self.current_user - self.upload_type = str(self.request.headers.get("X-Content-Upload-Type")) - - if self.upload_type == "server_import": - superuser = exec_user["superuser"] - if api_key is not None: - superuser = superuser and api_key.full_access - user_id = exec_user["user_id"] - stream_size_value = self.helper.get_setting("stream_size_GB") - - max_streamed_size = (1024 * 1024 * 1024) * stream_size_value - - self.content_len = int(self.request.headers.get("Content-Length")) - if self.content_len > max_streamed_size: - logger.error( - f"User with ID {user_id} attempted to upload a file that" - f" exceeded the max body size." - ) - - return self.finish_json( - 413, - { - "status": "error", - "error": "TOO LARGE", - "info": self.helper.translation.translate( - "error", - "fileTooLarge", - self.controller.users.get_user_lang_by_id(user_id), - ), - }, - ) - self.do_upload = True - - if superuser: - exec_user_server_permissions = ( - self.controller.server_perms.list_defined_permissions() - ) - elif api_key is not None: - exec_user_server_permissions = ( - self.controller.crafty_perms.get_api_key_permissions_list(api_key) - ) - else: - exec_user_server_permissions = ( - self.controller.crafty_perms.get_crafty_permissions_list( - exec_user["user_id"] - ) - ) - - 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 ( - EnumPermissionsCrafty.SERVER_CREATION - not in exec_user_server_permissions - and not exec_user["superuser"] - ): - logger.warning( - f"User {user_id} tried to upload a server" " without permissions!" - ) - Console.warning( - f"User {user_id} tried to upload a server" " without permissions!" - ) - self.do_upload = False - - path = os.path.join(self.controller.project_root, "import", "upload") - self.helper.ensure_dir_exists(path) - # Delete existing files - if len(os.listdir(path)) > 0: - for item in os.listdir(): - try: - os.remove(os.path.join(path, item)) - except: - logger.debug("Could not delete file on user server upload") - - self.helper.ensure_dir_exists(path) - filename = urllib.parse.unquote( - self.request.headers.get("X-FileName", None) - ) - if not str(filename).endswith(".zip"): - WebSocketManager().broadcast("close_upload_box", "error") - self.finish("error") - full_path = os.path.join(path, filename) - - if self.do_upload: - try: - self.f = open(full_path, "wb") - except Exception as e: - logger.error(f"Upload failed with error: {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) - - elif self.upload_type == "background": - superuser = exec_user["superuser"] - if api_key is not None: - superuser = superuser and api_key.full_access - user_id = exec_user["user_id"] - stream_size_value = self.helper.get_setting("stream_size_GB") - - max_streamed_size = (1024 * 1024 * 1024) * stream_size_value - - self.content_len = int(self.request.headers.get("Content-Length")) - if self.content_len > max_streamed_size: - logger.error( - f"User with ID {user_id} attempted to upload a file that" - f" exceeded the max body size." - ) - - return self.finish_json( - 413, - { - "status": "error", - "error": "TOO LARGE", - "info": self.helper.translation.translate( - "error", - "fileTooLarge", - self.controller.users.get_user_lang_by_id(user_id), - ), - }, - ) - self.do_upload = True - - if not superuser: - return self.finish_json( - 401, - { - "status": "error", - "error": "UNAUTHORIZED ACCESS", - "info": self.helper.translation.translate( - "error", - "superError", - self.controller.users.get_user_lang_by_id(user_id), - ), - }, - ) - if not self.request.headers.get("X-Content-Type", None).startswith( - "image/" - ): - return self.finish_json( - 415, - { - "status": "error", - "error": "TYPE ERROR", - "info": self.helper.translation.translate( - "error", - "fileError", - self.controller.users.get_user_lang_by_id(user_id), - ), - }, - ) - 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 - - path = os.path.join( - self.controller.project_root, - "app/frontend/static/assets/images/auth/custom", - ) - filename = self.request.headers.get("X-FileName", None) - full_path = os.path.join(path, filename) - - if self.do_upload: - try: - self.f = open(full_path, "wb") - except Exception as e: - logger.error(f"Upload failed with error: {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) - else: - server_id = self.get_argument("server_id", None) - superuser = exec_user["superuser"] - if api_key is not None: - superuser = superuser and api_key.full_access - user_id = exec_user["user_id"] - stream_size_value = self.helper.get_setting("stream_size_GB") - - max_streamed_size = (1024 * 1024 * 1024) * stream_size_value - - self.content_len = int(self.request.headers.get("Content-Length")) - if self.content_len > max_streamed_size: - logger.error( - f"User with ID {user_id} attempted to upload a file that" - f" exceeded the max body size." - ) - - return self.finish_json( - 413, - { - "status": "error", - "error": "TOO LARGE", - "info": self.helper.translation.translate( - "error", - "fileTooLarge", - self.controller.users.get_user_lang_by_id(user_id), - ), - }, - ) - self.do_upload = True - - if superuser: - exec_user_server_permissions = ( - self.controller.server_perms.list_defined_permissions() - ) - elif api_key is not None: - exec_user_server_permissions = ( - self.controller.server_perms.get_api_key_permissions_list( - api_key, server_id - ) - ) - else: - exec_user_server_permissions = ( - self.controller.server_perms.get_user_id_permissions_list( - exec_user["user_id"], server_id - ) - ) - - server_id = self.request.headers.get("X-ServerId", None) - 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 - - 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 EnumPermissionsServer.FILES not in exec_user_server_permissions: - logger.warning( - f"User {user_id} tried to upload a file to " - f"{server_id} without permissions!" - ) - Console.warning( - f"User {user_id} tried to upload a file to " - f"{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 self.helper.is_subdir( - full_path, - Helpers.get_os_understandable_path( - self.controller.servers.get_server_data_by_id(server_id)["path"] - ), - ): - logger.warning( - f"User {user_id} tried to upload a file to {server_id} " - f"but the path is not inside of the server!" - ) - Console.warning( - f"User {user_id} tried to upload a file to {server_id} " - f"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(f"Upload failed with error: {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.upload_type == "server_files": - files_left = int(self.request.headers.get("X-Files-Left", None)) - else: - files_left = 0 - - if self.do_upload: - time.sleep(5) - if files_left == 0: - WebSocketManager().broadcast("close_upload_box", "success") - self.finish("success") # Nope, I'm sending "success" - self.f.close() - else: - time.sleep(5) - if files_left == 0: - WebSocketManager().broadcast("close_upload_box", "error") - self.finish("error") - - def data_received(self, chunk): - if self.do_upload: - self.f.write(chunk) diff --git a/app/frontend/static/assets/js/shared/upload.js b/app/frontend/static/assets/js/shared/upload.js new file mode 100644 index 00000000..d6877570 --- /dev/null +++ b/app/frontend/static/assets/js/shared/upload.js @@ -0,0 +1,89 @@ +async function uploadFile(type) { + file = $("#file")[0].files[0] + const fileId = uuidv4(); + const token = getCookie("_xsrf") + document.getElementById("upload_input").innerHTML = '
 
' + if (!file) { + alert("Please select a file first."); + return; + } + + const chunkSize = 1024 * 1024; // 1MB + const totalChunks = Math.ceil(file.size / chunkSize); + + const uploadPromises = []; + let res = await fetch(`/api/v2/servers/import/upload/`, { + method: 'POST', + headers: { + 'X-XSRFToken': token, + 'chunked': true, + 'fileSize': file.size, + 'type': type, + 'total_chunks': totalChunks, + 'filename': file.name, + 'fileId': fileId, + }, + body: null, + }); + + let responseData = await res.json(); + + let file_id = "" + if (responseData.status === "ok") { + file_id = responseData.data["file-id"] + } + for (let i = 0; i < totalChunks; i++) { + const start = i * chunkSize; + const end = Math.min(start + chunkSize, file.size); + const chunk = file.slice(start, end); + + const uploadPromise = fetch(`/api/v2/servers/import/upload/`, { + method: 'POST', + body: chunk, + headers: { + 'Content-Range': `bytes ${start}-${end - 1}/${file.size}`, + 'Content-Length': chunk.size, + 'fileSize': file.size, + 'chunked': true, + 'type': type, + 'total_chunks': totalChunks, + 'filename': file.name, + 'fileId': fileId, + 'chunkId': i, + }, + }).then(response => response.json()) + .then(data => { + if (data.status === "completed") { + $("#upload_input").html(`
🔒
`); + document.getElementById("lower_half").style.visibility = "visible"; + document.getElementById("lower_half").hidden = false; + } else if (data.status !== "partial") { + throw new Error(data.message); + } + // Update progress bar + const progress = (i + 1) / totalChunks * 100; + updateProgressBar(Math.round(progress)); + }); + + uploadPromises.push(uploadPromise); + } + + try { + await Promise.all(uploadPromises); + } catch (error) { + alert("Error uploading file: " + error.message); + } +} + +function updateProgressBar(progress) { + $(`#upload-progress-bar`).css('width', progress + '%'); + $(`#upload-progress-bar`).html(progress + '%'); +} + +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0, + v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} \ No newline at end of file diff --git a/app/frontend/templates/panel/custom_login.html b/app/frontend/templates/panel/custom_login.html index 4eb22ca8..84af79e6 100644 --- a/app/frontend/templates/panel/custom_login.html +++ b/app/frontend/templates/panel/custom_login.html @@ -69,7 +69,7 @@
+ onclick="uploadFile('background')" disabled>UPLOAD
@@ -381,61 +381,6 @@ } img.src = src_path; } - - var file; - function sendFile() { - file = $("#file")[0].files[0] - document.getElementById("upload_input").innerHTML = '
 
'; - let xmlHttpRequest = new XMLHttpRequest(); - let token = getCookie("_xsrf") - let fileName = file.name - let target = '/upload' - let mimeType = file.type - let size = file.size - let type = 'background' - - xmlHttpRequest.upload.addEventListener('progress', function (e) { - - if (e.loaded <= size) { - var percent = Math.round(e.loaded / size * 100); - $(`#upload-progress-bar`).css('width', percent + '%'); - $(`#upload-progress-bar`).html(percent + '%'); - } - }); - - xmlHttpRequest.open('POST', target, true); - xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); - xmlHttpRequest.setRequestHeader('X-XSRFToken', token); - xmlHttpRequest.setRequestHeader('X-Content-Length', size); - xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"'); - xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type); - xmlHttpRequest.setRequestHeader('X-FileName', fileName); - xmlHttpRequest.addEventListener('load', (event) => { - if (event.target.responseText == 'success') { - console.log('Upload for file', file.name, 'was successful!') - document.getElementById("upload_input").innerHTML = '
' + fileName + ' 🔒
'; - setTimeout(function () { - window.location.reload(); - }, 2000); - } - else { - let response_text = JSON.parse(event.target.responseText); - var x = document.querySelector('.bootbox'); - console.log(JSON.parse(event.target.responseText).info) - bootbox.alert({ - message: JSON.parse(event.target.responseText).info, - callback: function () { - window.location.reload(); - } - }); - doUpload = false; - } - }, false); - xmlHttpRequest.addEventListener('error', (e) => { - console.error('Error while uploading file', file.name + '.', 'Event:', e) - }, false); - xmlHttpRequest.send(file); - } - + {% end %} \ No newline at end of file diff --git a/app/frontend/templates/server/bedrock_wizard.html b/app/frontend/templates/server/bedrock_wizard.html index 4b11e9d2..66f0d70b 100644 --- a/app/frontend/templates/server/bedrock_wizard.html +++ b/app/frontend/templates/server/bedrock_wizard.html @@ -318,8 +318,8 @@ 'labelZipFile', data['lang']) }}
-
@@ -523,61 +523,8 @@ {% end %} {% block js%} +