v1.0.123: fix ZIP — cgi.FieldStorage for multipart, CORS on all responses, revert Lucee unzip

This commit is contained in:
2026-06-23 08:24:35 +04:00
parent f19e0c970f
commit bb2811abb0
3 changed files with 42 additions and 154 deletions
+37 -23
View File
@@ -279,37 +279,40 @@ class Handler(BaseHTTPRequestHandler):
def _handle_unzip_upload(self):
"""Распаковать ZIP и загрузить каждый файл в Lucee. Только файлы из корня архива."""
import zipfile, io
import zipfile, io, cgi, tempfile
# Читаем multipart form data
# CORS preflight уже обработан nginx, но на всякий случай
self._send_cors()
# Парсим multipart через cgi.FieldStorage
content_type = self.headers.get("Content-Type", "")
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
# Извлекаем ZIP из multipart
boundary = content_type.split("boundary=")[1].strip()
parts = body.split(b"--" + boundary.encode())
zip_data = None
for part in parts:
if b"filename=" in part:
# Отделяем заголовки от тела
header_end = part.find(b"\r\n\r\n")
if header_end > 0:
zip_data = part[header_end+4:]
# Убираем trailing \r\n и boundary
if zip_data.endswith(b"\r\n"):
zip_data = zip_data[:-2]
break
# Сохраняем тело во временный файл для cgi.FieldStorage
fp = tempfile.TemporaryFile()
fp.write(body)
fp.seek(0)
if not zip_data:
self.send_error(400, "no file in request")
env = os.environ.copy()
env["REQUEST_METHOD"] = "POST"
env["CONTENT_TYPE"] = content_type
env["CONTENT_LENGTH"] = str(length)
form = cgi.FieldStorage(fp=fp, environ=env, keep_blank_values=True)
file_item = form.getfirst("file") or form.getfirst("files")
if not file_item or not file_item.file:
self._send_error_cors(400, "no file in request")
fp.close()
return
zip_data = file_item.file.read()
fp.close()
results = []
try:
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
for name in zf.namelist():
# Только файлы из корня (без папок), пропускаем директории
if name.endswith('/') or '/' in name:
continue
content = zf.read(name)
@@ -317,7 +320,6 @@ class Handler(BaseHTTPRequestHandler):
if ext not in ('docx', 'doc', 'pdf'):
continue
# Загрузить в Lucee (hex с \x префиксом для PostgreSQL bytea)
content_hex = "\\x" + content.hex()
resp = httpx.post(
f"{LUCEE_URL}/upload.cfm",
@@ -339,18 +341,30 @@ class Handler(BaseHTTPRequestHandler):
results.append({"filename": name, "error": f"HTTP {resp.status_code}"})
except zipfile.BadZipFile:
self.send_error(400, "not a valid ZIP file")
self._send_error_cors(400, "not a valid ZIP file")
return
except Exception as e:
self.send_error(500, str(e))
self._send_error_cors(500, str(e))
return
self.send_response(200)
self._send_cors()
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(json.dumps({"ok": True, "files": results}, ensure_ascii=False).encode())
def _send_cors(self):
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "*")
def _send_error_cors(self, code, message):
self.send_response(code)
self._send_cors()
self.send_header("Content-Type", "application/json; charset=utf-8")
self.end_headers()
self.wfile.write(json.dumps({"ok": False, "error": message}, ensure_ascii=False).encode())
def _handle_doc_convert(self):
length = int(self.headers.get("Content-Length", 0))
data = self.rfile.read(length)