diff --git a/deploy/convert_server.py b/deploy/convert_server.py index 24d01c5..35dd2b5 100755 --- a/deploy/convert_server.py +++ b/deploy/convert_server.py @@ -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) diff --git a/index.cfm b/index.cfm index ef3e648..0992a9f 100644 --- a/index.cfm +++ b/index.cfm @@ -75,7 +75,7 @@