UNPKG

app-package-builder

Version:

Idea is very simple — in the runtime we don't need to process or understand archive format. Wwe just need to know file data ranges. Where file data begins and where ends.

660 lines (639 loc) 23.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SubStreamsInfo = exports.StartHeader = exports.SevenZFile = undefined; var _bluebirdLst; function _load_bluebirdLst() { return _bluebirdLst = require("bluebird-lst"); } var _blockMapApi; function _load_blockMapApi() { return _blockMapApi = require("builder-util-runtime/out/blockMapApi"); } var _fsExtraP; function _load_fsExtraP() { return _fsExtraP = require("fs-extra-p"); } var _Archive; function _load_Archive() { return _Archive = require("./Archive"); } var _BitSet; function _load_BitSet() { return _BitSet = require("./BitSet"); } var _Folder; function _load_Folder() { return _Folder = require("./Folder"); } var _LittleEndianBuffer; function _load_LittleEndianBuffer() { return _LittleEndianBuffer = require("./LittleEndianBuffer"); } var _SevenZArchiveEntry; function _load_SevenZArchiveEntry() { return _SevenZArchiveEntry = require("./SevenZArchiveEntry"); } const sevenZSignature = Buffer.concat([Buffer.from("7z", "binary"), Buffer.from([0xBC, 0xAF, 0x27, 0x1C])]); /* start header size */ const kEnd = 0x00; const kArchiveProperties = 0x02; const kAdditionalStreamsInfo = 0x03; const kMainStreamsInfo = 0x04; const kEncodedHeader = 0x17; const kHeader = 0x01; const kFilesInfo = 0x05; const kPackInfo = 0x06; const kUnpackInfo = 0x07; const kSubStreamsInfo = 0x08; const kSize = 0x09; const kCRC = 0x0A; const kFolder = 0x0B; const kCodersUnpackSize = 0x0C; const kNumUnpackStream = 0x0D; const kEmptyStream = 0x0E; const kEmptyFile = 0x0F; const kAnti = 0x10; const kName = 0x11; const kCTime = 0x12; const kATime = 0x13; const kMTime = 0x14; const kWinAttributes = 0x15; const kStartPos = 0x18; const kDummy = 0x19; class SevenZFile { constructor(fd) { this.fd = fd; } read() { var _this = this; return (0, (_bluebirdLst || _load_bluebirdLst()).coroutine)(function* () { _this.archive = yield _this.readHeaders(); })(); } readHeaders() { var _this2 = this; return (0, (_bluebirdLst || _load_bluebirdLst()).coroutine)(function* () { let _buf = Buffer.allocUnsafe((_blockMapApi || _load_blockMapApi()).SIGNATURE_HEADER_SIZE); yield (0, (_fsExtraP || _load_fsExtraP()).read)(_this2.fd, _buf, 0, _buf.length, 0); const index = sevenZSignature.length; const signature = _buf.slice(0, index); if (!sevenZSignature.equals(signature)) { throw new Error("Bad 7z signature"); } let buf = new (_LittleEndianBuffer || _load_LittleEndianBuffer()).LittleEndianBuffer(_buf, index); // 7zFormat.txt has it wrong - it's first major then minor const archiveVersionMajor = buf.readByte(); const archiveVersionMinor = buf.readByte(); if (archiveVersionMajor !== 0) { throw new Error(`Unsupported 7z version (${archiveVersionMajor},${archiveVersionMinor})`); } const startHeaderCrc = buf.readUnsignedInt(); const startHeader = readStartHeader(startHeaderCrc, buf); _buf = Buffer.allocUnsafe(startHeader.nextHeaderSize); yield (0, (_fsExtraP || _load_fsExtraP()).read)(_this2.fd, _buf, 0, _buf.length, (_blockMapApi || _load_blockMapApi()).SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset); // if (startHeader.nextHeaderCrc !== crc32(_buf)) { // throw new Error("Header CRC mismatch") // } const archive = new (_Archive || _load_Archive()).Archive(); archive.headerSize = startHeader.nextHeaderSize; buf = new (_LittleEndianBuffer || _load_LittleEndianBuffer()).LittleEndianBuffer(_buf); const nid = buf.readByte(); if (nid === kEncodedHeader) { throw new Error("Encoded header is not supported"); } if (nid === kHeader) { readHeader(buf, archive); } else { throw new Error("Broken or unsupported archive: no Header"); } return archive; })(); } } exports.SevenZFile = SevenZFile; // noinspection JSUnusedLocalSymbols function readStartHeader(startHeaderCrc, buf) { // if (startHeaderCrc !== crc32(buf.slice())) { // throw new Error("startHeader CRC mismatch") // } const nextHeaderOffset = buf.readLong(); const nextHeaderSize = buf.readLong(); const nextHeaderCrc = buf.readUnsignedInt(); return new StartHeader(nextHeaderOffset, nextHeaderSize, nextHeaderCrc); } function readHeader(header, archive) { let nid = header.readUnsignedByte(); if (nid === kArchiveProperties) { readArchiveProperties(header); nid = header.readUnsignedByte(); } if (nid === kAdditionalStreamsInfo) { throw new Error("Additional streams unsupported"); } if (nid === kMainStreamsInfo) { readStreamsInfo(header, archive); nid = header.readUnsignedByte(); } if (nid === kFilesInfo) { readFilesInfo(header, archive); nid = header.readUnsignedByte(); } if (nid !== kEnd) { throw new Error(`Badly terminated header, found ${nid}`); } } function readFilesInfo(header, archive) { const numFiles = readUint64(header); const files = new Array(numFiles); for (let i = 0; i < files.length; i++) { files[i] = new (_SevenZArchiveEntry || _load_SevenZArchiveEntry()).SevenZArchiveEntry(); } let isEmptyStream = null; let isEmptyFile = null; let isAnti = null; while (true) { const propertyType = header.readUnsignedByte(); if (propertyType === 0) { break; } const size = readUint64(header); switch (propertyType) { case kEmptyStream: { isEmptyStream = readBits(header, files.length); break; } case kEmptyFile: { if (isEmptyStream == null) { throw new Error("Header format error: kEmptyStream must appear before kEmptyFile"); } isEmptyFile = readBits(header, isEmptyStream.cardinality()); break; } case kAnti: { if (isEmptyStream == null) { throw new Error("Header format error: kEmptyStream must appear before kAnti"); } isAnti = readBits(header, isEmptyStream.cardinality()); break; } case kName: { const external = header.readUnsignedByte(); if (external !== 0) { throw new Error("Not implemented"); } if ((size - 1 & 1) !== 0) { throw new Error("File names length invalid"); } const names = Buffer.allocUnsafe(size - 1); header.get(names); let nextFile = 0; let nextName = 0; for (let i = 0; i < names.length; i += 2) { if (names[i] === 0 && names[i + 1] === 0) { files[nextFile++].name = Buffer.from(names.slice(nextName, i)).toString("utf16le"); nextName = i + 2; } } if (nextName !== names.length || nextFile !== files.length) { throw new Error("Error parsing file names"); } break; } case kCTime: { throw new Error("Must be no kCTime"); } case kATime: { throw new Error("Must be no kATime"); } case kMTime: { throw new Error("Must be no kMTime"); } case kWinAttributes: { const attributesDefined = readAllOrBits(header, files.length); const external = header.readUnsignedByte(); if (external !== 0) { throw new Error("Unimplemented"); } for (let i = 0; i < files.length; i++) { files[i].hasWindowsAttributes = attributesDefined.get(i); if (files[i].hasWindowsAttributes) { files[i].windowsAttributes = header.readInt(); } } break; } case kStartPos: { throw new Error("kStartPos is unsupported"); } case kDummy: { if (skipBytesFully(header, size) < size) { throw new Error("Incomplete kDummy property"); } break; } default: { if (skipBytesFully(header, size) < size) { throw new Error(`Incomplete property of type ${propertyType}`); } break; } } } let nonEmptyFileCounter = 0; let emptyFileCounter = 0; for (let i = 0; i < files.length; i++) { files[i].hasStream = isEmptyStream == null || !isEmptyStream.get(i); if (files[i].hasStream) { files[i].isDirectory = false; files[i].isAntiItem = false; files[i].hasCrc = archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter); files[i].crcValue = archive.subStreamsInfo.crcs[nonEmptyFileCounter]; files[i].size = archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]; ++nonEmptyFileCounter; } else { files[i].isDirectory = isEmptyFile == null || !isEmptyFile.get(emptyFileCounter); files[i].isAntiItem = isAnti != null && isAnti.get(emptyFileCounter); files[i].hasCrc = false; files[i].size = 0; ++emptyFileCounter; } } archive.files = files; calculateStreamMap(archive); } function calculateStreamMap(archive) { const streamMap = new (_Archive || _load_Archive()).StreamMap(); let nextFolderPackStreamIndex = 0; const numFolders = archive.folders != null ? archive.folders.length : 0; for (let i = 0; i < numFolders; i++) { const folder = archive.folders[i]; folder.firstPackedStreamIndex = nextFolderPackStreamIndex; nextFolderPackStreamIndex += folder.packedStreams.length; } let nextPackStreamOffset = 0; const numPackSizes = archive.packedSizes != null ? archive.packedSizes.length : 0; streamMap.packStreamOffsets = new Array(numPackSizes); for (let i = 0; i < numPackSizes; i++) { streamMap.packStreamOffsets[i] = nextPackStreamOffset; nextPackStreamOffset += archive.packedSizes[i]; } streamMap.folderFirstFileIndex = new Array(numFolders); let nextFolderIndex = 0; let nextFolderUnpackStreamIndex = 0; for (let i = 0; i < archive.files.length; i++) { const file = archive.files[i]; if (!file.hasStream && nextFolderUnpackStreamIndex === 0) { file.blockIndex = -1; continue; } if (nextFolderUnpackStreamIndex === 0) { for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) { streamMap.folderFirstFileIndex[nextFolderIndex] = i; const folder = archive.folders[nextFolderIndex]; if (folder.numUnpackSubStreams > 0) { break; } } if (nextFolderIndex >= archive.folders.length) { throw new Error("Too few folders in archive"); } } file.blockIndex = nextFolderIndex; if (!file.hasStream) { continue; } ++nextFolderUnpackStreamIndex; if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) { ++nextFolderIndex; nextFolderUnpackStreamIndex = 0; } } archive.streamMap = streamMap; } function skipBytesFully(input, bytesToSkip) { if (bytesToSkip < 1) { return 0; } const maxSkip = input.remaining(); if (maxSkip < bytesToSkip) { bytesToSkip = maxSkip; } input.skip(bytesToSkip); return bytesToSkip; } function readArchiveProperties(input) { // just skip it let nid = input.readUnsignedByte(); while (nid !== kEnd) { input.skip(readUint64(input)); nid = input.readUnsignedByte(); } } function readStreamsInfo(header, archive) { let nid = header.readUnsignedByte(); if (nid === kPackInfo) { readPackInfo(header, archive); nid = header.readUnsignedByte(); } if (nid === kUnpackInfo) { readUnpackInfo(header, archive); nid = header.readUnsignedByte(); } else { // archive without unpack/coders info archive.folders = []; } if (nid === kSubStreamsInfo) { readSubStreamsInfo(header, archive); nid = header.readUnsignedByte(); } if (nid !== kEnd) { throw new Error("Badly terminated StreamsInfo"); } } function readSubStreamsInfo(header, archive) { for (const folder of archive.folders) { folder.numUnpackSubStreams = 1; } let totalUnpackStreams = archive.folders.length; let nid = header.readUnsignedByte(); if (nid === kNumUnpackStream) { totalUnpackStreams = 0; for (const folder of archive.folders) { const numStreams = readUint64(header); folder.numUnpackSubStreams = numStreams; totalUnpackStreams += numStreams; } nid = header.readUnsignedByte(); } const subStreamsInfo = new SubStreamsInfo(); subStreamsInfo.unpackSizes = new Array(totalUnpackStreams); subStreamsInfo.hasCrc = new (_BitSet || _load_BitSet()).BitSet(totalUnpackStreams); subStreamsInfo.crcs = new Array(totalUnpackStreams); let nextUnpackStream = 0; for (const folder of archive.folders) { if (folder.numUnpackSubStreams === 0) { continue; } let sum = 0; if (nid === kSize) { for (let i = 0; i < folder.numUnpackSubStreams - 1; i++) { const size = readUint64(header); subStreamsInfo.unpackSizes[nextUnpackStream++] = size; sum += size; } } subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum; } if (nid === kSize) { nid = header.readUnsignedByte(); } let numDigests = 0; for (const folder of archive.folders) { if (folder.numUnpackSubStreams !== 1 || !folder.hasCrc) { numDigests += folder.numUnpackSubStreams; } } if (nid === kCRC) { const hasMissingCrc = readAllOrBits(header, numDigests); const missingCrcs = new Array(numDigests); for (let i = 0; i < numDigests; i++) { if (hasMissingCrc.get(i)) { missingCrcs[i] = header.readUnsignedInt(); } } let nextCrc = 0; let nextMissingCrc = 0; for (const folder of archive.folders) { if (folder.numUnpackSubStreams === 1 && folder.hasCrc) { subStreamsInfo.hasCrc.set(nextCrc); subStreamsInfo.crcs[nextCrc] = folder.crc; ++nextCrc; } else { for (let i = 0; i < folder.numUnpackSubStreams; i++) { if (hasMissingCrc.get(nextMissingCrc)) { subStreamsInfo.hasCrc.set(nextCrc); } else { subStreamsInfo.hasCrc.clear(nextCrc); } subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc]; ++nextCrc; ++nextMissingCrc; } } } nid = header.readUnsignedByte(); } if (nid !== kEnd) { throw new Error("Badly terminated SubStreamsInfo"); } archive.subStreamsInfo = subStreamsInfo; } function readUnpackInfo(header, archive) { let nid = header.readUnsignedByte(); if (nid !== kFolder) { throw new Error(`Expected kFolder, got ${nid}`); } const numFolders = readUint64(header); const folders = new Array(numFolders); archive.folders = folders; const external = header.readUnsignedByte(); if (external !== 0) { throw new Error("External unsupported"); } for (let i = 0; i < numFolders; i++) { folders[i] = readFolder(header); } nid = header.readUnsignedByte(); if (nid !== kCodersUnpackSize) { throw new Error(`Expected kCodersUnpackSize, got ${nid}`); } for (const folder of folders) { folder.unpackSizes = new Array(folder.totalOutputStreams); for (let i = 0; i < folder.totalOutputStreams; i++) { folder.unpackSizes[i] = readUint64(header); } } nid = header.readUnsignedByte(); if (nid === kCRC) { const crcsDefined = readAllOrBits(header, numFolders); for (let i = 0; i < numFolders; i++) { if (crcsDefined.get(i)) { folders[i].hasCrc = true; folders[i].crc = 0xffffFFFF & header.readInt(); } else { folders[i].hasCrc = false; } } nid = header.readUnsignedByte(); } if (nid !== kEnd) { throw new Error("Badly terminated UnpackInfo"); } } function readFolder(header) { const folder = new (_Folder || _load_Folder()).Folder(); const numCoders = readUint64(header); const coders = new Array(numCoders); let totalInStreams = 0; let totalOutStreams = 0; for (let i = 0; i < coders.length; i++) { coders[i] = new (_Folder || _load_Folder()).Coder(); const bits = header.readUnsignedByte(); const idSize = bits & 0xf; const isSimple = (bits & 0x10) === 0; const hasAttributes = (bits & 0x20) !== 0; const moreAlternativeMethods = (bits & 0x80) !== 0; coders[i].decompressionMethodId = Buffer.allocUnsafe(idSize); header.get(coders[i].decompressionMethodId); if (isSimple) { coders[i].numInStreams = 1; coders[i].numOutStreams = 1; } else { coders[i].numInStreams = readUint64(header); coders[i].numOutStreams = readUint64(header); } totalInStreams += coders[i].numInStreams; totalOutStreams += coders[i].numOutStreams; if (hasAttributes) { const propertiesSize = readUint64(header); coders[i].properties = Buffer.allocUnsafe(propertiesSize); header.get(coders[i].properties); } // would need to keep looping as above: // noinspection LoopStatementThatDoesntLoopJS while (moreAlternativeMethods) { throw new Error("Alternative methods are unsupported, please report. The reference implementation doesn't support them either."); } } folder.coders = coders; folder.totalInputStreams = totalInStreams; folder.totalOutputStreams = totalOutStreams; if (totalOutStreams === 0) { throw new Error("Total output streams can't be 0"); } const numBindPairs = totalOutStreams - 1; const bindPairs = new Array(numBindPairs); for (let i = 0; i < numBindPairs; i++) { bindPairs[i] = new (_Folder || _load_Folder()).BindPair(); bindPairs[i].inIndex = readUint64(header); bindPairs[i].outIndex = readUint64(header); } folder.bindPairs = bindPairs; if (totalInStreams < numBindPairs) { throw new Error("Total input streams can't be less than the number of bind pairs"); } const numPackedStreams = totalInStreams - numBindPairs; const packedStreams = new Array(numPackedStreams); if (numPackedStreams === 1) { let i; for (i = 0; i < totalInStreams; i++) { if (folder.findBindPairForInStream(i) < 0) { break; } } if (i === totalInStreams) { throw new Error("Couldn't find stream's bind pair index"); } packedStreams[0] = i; } else if (numPackedStreams > 1) { for (let i = 0; i < numPackedStreams; i++) { packedStreams[i] = readUint64(header); } } folder.packedStreams = packedStreams; return folder; } function readPackInfo(header, archive) { archive.packPosition = readUint64(header); const numPackStreams = readUint64(header); let nid = header.readUnsignedByte(); if (nid === kSize) { archive.packedSizes = new Array(numPackStreams); for (let i = 0; i < archive.packedSizes.length; i++) { archive.packedSizes[i] = readUint64(header); } nid = header.readUnsignedByte(); } if (nid === kCRC) { // just read and ignore crc const packedCrcsDefined = readAllOrBits(header, numPackStreams); for (let i = 0; i < numPackStreams; i++) { if (packedCrcsDefined.get(i)) { header.skip(4); } } nid = header.readUnsignedByte(); } if (nid !== kEnd) { throw new Error(`Badly terminated PackInfo (${nid})`); } } function readAllOrBits(header, size) { const areAllDefined = header.readUnsignedByte(); let bits; if (areAllDefined !== 0) { bits = new (_BitSet || _load_BitSet()).BitSet(size); for (let i = 0; i < size; i++) { bits.set(i); } } else { bits = readBits(header, size); } return bits; } function readBits(header, size) { const bits = new (_BitSet || _load_BitSet()).BitSet(size); let mask = 0; let cache = 0; for (let i = 0; i < size; i++) { if (mask === 0) { mask = 0x80; cache = header.readUnsignedByte(); } if ((cache & mask) !== 0) { bits.set(i); } else { bits.clear(i); } mask >>>= 1; } return bits; } function readUint64(input) { // long rather than int as it might get shifted beyond the range of an int const firstByte = input.readUnsignedByte(); let mask = 0x80; let value = 0; for (let i = 0; i < 8; i++) { if ((firstByte & mask) === 0) { return value | (firstByte & mask - 1) << 8 * i; } const nextByte = input.readUnsignedByte(); value |= nextByte << 8 * i; mask >>>= 1; } return value; } class StartHeader { // noinspection JSUnusedGlobalSymbols constructor(nextHeaderOffset, nextHeaderSize, nextHeaderCrc) { this.nextHeaderOffset = nextHeaderOffset; this.nextHeaderSize = nextHeaderSize; this.nextHeaderCrc = nextHeaderCrc; } } exports.StartHeader = StartHeader; class SubStreamsInfo {} exports.SubStreamsInfo = SubStreamsInfo; //# sourceMappingURL=SevenZFile.js.map