fix: XHR+FormData like main service, multipart on VM — v1.3
Deploy contracts-flask / validate (push) Successful in 0s

This commit is contained in:
2026-06-29 18:05:26 +04:00
parent 0a663645c6
commit f5f8bf294b
2 changed files with 51 additions and 48 deletions
+24 -12
View File
@@ -215,24 +215,36 @@ class Handler(BaseHTTPRequestHandler):
# ── POST /api/drhider ──────────────────────────────────────────────── # ── POST /api/drhider ────────────────────────────────────────────────
def _handle_drhider(self): def _handle_drhider(self):
"""Обфускация: JSON {files: [{name, data: base64}]} → ZIP.""" """Обфускация: multipart files → ZIP."""
from services.drhider import obfuscate_files from services.drhider import obfuscate_files
import base64 import cgi, io as io_mod
length = int(self.headers.get("Content-Length", 0)) length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length) raw = self.rfile.read(length)
ctype = self.headers.get("Content-Type", "")
body = json.loads(raw) if raw else {} # Простой multipart приём (как в upload.py)
raw_files = body.get("files", []) import cgi, io as io_mod
if not raw_files: _, params = cgi.parse_header(ctype)
self._json({"ok": False, "error": "No files"}, 400) fs = cgi.FieldStorage(
return fp=io_mod.BytesIO(raw),
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': ctype},
keep_blank_values=True
)
items = fs.getlist("files") if hasattr(fs, 'getlist') else ([fs["files"]] if "files" in fs else [])
files = [] files = []
for f in raw_files: for item in items:
name = f.get("name", "unnamed") if not getattr(item, 'filename', None):
data = base64.b64decode(f.get("data", "")) continue
files.append((name, data, f.get("type", ""))) name = os.path.basename(item.filename)
if not name or ".." in name or "/" in name or "\\" in name:
continue
data = item.file.read()
files.append((name, data, ""))
if not files:
self._json({"ok": False, "error": "Нет файлов"}, 400)
return
class _VMLLM: class _VMLLM:
def complete(self, prompt): def complete(self, prompt):
+18 -27
View File
@@ -118,46 +118,37 @@ function render() {
BTN.disabled = !files.length; BTN.disabled = !files.length;
} }
BTN.onclick = async () => { BTN.onclick = () => {
if (!files.length) return; if (!files.length) return;
BTN.disabled = true; BTN.disabled = true;
setStatus('progress', '⏳ Чтение файлов...'); setStatus('progress', '⏳ Отправка...');
try { const xhr = new XMLHttpRequest();
const payload = []; const fd = new FormData();
for (const f of files) { files.forEach(f => fd.append('files', f, f.name));
const buf = await f.arrayBuffer(); xhr.open('POST', 'https://contracts.kube5s.ru/api/drhider');
const bytes = new Uint8Array(buf); xhr.responseType = 'blob';
let b64 = '';
for (let i = 0; i < bytes.length; i += 8192) {
b64 += String.fromCharCode.apply(null, bytes.slice(i, i + 8192));
}
b64 = btoa(b64);
payload.push({name: f.name, data: b64, type: f.type || ''});
}
setStatus('progress', '⏳ Обработка... 0с');
const startTime = Date.now(); const startTime = Date.now();
const timer = setInterval(() => { const timer = setInterval(() => {
const elapsed = Math.round((Date.now() - startTime) / 1000); setStatus('progress', '⏳ Обработка... ' + Math.round((Date.now() - startTime) / 1000) + 'с');
setStatus('progress', '⏳ Обработка... ' + elapsed + 'с');
}, 1000); }, 1000);
const r = await fetch('https://contracts.kube5s.ru/api/drhider', { xhr.onload = () => {
method:'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({files: payload})
});
clearInterval(timer); clearInterval(timer);
if (!r.ok) throw new Error(await r.text()); if (xhr.status === 200) {
const blob = await r.blob();
const a = document.createElement('a'); const a = document.createElement('a');
a.href = URL.createObjectURL(blob); a.href = URL.createObjectURL(xhr.response);
a.download = 'drhider_output.zip'; a.download = 'drhider_output.zip';
a.click(); a.click();
setStatus('done', '✅ Готово!'); setStatus('done', '✅ Готово!');
} catch(e) { } else {
setStatus('error', '❌ ' + (e.message || 'Ошибка')); setStatus('error', '❌ Ошибка ' + xhr.status);
} }
BTN.disabled = false; BTN.disabled = false;
}; };
xhr.onerror = () => { clearInterval(timer); setStatus('error', '❌ Сеть'); BTN.disabled = false; };
xhr.ontimeout = () => { clearInterval(timer); setStatus('error', '❌ Таймаут'); BTN.disabled = false; };
xhr.timeout = 600000;
xhr.send(fd);
};
function setStatus(c,m) { STATUS.className = 'show '+c; STATUS.textContent = m; } function setStatus(c,m) { STATUS.className = 'show '+c; STATUS.textContent = m; }
function esc(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); } function esc(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }