diff --git a/headers/entryHeader.js b/headers/entryHeader.js index 1d02a16..6375d0c 100644 --- a/headers/entryHeader.js +++ b/headers/entryHeader.js @@ -74,6 +74,17 @@ module.exports = function () { } }, + get flags_desc() { + return (_flags & Constants.FLG_DESC) > 0; + }, + set flags_desc(val) { + if (val) { + _flags |= Constants.FLG_DESC; + } else { + _flags &= ~Constants.FLG_DESC; + } + }, + get method() { return _method; }, diff --git a/test/assets/stream-nozip64.zip b/test/assets/stream-nozip64.zip new file mode 100644 index 0000000..d664008 Binary files /dev/null and b/test/assets/stream-nozip64.zip differ diff --git a/test/methods/methods.test.js b/test/methods/methods.test.js index 8de1008..d3f15ca 100644 --- a/test/methods/methods.test.js +++ b/test/methods/methods.test.js @@ -44,6 +44,14 @@ describe("adm-zip.js - methods handling local files", () => { ].sort() ); }); + + it("zip.extractAllTo(destination) - streamed file", () => { + const zip = new Zip("./test/assets/stream-nozip64.zip"); + zip.extractAllTo(destination); + const files = walk(destination); + + expect(files.sort()).to.deep.equal([pth.normalize("./test/xxx/lorem.txt")]); + }); }); describe(".extractAllToAsync - sync", () => { diff --git a/zipEntry.js b/zipEntry.js index 6eacc4c..51c44a2 100644 --- a/zipEntry.js +++ b/zipEntry.js @@ -28,15 +28,46 @@ module.exports = function (/** object */ options, /*Buffer*/ input) { } function crc32OK(data) { - // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written - if ((_centralHeader.flags & 0x8) !== 0x8) { + // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the local header is written + if (!_centralHeader.flags_desc) { if (Utils.crc32(data) !== _centralHeader.localHeader.crc) { return false; } } else { - // @TODO: load and check data descriptor header - // The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure - // (optionally preceded by a 4-byte signature) immediately after the compressed data: + const descriptor = {}; + const dataEndOffset = _centralHeader.realDataOffset + _centralHeader.compressedSize; + // no descriptor after compressed data, instead new local header + if (input.readUInt32LE(dataEndOffset) == Constants.LOCSIG || input.readUInt32LE(dataEndOffset) == Constants.CENSIG) { + throw new Error("ADM-ZIP: No descriptor present"); + } + + // get decriptor data + if (input.readUInt32LE(dataEndOffset) == Constants.EXTSIG) { + // descriptor with signature + descriptor.crc = input.readUInt32LE(dataEndOffset + Constants.EXTCRC); + descriptor.compressedSize = input.readUInt32LE(dataEndOffset + Constants.EXTSIZ); + descriptor.size = input.readUInt32LE(dataEndOffset + Constants.EXTLEN); + } else if (input.readUInt16LE(dataEndOffset + 12) === 0x4b50) { + // descriptor without signature (we check is new header starting where we expect) + descriptor.crc = input.readUInt32LE(dataEndOffset + Constants.EXTCRC - 4); + descriptor.compressedSize = input.readUInt32LE(dataEndOffset + Constants.EXTSIZ - 4); + descriptor.size = input.readUInt32LE(dataEndOffset + Constants.EXTLEN - 4); + } else { + throw new Error("ADM-ZIP: Unknown descriptor format"); + } + + // check data integrity + if (descriptor.compressedSize !== _centralHeader.compressedSize || descriptor.size !== _centralHeader.size || descriptor.crc !== _centralHeader.crc) { + throw new Error("ADM-ZIP: Descriptor data is malformed"); + } + if (Utils.crc32(data) !== descriptor.crc) { + return false; + } + + // @TODO: zip64 bit descriptor fields + // if bit 3 is set and any value in local header "zip64 Extended information" extra field are set 0 (place holder) + // then 64-bit descriptor format is used instead of 32-bit + // central header - "zip64 Extended information" extra field should store real values and not place holders } return true; } diff --git a/zipFile.js b/zipFile.js index 354aa28..a33e792 100644 --- a/zipFile.js +++ b/zipFile.js @@ -76,6 +76,10 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { endOffset = -1, // Start offset of the END header commentEnd = 0; + // option to search header form entire file + const trailingSpace = typeof opts.trailingSpace === "boolean" ? opts.trailingSpace : false; + if (trailingSpace) max = 0; + for (i; i >= n; i--) { if (inBuffer[i] !== 0x50) continue; // quick check that the byte is 'P' if (inBuffer.readUInt32LE(i) === Utils.Constants.ENDSIG) { @@ -102,7 +106,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { } } - if (!~endOffset) throw new Error(Utils.Errors.INVALID_FORMAT); + if (endOffset == -1) throw new Error(Utils.Errors.INVALID_FORMAT); mainHeader.loadFromBinary(inBuffer.slice(endOffset, endStart)); if (mainHeader.commentLength) { @@ -225,7 +229,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { const len = name.length; entryList.forEach(function (zipEntry) { - if (zipEntry.entryName.substr(0, len) === name) { + if (zipEntry.entryName.startsWith(name)) { list.push(zipEntry); } });