v6.0.0: вся обработка на бэкенде — zip.js удалён, фронт только XHR+скачивание
Deploy drhider / validate (push) Waiting to run

This commit is contained in:
2026-07-13 07:53:08 +04:00
parent cbf619aaba
commit e590d8ab9a
3 changed files with 8 additions and 123 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ if _sys_path_root not in sys.path:
sys.path.insert(0, _sys_path_root) sys.path.insert(0, _sys_path_root)
# Версия приложения (меняется при изменениях) # Версия приложения (меняется при изменениях)
VERSION = "5.2.1" VERSION = "6.0.0"
def create_app(): def create_app():
-94
View File
@@ -1,94 +0,0 @@
/**
* Нативный ZIP — упаковка и распаковка без внешних библиотек.
*
* Использование:
* const files = await unzip(zipBlob); // {name: Blob, ...}
* const zip = await buildZip([{name, data}]); // Blob
*/
/**
* Распаковать ZIP blob → {filename: Blob}.
* Поддерживает только STORE (без сжатия) — как отдаёт drhider.
*/
async function unzip(zb) {
const buf = await zb.arrayBuffer();
const dv = new DataView(buf);
const files = {};
let pos = 0;
while (pos < buf.byteLength - 4) {
const sig = dv.getUint32(pos, true);
if (sig === 0x04034b50) {
// Local file header
const start = pos;
pos += 26;
const nameLen = dv.getUint16(pos, true);
pos += 2;
const extraLen = dv.getUint16(pos, true);
pos += 2;
const name = new TextDecoder().decode(
new Uint8Array(buf, pos, nameLen)
);
pos += nameLen + extraLen;
const compSize = dv.getUint32(start + 18, true);
files[name] = new Blob([new Uint8Array(buf, pos, compSize)]);
pos += compSize;
} else {
break; // Central directory или EOCD — конец файлов
}
}
return files;
}
/**
* Создать ZIP blob из entries [{name, data}].
* Метод: STORE (без сжатия), UTF-8 имена.
*/
async function buildZip(entries) {
const parts = [];
const centralDir = [];
let offset = 0;
for (const e of entries) {
const data = new Uint8Array(await e.data.arrayBuffer());
const nameBytes = new TextEncoder().encode(e.name);
// Local file header (30 bytes + filename)
const lh = new Uint8Array(30 + nameBytes.length);
const lv = new DataView(lh.buffer);
lv.setUint32(0, 0x04034b50, true); // signature
lv.setUint16(6, 0x800, true); // flags: UTF-8
lv.setUint32(18, data.length, true); // compressed size
lv.setUint32(22, data.length, true); // uncompressed size
lv.setUint16(26, nameBytes.length, true);
lh.set(nameBytes, 30);
parts.push(lh, data);
// Central directory entry (46 bytes + filename)
const ce = new Uint8Array(46 + nameBytes.length);
const cv = new DataView(ce.buffer);
cv.setUint32(0, 0x02014b50, true);
cv.setUint16(8, 0x800, true);
cv.setUint32(20, data.length, true);
cv.setUint32(24, data.length, true);
cv.setUint16(28, nameBytes.length, true);
cv.setUint32(42, offset, true);
ce.set(nameBytes, 46);
centralDir.push(ce);
offset += 30 + nameBytes.length + data.length;
}
// End of central directory (22 bytes)
const cdSize = centralDir.reduce((s, c) => s + c.length, 0);
const eocd = new Uint8Array(22);
const ev = new DataView(eocd.buffer);
ev.setUint32(0, 0x06054b50, true);
ev.setUint16(8, entries.length, true);
ev.setUint16(10, entries.length, true);
ev.setUint32(12, cdSize, true);
ev.setUint32(16, offset, true);
return new Blob([...parts, ...centralDir, eocd], { type: 'application/zip' });
}
+7 -28
View File
@@ -8,7 +8,6 @@
<meta http-equiv="Expires" content="0"> <meta http-equiv="Expires" content="0">
<title>DrHider — обфускация документов</title> <title>DrHider — обфускация документов</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<script src="/static/zip.js"></script>
<style> <style>
:root { :root {
--brand-primary: #2563eb; --brand-primary: #2563eb;
@@ -156,15 +155,11 @@ async function uploadFiles() {
if (sf.length === 0) return; if (sf.length === 0) return;
ub.disabled = true; ub.disabled = true;
const total = sf.length; const total = sf.length;
const tstamp = ts();
const maps = [];
const zEntries = [];
let ok = 0, fail = 0; let ok = 0, fail = 0;
for (let i = 0; i < total; i++) { for (let i = 0; i < total; i++) {
const f = sf[i], n = i + 1; const f = sf[i], n = i + 1;
const t0 = performance.now(); const t0 = performance.now();
// Бегущий секундомер в статусе
const timer = setInterval(() => { const timer = setInterval(() => {
const sec = Math.round((performance.now() - t0) / 1000); const sec = Math.round((performance.now() - t0) / 1000);
ss(i, '<span style="color:#2563eb">⏳ ' + sec + 'с</span>'); ss(i, '<span style="color:#2563eb">⏳ ' + sec + 'с</span>');
@@ -186,14 +181,13 @@ async function uploadFiles() {
}); });
clearInterval(timer); clearInterval(timer);
const el = ((performance.now() - t0) / 1000).toFixed(1); const el = ((performance.now() - t0) / 1000).toFixed(1);
const files = await unzip(blob); // Скачиваем ZIP как есть — бэкенд всё сделал
for (const [name, data] of Object.entries(files)) { const u = URL.createObjectURL(blob);
if (name === 'mapping.csv') { const a = document.createElement('a');
const t = await data.text(); a.href = u;
const ls = t.split('\n'); a.download = f.name.replace(/\.[^.]+$/, '') + '_obfuscated.zip';
maps.push(maps.length === 0 ? ls.join('\n') : ls.slice(1).join('\n')); a.click();
} else { zEntries.push({ name, data }); } URL.revokeObjectURL(u);
}
ss(i, '<span style="color:#22c55e">✓ ' + el + 'с</span>'); ss(i, '<span style="color:#22c55e">✓ ' + el + 'с</span>');
ok++; ok++;
} catch (err) { } catch (err) {
@@ -204,21 +198,6 @@ async function uploadFiles() {
if (i < total - 1) await new Promise(r => setTimeout(r, 300)); if (i < total - 1) await new Promise(r => setTimeout(r, 300));
} }
if (ok > 0) {
st.className = 'status pack'; st.textContent = 'Упаковка...';
const zb = await buildZip(zEntries);
const u = URL.createObjectURL(zb);
const a = document.createElement('a'); a.href = u; a.download = 'drhider_' + tstamp + '.zip'; a.click();
URL.revokeObjectURL(u);
}
if (maps.length > 0) {
const cb = new Blob(['\ufeff' + maps.join('\n')], { type: 'text/csv;charset=utf-8' });
const u = URL.createObjectURL(cb);
const a = document.createElement('a'); a.href = u; a.download = 'drhider_' + tstamp + '.csv'; a.click();
URL.revokeObjectURL(u);
}
st.className = fail === 0 ? 'status done' : 'status error'; st.className = fail === 0 ? 'status done' : 'status error';
st.textContent = fail === 0 ? '✅ Готово! ' + ok + '/' + total : 'Готово: ' + ok + ' OK, ' + fail + ' ошибок из ' + total; st.textContent = fail === 0 ? '✅ Готово! ' + ok + '/' + total : 'Готово: ' + ok + ' OK, ' + fail + ' ошибок из ' + total;
ub.disabled = false; ub.disabled = false;