v1.0.176: upload.py — path traversal + size limit + UUID + data loss

This commit is contained in:
2026-06-24 18:01:18 +04:00
parent 60dc41e83f
commit 666f85b743
+27 -4
View File
@@ -1,12 +1,24 @@
"""Upload service — accept file, store to DB, parse.""" """Upload service — accept file, store to DB, parse."""
import uuid, base64, json, io, cgi import uuid, base64, json, io, cgi, os
from db import documents, contracts, supplements from db import documents, contracts, supplements
from .parse import parse_file from .parse import parse_file
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100 MB
def handle_upload(rfile, content_type, content_length): def handle_upload(rfile, content_type, content_length):
"""Parse multipart upload, store in DB, return result dict.""" """Parse multipart upload, store in DB, return result dict."""
body = rfile.read(content_length) # Validate content_length
try:
cl = int(content_length)
except (TypeError, ValueError):
return {"ok": False, "error": "invalid content-length"}
if cl <= 0:
return {"ok": False, "error": "empty request"}
if cl > MAX_FILE_SIZE:
return {"ok": False, "error": f"file too large (max {MAX_FILE_SIZE // 1024 // 1024}MB)"}
body = rfile.read(cl)
environ = { environ = {
"REQUEST_METHOD": "POST", "REQUEST_METHOD": "POST",
@@ -23,13 +35,21 @@ def handle_upload(rfile, content_type, content_length):
item = fs["files"] item = fs["files"]
if isinstance(item, list): if isinstance(item, list):
item = item[0] item = item[0]
filename = item.filename filename = os.path.basename(item.filename) if item.filename else None
if filename and (".." in filename or "/" in filename or "\\" in filename):
return {"ok": False, "error": "invalid filename"}
file_data = item.file.read() if hasattr(item, "file") else item.value file_data = item.file.read() if hasattr(item, "file") else item.value
if isinstance(file_data, str): if isinstance(file_data, str):
file_data = file_data.encode("utf-8") file_data = file_data.encode("utf-8")
if "contract_id" in fs: if "contract_id" in fs:
contract_id = fs.getfirst("contract_id", "") contract_id = fs.getfirst("contract_id", "")
# Validate UUID
if contract_id:
try:
uuid.UUID(contract_id)
except (ValueError, AttributeError):
contract_id = ""
batch_id = fs.getfirst("batch_id", None) if "batch_id" in fs else None batch_id = fs.getfirst("batch_id", None) if "batch_id" in fs else None
# Validate UUID # Validate UUID
@@ -46,7 +66,10 @@ def handle_upload(rfile, content_type, content_length):
b64 = base64.b64encode(file_data).decode() b64 = base64.b64encode(file_data).decode()
if contract_id: if contract_id:
supplements.delete_by_document(contract_id, filename) try:
supplements.delete_by_document(contract_id, filename)
except Exception:
pass # old record may not exist or FK issue — proceed with insert
doc = documents.insert(filename, mime, b64, batch_id=batch_id) doc = documents.insert(filename, mime, b64, batch_id=batch_id)