import { inflateRawSync } from 'zlib'; const EOCD_SIGNATURE = 0x06054b50; const CENTRAL_SIGNATURE = 0x02014b50; const LOCAL_SIGNATURE = 0x04034b50; function normalizeEntryName(name) { const normalized = String(name || '').replace(/\\/g, '/').replace(/^\/+/, ''); if (!normalized || normalized.endsWith('/')) return normalized; if (normalized.includes('\0') || normalized.split('/').some(part => part === '..')) { throw new Error(`Unsicherer ZIP-Pfad: ${name}`); } return normalized; } function findEndOfCentralDirectory(buffer) { const min = Math.max(0, buffer.length - 0xffff - 22); for (let offset = buffer.length - 22; offset >= min; offset--) { if (buffer.readUInt32LE(offset) === EOCD_SIGNATURE) return offset; } throw new Error('ZIP-Endverzeichnis wurde nicht gefunden.'); } export function readZipEntries(buffer, { maxEntries = 10000, maxUncompressedBytes = 8 * 1024 * 1024 * 1024 } = {}) { if (!Buffer.isBuffer(buffer)) buffer = Buffer.from(buffer || []); const eocd = findEndOfCentralDirectory(buffer); const entryCount = buffer.readUInt16LE(eocd + 10); const centralSize = buffer.readUInt32LE(eocd + 12); const centralOffset = buffer.readUInt32LE(eocd + 16); if (entryCount > maxEntries) throw new Error(`ZIP enthält zu viele Dateien (${entryCount}).`); if (centralOffset + centralSize > buffer.length) throw new Error('ZIP-Zentralverzeichnis ist beschädigt.'); const entries = []; let offset = centralOffset; let totalUncompressed = 0; for (let index = 0; index < entryCount; index++) { if (buffer.readUInt32LE(offset) !== CENTRAL_SIGNATURE) throw new Error('ZIP-Zentralverzeichnis ist ungültig.'); const flags = buffer.readUInt16LE(offset + 8); const method = buffer.readUInt16LE(offset + 10); const crc = buffer.readUInt32LE(offset + 16); const compressedSize = buffer.readUInt32LE(offset + 20); const uncompressedSize = buffer.readUInt32LE(offset + 24); const nameLength = buffer.readUInt16LE(offset + 28); const extraLength = buffer.readUInt16LE(offset + 30); const commentLength = buffer.readUInt16LE(offset + 32); const localOffset = buffer.readUInt32LE(offset + 42); const nameStart = offset + 46; const name = normalizeEntryName(buffer.subarray(nameStart, nameStart + nameLength).toString((flags & 0x0800) ? 'utf8' : 'utf8')); totalUncompressed += uncompressedSize; if (totalUncompressed > maxUncompressedBytes) throw new Error('ZIP ist nach dem Entpacken zu groß.'); entries.push({ name, method, crc, compressedSize, uncompressedSize, localOffset, directory: name.endsWith('/') }); offset = nameStart + nameLength + extraLength + commentLength; } return entries; } export function extractZipEntry(buffer, entry) { if (entry.directory) return Buffer.alloc(0); const offset = entry.localOffset; if (buffer.readUInt32LE(offset) !== LOCAL_SIGNATURE) throw new Error(`Lokaler ZIP-Header fehlt: ${entry.name}`); const nameLength = buffer.readUInt16LE(offset + 26); const extraLength = buffer.readUInt16LE(offset + 28); const dataStart = offset + 30 + nameLength + extraLength; const compressed = buffer.subarray(dataStart, dataStart + entry.compressedSize); let data; if (entry.method === 0) data = Buffer.from(compressed); else if (entry.method === 8) data = inflateRawSync(compressed); else throw new Error(`Nicht unterstützte ZIP-Kompression (${entry.method}) für ${entry.name}.`); if (data.length !== entry.uncompressedSize) throw new Error(`ZIP-Größe stimmt nicht: ${entry.name}`); return data; } export function readZip(buffer, options) { const entries = readZipEntries(buffer, options); const files = new Map(); for (const entry of entries) if (!entry.directory) files.set(entry.name, extractZipEntry(buffer, entry)); return { entries, files }; }