diff --git a/upload/frontend/test_upload_frontend.mjs b/upload/frontend/test_upload_frontend.mjs index 389cef5..0d2533b 100644 --- a/upload/frontend/test_upload_frontend.mjs +++ b/upload/frontend/test_upload_frontend.mjs @@ -2,6 +2,7 @@ // Запуск: node upload/frontend/test_upload_frontend.mjs import assert from 'node:assert/strict'; +import { deflateRawSync } from 'node:zlib'; // ── Полифилл File (Node 18 не имеет global File) ── if (typeof globalThis.File === 'undefined') { @@ -105,6 +106,72 @@ function makeZip(entries) { return Buffer.concat(chunks); } +// ── Сборка ZIP с методом 8 (deflate) — для проверки inflateRaw ── +async function deflateRaw(bytes) { + // zlib.deflateRawSync — deflate без zlib-заголовка (то, что ждёт inflateRaw) + return deflateRawSync(Buffer.from(bytes)); +} + +async function makeZipDeflate(entries) { + const enc = new TextEncoder(); + const chunks = []; + const central = []; + let offset = 0; + const utf8Flag = 0x0800; + for (const e of entries) { + const nameB = enc.encode(e.name); + const raw = enc.encode(e.data); + const comp = await deflateRaw(raw); + const crc = crc32(raw); + const lh = new DataView(new ArrayBuffer(30)); + lh.setUint32(0, 0x04034b50, true); + lh.setUint16(4, 20, true); + lh.setUint16(6, utf8Flag, true); + lh.setUint16(8, 8, true); // method 8 deflate + lh.setUint16(10, 0, true); + lh.setUint16(12, 0x21, true); + lh.setUint32(14, crc, true); + lh.setUint32(18, comp.length, true); + lh.setUint32(22, raw.length, true); + lh.setUint16(26, nameB.length, true); + lh.setUint16(28, 0, true); + chunks.push(Buffer.from(lh.buffer), Buffer.from(nameB), Buffer.from(comp)); + const cd = new DataView(new ArrayBuffer(46)); + cd.setUint32(0, 0x02014b50, true); + cd.setUint16(4, 20, true); + cd.setUint16(6, 20, true); + cd.setUint16(8, utf8Flag, true); + cd.setUint16(10, 8, true); + cd.setUint16(12, 0, true); + cd.setUint16(14, 0x21, true); + cd.setUint32(16, crc, true); + cd.setUint32(20, comp.length, true); + cd.setUint32(24, raw.length, true); + cd.setUint16(28, nameB.length, true); + cd.setUint16(30, 0, true); + cd.setUint16(32, 0, true); + cd.setUint16(34, 0, true); + cd.setUint16(36, 0, true); + cd.setUint32(38, 0, true); + cd.setUint32(42, offset, true); + central.push(Buffer.from(cd.buffer), Buffer.from(nameB)); + offset += 30 + nameB.length + comp.length; + } + const cdSize = central.reduce((s, x) => s + x.byteLength, 0); + const cdStart = offset; + const eocd = new DataView(new ArrayBuffer(22)); + eocd.setUint32(0, 0x06054b50, true); + eocd.setUint16(4, 0, true); + eocd.setUint16(6, 0, true); + eocd.setUint16(8, entries.length, true); + eocd.setUint16(10, entries.length, true); + eocd.setUint32(12, cdSize, true); + eocd.setUint32(16, cdStart, true); + eocd.setUint16(20, 0, true); + chunks.push(...central, Buffer.from(eocd.buffer)); + return Buffer.concat(chunks); +} + // ── Тесты decodeZipName ── function testDecodeZipName() { const utf8 = new TextEncoder().encode('договор.pdf'); @@ -147,6 +214,23 @@ async function testZipNonDoc() { console.log(' не-doc фильтр: ok'); } +async function testZipDeflate() { + // deflate-raw требует DecompressionStream('deflate-raw') — в браузере есть, в Node 18 нет + let supported = true; + try { new DecompressionStream('deflate-raw'); } catch (e) { supported = false; } + if (!supported) { + console.log(' zip deflate (метод 8): skip — Node 18 не поддерживает deflate-raw'); + return; + } + const content = 'Hello deflate world '.repeat(50); + const zip = await makeZipDeflate([{ name: 'deflated.txt', data: content }]); + const files = await listZipFiles(new File([zip], 'a.zip'), ALLOWED); + assert.equal(files.length, 1); + assert.equal(files[0].name, 'deflated.txt'); + assert.equal(await files[0].text(), content); + console.log(' zip deflate (метод 8): ok'); +} + async function testParseZipNotZip() { await assert.rejects(() => parseZip(new Uint8Array([1, 2, 3])), /Не ZIP/); console.log(' не-ZIP бросок: ok'); @@ -231,6 +315,7 @@ const TESTS = [ ['zip кириллица', testZipCyrillic], ['вложенный zip', testZipNested], ['не-doc фильтр', testZipNonDoc], + ['zip deflate (метод 8)', testZipDeflate], ['не-ZIP бросок', testParseZipNotZip], ['дедуп/суффиксы', testDedup], ['лимиты over', testLimits],