/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/internal/zip/header-builders.js
251 строка
10 KB
Philipp Dunkel
zlib: add ZipEntry, ZipFile, and ZipBuffer
01 авг 2026, 11:47
Не верифицирован
01 авг 2026, 11:47
5ba72ae
Код
Авторство
О чём код?
'use strict'; // Write-path header builders: local/central file headers, the Zip64 data // descriptor, and the (Zip64) end-of-central-directory records, plus the // version-needed and extra-length helpers they share. const { ArrayPrototypePush, MathMax, MathMin, } = primordials; const { codes: { ERR_ZIP_ENTRY_TOO_LARGE, }, } = require('internal/errors'); const { Buffer } = require('buffer'); const { EMPTY_BUFFER, SIG_LOCAL_FILE_HEADER, SIG_DATA_DESCRIPTOR, SIG_CENTRAL_FILE_HEADER, SIG_ZIP64_EOCD_RECORD, SIG_ZIP64_EOCD_LOCATOR, SIG_EOCD, MADE_BY_UNIX, ZIP64_EXTRA_ID, SENTINEL16, SENTINEL32, FLAG_DATA_DESCRIPTOR, METHOD_ZSTD, VERSION_DEFAULT, VERSION_ZIP64, VERSION_ZSTD, } = require('internal/zip/constants'); const { encodeDosDateTime } = require('internal/zip/dos'); const { writeSafeUint64 } = require('internal/zip/binary'); const { buildExtTimestampExtra } = require('internal/zip/extra-fields'); // The "version needed to extract" (sec. 4.4.3): the highest version any // feature of the member demands - 6.3 for Zstandard, 4.5 for Zip64 // structures, 2.0 otherwise. function versionNeeded(meta, zip64) { return MathMax( zip64 ? VERSION_ZIP64 : VERSION_DEFAULT, meta.method === METHOD_ZSTD ? VERSION_ZSTD : 0); } // Guard the combined extra-field length against the 16-bit field it is stored // in (sec. 4.4.11). function checkExtraLength(extraLength) { if (extraLength > SENTINEL16) { throw new ERR_ZIP_ENTRY_TOO_LARGE( 'the entry extra fields must not exceed 65535 bytes'); } } // Build a local file header (sec. 4.3.7). Streamed entries (unknown sizes/CRC) // zero those fields and always carry a Zip64 extra so the trailing data // descriptor can hold 64-bit sizes; oversized non-streamed entries get one too. function buildLocalHeader(meta) { const streaming = (meta.flags & FLAG_DATA_DESCRIPTOR) !== 0; const zip64 = streaming || meta.compressedSize >= SENTINEL32 || meta.uncompressedSize >= SENTINEL32; const ts = meta.extendedMtime !== null ? buildExtTimestampExtra(meta.extendedMtime) : EMPTY_BUFFER; const extraLength = (zip64 ? 20 : 0) + ts.length + meta.extra.length; checkExtraLength(extraLength); const buffer = Buffer.allocUnsafe(30 + meta.name.length + extraLength); buffer.writeUInt32LE(SIG_LOCAL_FILE_HEADER, 0); buffer.writeUInt16LE(versionNeeded(meta, zip64), 4); buffer.writeUInt16LE(meta.flags, 6); buffer.writeUInt16LE(meta.method, 8); const { time, date } = encodeDosDateTime(meta.modified); buffer.writeUInt16LE(time, 10); buffer.writeUInt16LE(date, 12); buffer.writeUInt32LE(streaming ? 0 : meta.crc, 14); buffer.writeUInt32LE(zip64 ? SENTINEL32 : meta.compressedSize, 18); buffer.writeUInt32LE(zip64 ? SENTINEL32 : meta.uncompressedSize, 22); buffer.writeUInt16LE(meta.name.length, 26); buffer.writeUInt16LE(extraLength, 28); meta.name.copy(buffer, 30); let pos = 30 + meta.name.length; if (zip64) { buffer.writeUInt16LE(ZIP64_EXTRA_ID, pos); buffer.writeUInt16LE(16, pos + 2); writeSafeUint64(buffer, pos + 4, streaming ? 0 : meta.uncompressedSize); writeSafeUint64(buffer, pos + 12, streaming ? 0 : meta.compressedSize); pos += 20; } ts.copy(buffer, pos); pos += ts.length; meta.extra.copy(buffer, pos); return buffer; } // Build a central directory file header (sec. 4.3.12). Each of the // uncompressed size, compressed size, and local-header offset that overflows // 32 bits is moved into the Zip64 extra field (sec. 4.5.3) in that order. function buildCentralHeader(meta, localOffset) { const zip64Streaming = (meta.flags & FLAG_DATA_DESCRIPTOR) !== 0; const u64 = meta.uncompressedSize >= SENTINEL32; const c64 = meta.compressedSize >= SENTINEL32; const o64 = localOffset >= SENTINEL32; const zip64Fields = (u64 ? 1 : 0) + (c64 ? 1 : 0) + (o64 ? 1 : 0); const ts = meta.extendedMtime !== null ? buildExtTimestampExtra(meta.extendedMtime) : EMPTY_BUFFER; const extraLength = (zip64Fields ? 4 + 8 * zip64Fields : 0) + ts.length + meta.extra.length; checkExtraLength(extraLength); const zip64 = zip64Streaming || zip64Fields > 0; const version = versionNeeded(meta, zip64); const buffer = Buffer.allocUnsafe( 46 + meta.name.length + extraLength + meta.comment.length); buffer.writeUInt32LE(SIG_CENTRAL_FILE_HEADER, 0); // "Version made by" (sec. 4.4.2): the upper byte is the creator's host // system, which determines how the external attributes (sec. 4.4.15) are // interpreted. Preserve the original host for round-tripped entries - // stamping everything as Unix would turn, say, a DOS entry's zeroed high // bits into Unix mode 0000. Entries built by `createEntryMeta()` are Unix. buffer.writeUInt16LE((meta.madeBy << 8) | version, 4); buffer.writeUInt16LE(version, 6); buffer.writeUInt16LE(meta.flags, 8); buffer.writeUInt16LE(meta.method, 10); const { time, date } = encodeDosDateTime(meta.modified); buffer.writeUInt16LE(time, 12); buffer.writeUInt16LE(date, 14); buffer.writeUInt32LE(meta.crc, 16); buffer.writeUInt32LE(c64 ? SENTINEL32 : meta.compressedSize, 20); buffer.writeUInt32LE(u64 ? SENTINEL32 : meta.uncompressedSize, 24); buffer.writeUInt16LE(meta.name.length, 28); buffer.writeUInt16LE(extraLength, 30); buffer.writeUInt16LE(meta.comment.length, 32); buffer.writeUInt16LE(0, 34); // disk number buffer.writeUInt16LE(meta.internal, 36); buffer.writeUInt32LE(meta.external, 38); buffer.writeUInt32LE(o64 ? SENTINEL32 : localOffset, 42); meta.name.copy(buffer, 46); let pos = 46 + meta.name.length; if (zip64Fields) { buffer.writeUInt16LE(ZIP64_EXTRA_ID, pos); buffer.writeUInt16LE(8 * zip64Fields, pos + 2); pos += 4; if (u64) { writeSafeUint64(buffer, pos, meta.uncompressedSize); pos += 8; } if (c64) { writeSafeUint64(buffer, pos, meta.compressedSize); pos += 8; } if (o64) { writeSafeUint64(buffer, pos, localOffset); pos += 8; } } ts.copy(buffer, pos); pos += ts.length; meta.extra.copy(buffer, pos); pos += meta.extra.length; meta.comment.copy(buffer, pos); return buffer; } // Zip64 data descriptor (sec. 4.3.9): emitted after a streamed entry, whose // local header always carries a Zip64 extra field. function buildDataDescriptor64(crc, compressedSize, uncompressedSize) { const buffer = Buffer.allocUnsafe(24); buffer.writeUInt32LE(SIG_DATA_DESCRIPTOR, 0); buffer.writeUInt32LE(crc, 4); writeSafeUint64(buffer, 8, compressedSize); writeSafeUint64(buffer, 16, uncompressedSize); return buffer; } // Build the end of central directory record (sec. 4.3.16); fields that // overflow their 16/32-bit slots are written as sentinels and the true values // live in the Zip64 EOCD record. function buildEndOfCentralDirectory(count, size, offset, comment) { const buffer = Buffer.allocUnsafe(22 + comment.length); buffer.writeUInt32LE(SIG_EOCD, 0); buffer.writeUInt16LE(0, 4); // disk number buffer.writeUInt16LE(0, 6); // Central directory disk number buffer.writeUInt16LE(MathMin(count, SENTINEL16), 8); buffer.writeUInt16LE(MathMin(count, SENTINEL16), 10); buffer.writeUInt32LE(MathMin(size, SENTINEL32), 12); buffer.writeUInt32LE(MathMin(offset, SENTINEL32), 16); buffer.writeUInt16LE(comment.length, 20); comment.copy(buffer, 22); return buffer; } // Build the Zip64 end of central directory record (sec. 4.3.14): the 64-bit // counterpart to the EOCD, holding the real record count/size/offset. function buildZip64EndRecord(count, size, offset) { const buffer = Buffer.allocUnsafe(56); buffer.writeUInt32LE(SIG_ZIP64_EOCD_RECORD, 0); writeSafeUint64(buffer, 4, 44); // Size of the remainder of this record buffer.writeUInt16LE((MADE_BY_UNIX << 8) | VERSION_ZIP64, 12); buffer.writeUInt16LE(VERSION_ZIP64, 14); buffer.writeUInt32LE(0, 16); // disk number buffer.writeUInt32LE(0, 20); // Central directory disk number writeSafeUint64(buffer, 24, count); writeSafeUint64(buffer, 32, count); writeSafeUint64(buffer, 40, size); writeSafeUint64(buffer, 48, offset); return buffer; } // Build the Zip64 end of central directory locator (sec. 4.3.15): points the // reader from just before the EOCD to the Zip64 EOCD record. function buildZip64EndLocator(recordOffset) { const buffer = Buffer.allocUnsafe(20); buffer.writeUInt32LE(SIG_ZIP64_EOCD_LOCATOR, 0); buffer.writeUInt32LE(0, 4); // Disk with the Zip64 EOCD record writeSafeUint64(buffer, 8, recordOffset); buffer.writeUInt32LE(1, 16); // total disks return buffer; } // The archive trailer: the Zip64 EOCD record and locator // (sec. 4.3.14/4.3.15) when the record count, central directory offset, or // central directory size overflows its classic 16-/32-bit field, followed // by the end of central directory record (sec. 4.3.16). Shared by the // streaming serializers and ZipFile's in-place rewrite so the Zip64 // switchover rule lives in exactly one place. function buildArchiveTrailer(count, centralDirectorySize, centralDirectoryOffset, comment) { const chunks = []; const zip64 = count >= SENTINEL16 || centralDirectoryOffset >= SENTINEL32 || centralDirectorySize >= SENTINEL32; if (zip64) { const recordOffset = centralDirectoryOffset + centralDirectorySize; ArrayPrototypePush(chunks, buildZip64EndRecord(count, centralDirectorySize, centralDirectoryOffset)); ArrayPrototypePush(chunks, buildZip64EndLocator(recordOffset)); } ArrayPrototypePush(chunks, buildEndOfCentralDirectory(count, centralDirectorySize, centralDirectoryOffset, comment)); return chunks; } module.exports = { buildLocalHeader, buildCentralHeader, buildDataDescriptor64, buildEndOfCentralDirectory, buildZip64EndRecord, buildZip64EndLocator, buildArchiveTrailer, };