v2.0.10: загрузка через ВМ-буфер (паттерн drhider)
Deploy contracts-flask / validate (push) Canceled after 0s

- /api/upload_refs: pull файлов с ВМ (SSRF-guard, лимит 50МБ, ретраи, DELETE)
- uploadFile: PUT на WebDAV /contracts-upload/ → refs → pull
- nginx: location /contracts-upload/ (WebDAV + CORS для managed-фронта)
This commit is contained in:
“Naeel”
2026-08-24 21:45:46 +03:00
parent 4653aa5e8e
commit 78045f2c81
6 changed files with 193 additions and 52 deletions
+25 -12
View File
@@ -241,21 +241,18 @@ function convertDoc(file, onProgress) {
}
/**
* ⛔ НЕ МЕНЯТЬ ⛔ uploadFile — fetch-загрузка с честным счётчиком времени.
* ⛔ НЕ МЕНЯТЬ БЕЗ РАЗРЕШЕНИЯ НАЕЛЯ ⛔ uploadFile — загрузка через ВМ-буфер (паттерн drhider).
*
* v2.0.3: вместо фейкового ↑N% — честный счётчик ⏳ соединение... Nс → ⏳ отправка... Nс.
* fetch() не даёт реальный upload progress, поэтому считаем секунды.
* Кэш-бастинг: ?_=Date.now()
* Фаза 1: PUT файла на ВМ (WebDAV /contracts-upload/, мимо шлюза кластера ~64КБ).
* Фаза 2: POST /api/upload_refs {files:[{name,size,url}]} — бэк сам тянет файл с ВМ.
* Честный счётчик времени: ⏳ соединение... Nс → ⏳ отправка... Nс (fetch не даёт progress).
*/
function uploadFile(file, onProgress, zipSource) {
var startTime = Date.now();
var phase = 'connecting'; // connecting → uploading
if (onProgress) onProgress({ kind: 'connecting', elapsed: 0 });
var fd = new FormData();
fd.append('files', file, file.name);
if (state.contractId) fd.append('contract_id', state.contractId);
fd.append('batch_id', state.batchId);
if (zipSource) fd.append('zip_source', zipSource);
var token = crypto.randomUUID();
var vmUrl = VM_UPLOAD_URL + token + '_0';
// Честный счётчик: каждую секунду обновляем elapsed
var timer = setInterval(function() {
@@ -263,17 +260,33 @@ function uploadFile(file, onProgress, zipSource) {
if (onProgress) onProgress({ kind: phase, elapsed: elapsed });
}, 1000);
return fetch(UPLOAD_URL + '?_=' + Date.now(), { method: 'POST', body: fd })
// Фаза 1: PUT файла на ВМ-буфер
return fetch(vmUrl, { method: 'PUT', body: file, headers: { 'Content-Type': 'application/octet-stream' } })
.then(function(r) {
if (!r.ok) throw new Error('VM upload HTTP ' + r.status);
phase = 'uploading';
// Фаза 2: refs на бэкенд → pull с ВМ
var body = { batch_id: state.batchId, files: [{ name: file.name, size: file.size, url: vmUrl }] };
if (state.contractId) body.contract_id = state.contractId;
if (zipSource) body.zip_source = zipSource;
return fetch('/api/upload_refs?_=' + Date.now(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
})
.then(function(r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
})
.then(function(data) {
clearInterval(timer);
if (data.ok) {
if (data.ok && data.results && data.results.length === 1) {
var res = data.results[0];
if (!res.ok) throw new Error(res.error || 'Неизвестная ошибка');
var elapsed = Math.floor((Date.now() - startTime) / 1000);
if (onProgress) onProgress({ kind: 'uploading', elapsed: elapsed });
return data;
return res; // {ok, doc_id, contract_id, parsed}
}
throw new Error(data.error || 'Неизвестная ошибка');
})