95 lines
3.0 KiB
JavaScript
95 lines
3.0 KiB
JavaScript
/**
|
|
* Нативный 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' });
|
|
}
|