diff --git a/site/app.py b/site/app.py index d2dd96d..0a1a257 100644 --- a/site/app.py +++ b/site/app.py @@ -20,7 +20,7 @@ if _sys_path_root not in sys.path: sys.path.insert(0, _sys_path_root) # Версия приложения (меняется при изменениях) -VERSION = "5.0.7" +VERSION = "5.0.9" def create_app(): diff --git a/site/static/zip.js b/site/static/zip.js new file mode 100644 index 0000000..2813b1a --- /dev/null +++ b/site/static/zip.js @@ -0,0 +1,94 @@ +/** + * Нативный 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' }); +} diff --git a/site/templates/index.html b/site/templates/index.html index 625f3d7..9b5275d 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -8,6 +8,7 @@ DrHider — обфускация документов +