diff --git a/API/Routes/Upload/UploadRoute.py b/API/Routes/Upload/UploadRoute.py index 116a3163..d0586c67 100644 --- a/API/Routes/Upload/UploadRoute.py +++ b/API/Routes/Upload/UploadRoute.py @@ -1,4 +1,6 @@ import shutil + fix/request-size-limits +from flask import Blueprint, request, jsonify, send_file, after_this_request, current_app from unittest import case from flask import Blueprint, request, jsonify, send_file, after_this_request from zipfile import ZipFile @@ -257,9 +259,29 @@ def uploadCaseUnchunked_old(): if submitted_file and allowed_filename(submitted_file): filename = secure_filename(submitted_file) + + max_len = current_app.config.get("MAX_CONTENT_LENGTH") + if max_len: + file.seek(0, os.SEEK_END) + file_length = file.tell() + file.seek(0) + if file_length > max_len: + return jsonify({"error": "File exceeds maximum allowed size"}), 413 + #spasiti zip u data storage file.save(os.path.join(Config.DATA_STORAGE, filename)) #zipfiles = [] + with ZipFile(os.path.join(Config.DATA_STORAGE, filename)) as zf: + MAX_ZIP_UNCOMPRESSED = 1 * 1024 * 1024 * 1024 # 1GB + if sum(info.file_size for info in zf.infolist()) > MAX_ZIP_UNCOMPRESSED: + too_large = True + else: + too_large = False + + if too_large: + os.remove(os.path.join(Config.DATA_STORAGE, filename)) + return jsonify({"error": "File exceeds maximum allowed size"}), 413 + with ZipFile(os.path.join(Config.DATA_STORAGE, filename)) as zf: errorcode = 1 for zippedfile in zf.namelist(): @@ -424,6 +446,17 @@ def handle_full_zip(file, filepath=None): if submitted_file and allowed_filename(submitted_file): filename = secure_filename(submitted_file) + with ZipFile(filepath) as zf: + MAX_ZIP_UNCOMPRESSED = 1 * 1024 * 1024 * 1024 # 1GB + if sum(info.file_size for info in zf.infolist()) > MAX_ZIP_UNCOMPRESSED: + too_large = True + else: + too_large = False + + if too_large: + os.remove(filepath) + return jsonify({"error": "File exceeds maximum allowed size"}), 413 + with ZipFile(filepath) as zf: errorcode = 1 @@ -555,6 +588,13 @@ def uploadCase(): # Ako nije chunked upload (chrome browser dev mode) if dz_uuid is None: + max_len = current_app.config.get("MAX_CONTENT_LENGTH") + if max_len: + file.seek(0, os.SEEK_END) + file_length = file.tell() + file.seek(0) + if file_length > max_len: + return jsonify({"error": "File exceeds maximum allowed size"}), 413 # ========================== # TVOJ ORIGINALNI KOD # ========================== @@ -570,6 +610,16 @@ def uploadCase(): chunk_dir = os.path.join(Config.DATA_STORAGE, "_chunks", dz_uuid) os.makedirs(chunk_dir, exist_ok=True) + max_len = current_app.config.get("MAX_CONTENT_LENGTH") + if max_len: + current_size = sum(os.path.getsize(os.path.join(chunk_dir, f)) for f in os.listdir(chunk_dir) if os.path.isfile(os.path.join(chunk_dir, f))) + file.seek(0, os.SEEK_END) + chunk_size = file.tell() + file.seek(0) + if current_size + chunk_size > max_len: + shutil.rmtree(chunk_dir) + return jsonify({"error": "File exceeds maximum allowed size"}), 413 + chunk_path = os.path.join(chunk_dir, f"chunk_{dz_chunk_index}") file.save(chunk_path) @@ -622,6 +672,15 @@ def uploadXls(): if submitted_file and allowed_filename_xls(submitted_file): filename = secure_filename(submitted_file) + + max_len = current_app.config.get("MAX_CONTENT_LENGTH") + if max_len: + file.seek(0, os.SEEK_END) + file_length = file.tell() + file.seek(0) + if file_length > max_len: + return jsonify({"error": "File exceeds maximum allowed size"}), 413 + #spasiti zip u data storage file.save(os.path.join(Config.DATA_STORAGE, filename)) diff --git a/API/app.py b/API/app.py index ffec865e..e00364e8 100644 --- a/API/app.py +++ b/API/app.py @@ -90,7 +90,8 @@ def log_exception(exc_type, exc_value, exc_traceback): app.permanent_session_lifetime = timedelta(days=5) app.config['SECRET_KEY'] = '12345' -app.config["MAX_CONTENT_LENGTH"] = None +UPLOAD_MAX_SIZE = int(os.getenv("UPLOAD_MAX_SIZE", 500 * 1024 * 1024)) +app.config["MAX_CONTENT_LENGTH"] = UPLOAD_MAX_SIZE app.register_blueprint(upload_api) app.register_blueprint(case_api) @@ -120,6 +121,12 @@ def add_headers(response): # response.status_code = error.status_code # return response +@app.errorhandler(413) +def request_entity_too_large(error): + return jsonify({ + "error": "Uploaded file is too large" + }), 413 + #entry point to frontend @app.route("/", methods=['GET']) def home(): diff --git a/README.md b/README.md index 001bb8b9..24586b0a 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ This repository contains the user interface for the Open Source Energy Modelling 4. The App will open automatically once the installation is complete. If not, search on the Windows Taskbar for ‘’MUIO’’ and open the App. 5. You will see the MUIO in a new window. +## Environment Configuration + +- `UPLOAD_MAX_SIZE` + Example: `UPLOAD_MAX_SIZE=524288000` + This overrides the default request size limit (500 MB) to prevent unbounded file uploads. + ## Questions and Issues For troubleshooting model-related issues and discussions, please visit the [Energy Modelling Community Discussion Forum](https://forum.u4ria.org/).