// A TINY ZIP WRITER, for the unit tests of lib/zipReader.ts and // controller/attachMedia.ts: stored and deflated entries, UTF-8 names, and a // forced-ZIP64 variant (every size and offset in the ZIP64 extra field, the // ZIP64 end records present), so the reader's 64-bit paths run without a // 4 GB fixture. Synthetic bytes only — never a real archive. import { crc32, deflateRawSync } from "node:zlib"; export type ZipFixtureEntry = { name: string; data?: Buffer | string; // Default: stored. A directory entry has a name ending in "/" and no data. deflate?: boolean; }; export function makeZip(entries: ZipFixtureEntry[], opts: { zip64?: boolean } = {}): Buffer { const zip64 = !!opts.zip64; const locals: Buffer[] = []; const centrals: Buffer[] = []; let offset = 0; for (const e of entries) { const name = Buffer.from(e.name, "utf8"); const raw = e.data === undefined ? Buffer.alloc(0) : Buffer.isBuffer(e.data) ? e.data : Buffer.from(e.data, "utf8"); const method = e.deflate ? 8 : 0; const body = e.deflate ? deflateRawSync(raw) : raw; const crc = crc32(raw) >>> 0; const flags = 0x800; const localExtra = zip64 ? z64Extra([raw.length, body.length]) : Buffer.alloc(0); const lfh = Buffer.alloc(30); lfh.writeUInt32LE(0x04034b50, 0); lfh.writeUInt16LE(zip64 ? 45 : 20, 4); lfh.writeUInt16LE(flags, 6); lfh.writeUInt16LE(method, 8); lfh.writeUInt32LE(crc, 14); lfh.writeUInt32LE(zip64 ? 0xffffffff : body.length, 18); lfh.writeUInt32LE(zip64 ? 0xffffffff : raw.length, 22); lfh.writeUInt16LE(name.length, 26); lfh.writeUInt16LE(localExtra.length, 28); const local = Buffer.concat([lfh, name, localExtra, body]); const cdExtra = zip64 ? z64Extra([raw.length, body.length, offset]) : Buffer.alloc(0); const cdh = Buffer.alloc(46); cdh.writeUInt32LE(0x02014b50, 0); cdh.writeUInt16LE(zip64 ? 45 : 20, 4); cdh.writeUInt16LE(zip64 ? 45 : 20, 6); cdh.writeUInt16LE(flags, 8); cdh.writeUInt16LE(method, 10); cdh.writeUInt32LE(crc, 16); cdh.writeUInt32LE(zip64 ? 0xffffffff : body.length, 20); cdh.writeUInt32LE(zip64 ? 0xffffffff : raw.length, 24); cdh.writeUInt16LE(name.length, 28); cdh.writeUInt16LE(cdExtra.length, 30); cdh.writeUInt32LE(zip64 ? 0xffffffff : offset, 42); centrals.push(Buffer.concat([cdh, name, cdExtra])); locals.push(local); offset += local.length; } const cd = Buffer.concat(centrals); const cdOffset = offset; const tail: Buffer[] = []; if (zip64) { const rec = Buffer.alloc(56); rec.writeUInt32LE(0x06064b50, 0); rec.writeBigUInt64LE(44n, 4); rec.writeUInt16LE(45, 12); rec.writeUInt16LE(45, 14); rec.writeBigUInt64LE(BigInt(entries.length), 24); rec.writeBigUInt64LE(BigInt(entries.length), 32); rec.writeBigUInt64LE(BigInt(cd.length), 40); rec.writeBigUInt64LE(BigInt(cdOffset), 48); const loc = Buffer.alloc(20); loc.writeUInt32LE(0x07064b50, 0); loc.writeBigUInt64LE(BigInt(cdOffset + cd.length), 8); loc.writeUInt32LE(1, 16); tail.push(rec, loc); } const eocd = Buffer.alloc(22); eocd.writeUInt32LE(0x06054b50, 0); eocd.writeUInt16LE(zip64 ? 0xffff : entries.length, 8); eocd.writeUInt16LE(zip64 ? 0xffff : entries.length, 10); eocd.writeUInt32LE(zip64 ? 0xffffffff : cd.length, 12); eocd.writeUInt32LE(zip64 ? 0xffffffff : cdOffset, 16); tail.push(eocd); return Buffer.concat([...locals, cd, ...tail]); } function z64Extra(values: number[]): Buffer { const b = Buffer.alloc(4 + 8 * values.length); b.writeUInt16LE(0x0001, 0); b.writeUInt16LE(8 * values.length, 2); values.forEach((v, i) => b.writeBigUInt64LE(BigInt(v), 4 + 8 * i)); return b; }