Reset picker status between selections and harden upload guards
This commit is contained in:
@@ -63,6 +63,12 @@ def upload():
|
|||||||
if not name:
|
if not name:
|
||||||
had_unnamed = True
|
had_unnamed = True
|
||||||
continue
|
continue
|
||||||
|
# Ранняя проверка размера по Content-Length позволяет не читать в память
|
||||||
|
# тело файла, заведомо превышающего лимит. Проверка выполняется до f.read().
|
||||||
|
if f.content_length is not None and f.content_length > MAX_FILE_BYTES:
|
||||||
|
log.warning("upload: file exceeds %dMB, skipped sid=%s file=%r size=%d",
|
||||||
|
MAX_FILE_BYTES // (1024 * 1024), sid, name, f.content_length)
|
||||||
|
continue
|
||||||
data = f.read()
|
data = f.read()
|
||||||
log.info("upload: sid=%s file=%r size=%d", sid, name, len(data))
|
log.info("upload: sid=%s file=%r size=%d", sid, name, len(data))
|
||||||
if len(data) > MAX_FILE_BYTES:
|
if len(data) > MAX_FILE_BYTES:
|
||||||
|
|||||||
Vendored
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
// node_modules/fflate/esm/browser.js
|
// .agent-tmp/node_modules/fflate/esm/browser.js
|
||||||
var u8 = Uint8Array;
|
var u8 = Uint8Array;
|
||||||
var u16 = Uint16Array;
|
var u16 = Uint16Array;
|
||||||
var i32 = Int32Array;
|
var i32 = Int32Array;
|
||||||
@@ -717,6 +717,7 @@ function onFilesChange(state, cfg, elements) {
|
|||||||
return async () => {
|
return async () => {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
try {
|
try {
|
||||||
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -731,6 +732,7 @@ function onFolderChange(state, cfg, elements) {
|
|||||||
return async () => {
|
return async () => {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
try {
|
try {
|
||||||
const roots = /* @__PURE__ */ new Map();
|
const roots = /* @__PURE__ */ new Map();
|
||||||
for (const file of Array.from(elements.folderInputEl.files)) {
|
for (const file of Array.from(elements.folderInputEl.files)) {
|
||||||
@@ -1153,6 +1155,7 @@ function initFilePicker(config) {
|
|||||||
if (root.querySelector("#fp-clear-btn")) listen(root.querySelector("#fp-clear-btn"), "click", () => {
|
if (root.querySelector("#fp-clear-btn")) listen(root.querySelector("#fp-clear-btn"), "click", () => {
|
||||||
state.nodes = [];
|
state.nodes = [];
|
||||||
state.fileKeys.clear();
|
state.fileKeys.clear();
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
redraw();
|
redraw();
|
||||||
notify(cfg, state);
|
notify(cfg, state);
|
||||||
});
|
});
|
||||||
@@ -1174,6 +1177,7 @@ function initFilePicker(config) {
|
|||||||
clear: () => {
|
clear: () => {
|
||||||
state.nodes = [];
|
state.nodes = [];
|
||||||
state.fileKeys.clear();
|
state.fileKeys.clear();
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
redraw();
|
redraw();
|
||||||
notify(cfg, state);
|
notify(cfg, state);
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+5
-1
@@ -26,7 +26,7 @@ var FilePicker = (() => {
|
|||||||
uploadViaVM: () => uploadViaVM
|
uploadViaVM: () => uploadViaVM
|
||||||
});
|
});
|
||||||
|
|
||||||
// node_modules/fflate/esm/browser.js
|
// .agent-tmp/node_modules/fflate/esm/browser.js
|
||||||
var u8 = Uint8Array;
|
var u8 = Uint8Array;
|
||||||
var u16 = Uint16Array;
|
var u16 = Uint16Array;
|
||||||
var i32 = Int32Array;
|
var i32 = Int32Array;
|
||||||
@@ -745,6 +745,7 @@ var FilePicker = (() => {
|
|||||||
return async () => {
|
return async () => {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
try {
|
try {
|
||||||
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -759,6 +760,7 @@ var FilePicker = (() => {
|
|||||||
return async () => {
|
return async () => {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
try {
|
try {
|
||||||
const roots = /* @__PURE__ */ new Map();
|
const roots = /* @__PURE__ */ new Map();
|
||||||
for (const file of Array.from(elements.folderInputEl.files)) {
|
for (const file of Array.from(elements.folderInputEl.files)) {
|
||||||
@@ -1181,6 +1183,7 @@ var FilePicker = (() => {
|
|||||||
if (root.querySelector("#fp-clear-btn")) listen(root.querySelector("#fp-clear-btn"), "click", () => {
|
if (root.querySelector("#fp-clear-btn")) listen(root.querySelector("#fp-clear-btn"), "click", () => {
|
||||||
state.nodes = [];
|
state.nodes = [];
|
||||||
state.fileKeys.clear();
|
state.fileKeys.clear();
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
redraw();
|
redraw();
|
||||||
notify(cfg, state);
|
notify(cfg, state);
|
||||||
});
|
});
|
||||||
@@ -1202,6 +1205,7 @@ var FilePicker = (() => {
|
|||||||
clear: () => {
|
clear: () => {
|
||||||
state.nodes = [];
|
state.nodes = [];
|
||||||
state.fileKeys.clear();
|
state.fileKeys.clear();
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||||
redraw();
|
redraw();
|
||||||
notify(cfg, state);
|
notify(cfg, state);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -877,13 +877,23 @@
|
|||||||
if (!sessionDone) ub.disabled = false;
|
if (!sessionDone) ub.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseFilename(disp) {
|
||||||
|
if (!disp) return 'obfuscator.zip';
|
||||||
|
// RFC 5987: filename*=UTF-8''... имеет приоритет над обычным filename=.
|
||||||
|
const utf8 = disp.match(/filename\*=UTF-8''([^;]+)/i);
|
||||||
|
if (utf8) {
|
||||||
|
try { return decodeURIComponent(utf8[1]); } catch (e) { /* повреждённый percent-encoding */ }
|
||||||
|
}
|
||||||
|
const plain = disp.match(/filename="?([^";]+)"?/i);
|
||||||
|
return plain ? plain[1] : 'obfuscator.zip';
|
||||||
|
}
|
||||||
|
|
||||||
function downloadZip() {
|
function downloadZip() {
|
||||||
if (!currentSid) return;
|
if (!currentSid) return;
|
||||||
fetch('/api/download/' + currentSid)
|
fetch('/api/download/' + currentSid)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
const disp = r.headers.get('Content-Disposition');
|
const disp = r.headers.get('Content-Disposition');
|
||||||
const m = disp && disp.match(/filename="?(.+?)"?$/);
|
return Promise.all([r.blob(), parseFilename(disp)]);
|
||||||
return Promise.all([r.blob(), m ? m[1] : 'obfuscator.zip']);
|
|
||||||
})
|
})
|
||||||
.then(([blob, fname]) => {
|
.then(([blob, fname]) => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export function initFilePicker(config) {
|
|||||||
if (root.querySelector('#fp-clear-btn')) listen(root.querySelector('#fp-clear-btn'), 'click', () => {
|
if (root.querySelector('#fp-clear-btn')) listen(root.querySelector('#fp-clear-btn'), 'click', () => {
|
||||||
state.nodes = [];
|
state.nodes = [];
|
||||||
state.fileKeys.clear();
|
state.fileKeys.clear();
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = '';
|
||||||
redraw();
|
redraw();
|
||||||
notify(cfg, state);
|
notify(cfg, state);
|
||||||
});
|
});
|
||||||
@@ -174,6 +175,7 @@ export function initFilePicker(config) {
|
|||||||
clear: () => {
|
clear: () => {
|
||||||
state.nodes = [];
|
state.nodes = [];
|
||||||
state.fileKeys.clear();
|
state.fileKeys.clear();
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = '';
|
||||||
redraw();
|
redraw();
|
||||||
notify(cfg, state);
|
notify(cfg, state);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ export function onFilesChange(state, cfg, elements) {
|
|||||||
return async () => {
|
return async () => {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
|
// Сброс статуса перед новым выбором: иначе сообщение об ошибке предыдущего
|
||||||
|
// выбора (например, «Ошибка чтения архива») останется висеть в интерфейсе.
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = '';
|
||||||
try {
|
try {
|
||||||
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export function onFolderChange(state, cfg, elements) {
|
|||||||
return async () => {
|
return async () => {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
|
// Сброс статуса перед новым выбором: иначе сообщение об ошибке предыдущего
|
||||||
|
// выбора (например, «Ошибка чтения архива») останется висеть в интерфейсе.
|
||||||
|
if (elements.statusEl) elements.statusEl.textContent = '';
|
||||||
try {
|
try {
|
||||||
const roots = new Map();
|
const roots = new Map();
|
||||||
for (const file of Array.from(elements.folderInputEl.files)) {
|
for (const file of Array.from(elements.folderInputEl.files)) {
|
||||||
@@ -30,8 +33,10 @@ export function onFolderChange(state, cfg, elements) {
|
|||||||
const rootName = parts[0] || file.name;
|
const rootName = parts[0] || file.name;
|
||||||
if (!roots.has(rootName)) {
|
if (!roots.has(rootName)) {
|
||||||
// Один root на выбранный каталог позволяет сохранить дерево целиком.
|
// Один root на выбранный каталог позволяет сохранить дерево целиком.
|
||||||
roots.set(rootName, { id: crypto.randomUUID(), kind: 'folder', name: rootName,
|
roots.set(rootName, {
|
||||||
path: rootName, children: [], expanded: false });
|
id: crypto.randomUUID(), kind: 'folder', name: rootName,
|
||||||
|
path: rootName, children: [], expanded: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const root = roots.get(rootName);
|
const root = roots.get(rootName);
|
||||||
// Вставляет узел по его пути, создавая отсутствующие промежуточные папки.
|
// Вставляет узел по его пути, создавая отсутствующие промежуточные папки.
|
||||||
@@ -42,9 +47,11 @@ export function onFolderChange(state, cfg, elements) {
|
|||||||
const last = index === nodeParts.length - 1;
|
const last = index === nodeParts.length - 1;
|
||||||
let child = current.children.find((item) => item.name === part);
|
let child = current.children.find((item) => item.name === part);
|
||||||
if (!child) {
|
if (!child) {
|
||||||
child = last ? node : { id: crypto.randomUUID(), kind: 'folder', name: part,
|
child = last ? node : {
|
||||||
|
id: crypto.randomUUID(), kind: 'folder', name: part,
|
||||||
path: `${rootName}/${nodeParts.slice(0, index + 1).join('/')}`,
|
path: `${rootName}/${nodeParts.slice(0, index + 1).join('/')}`,
|
||||||
children: [], expanded: false };
|
children: [], expanded: false
|
||||||
|
};
|
||||||
current.children.push(child);
|
current.children.push(child);
|
||||||
}
|
}
|
||||||
current = child;
|
current = child;
|
||||||
@@ -68,8 +75,10 @@ export function onFolderChange(state, cfg, elements) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (cfg.allowedExt.some((extension) => lowerPath.endsWith(extension))) {
|
} else if (cfg.allowedExt.some((extension) => lowerPath.endsWith(extension))) {
|
||||||
addToFolder({ id: crypto.randomUUID(), kind: 'file', name: parts.at(-1),
|
addToFolder({
|
||||||
path: `${rootName}/${relativePath}`, file, children: [], expanded: true });
|
id: crypto.randomUUID(), kind: 'file', name: parts.at(-1),
|
||||||
|
path: `${rootName}/${relativePath}`, file, children: [], expanded: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Дедупликация выполняется после сборки каждого корня каталога.
|
// Дедупликация выполняется после сборки каждого корня каталога.
|
||||||
|
|||||||
Reference in New Issue
Block a user