79 lines
3.7 KiB
JavaScript
79 lines
3.7 KiB
JavaScript
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 };
|
|
}
|