Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lib/reader/ArrayReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ ArrayReader.prototype.byteAt = function(i) {
/**
* @see DataReader.lastIndexOfSignature
*/
ArrayReader.prototype.lastIndexOfSignature = function(sig) {
ArrayReader.prototype.lastIndexOfSignature = function(sig, endIndex) {
var sig0 = sig.charCodeAt(0),
sig1 = sig.charCodeAt(1),
sig2 = sig.charCodeAt(2),
sig3 = sig.charCodeAt(3);
for (var i = this.length - 4; i >= 0; --i) {
var start = typeof endIndex === "number" ?
Math.min(endIndex + this.zero, this.length - 4) : this.length - 4;
for (var i = start; i >= 0; --i) {
if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) {
return i - this.zero;
}
Expand Down
1 change: 1 addition & 0 deletions lib/reader/DataReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ DataReader.prototype = {
/**
* Find the last occurrence of a zip signature (4 bytes).
* @param {string} sig the signature to find.
* @param {number} [endIndex] if given, only search at or before this index.
* @return {number} the index of the last occurrence, -1 if not found.
*/
lastIndexOfSignature: function() {
Expand Down
5 changes: 3 additions & 2 deletions lib/reader/StringReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ StringReader.prototype.byteAt = function(i) {
/**
* @see DataReader.lastIndexOfSignature
*/
StringReader.prototype.lastIndexOfSignature = function(sig) {
return this.data.lastIndexOf(sig) - this.zero;
StringReader.prototype.lastIndexOfSignature = function(sig, endIndex) {
var fromIndex = typeof endIndex === "number" ? endIndex + this.zero : undefined;
return this.data.lastIndexOf(sig, fromIndex) - this.zero;
};
/**
* @see DataReader.readAndCheckSignature
Expand Down
33 changes: 32 additions & 1 deletion lib/zipEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,42 @@ ZipEntries.prototype = {
}
}
},
/**
* Find the offset of the genuine "end of central directory" record.
*
* The EOCD signature ("PK\x05\x06") can legitimately appear inside the
* archive comment, so the last occurrence in the file is not necessarily
* the real record. As described in APPNOTE.TXT §4.3.16, the genuine EOCD
* satisfies `offset + 22 + commentLength === fileLength` (22 being the
* fixed size of the record). We scan the candidates backward and prefer
* one that matches this invariant. If none does (e.g. a zip with trailing
* bytes appended), we fall back to the last occurrence, keeping the
* historical behavior.
* @return {number} the offset of the EOCD record, -1 if not found.
*/
findEndOfCentral: function() {
var fileLength = this.reader.length - this.reader.zero;
var lastOffset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);
var offset = lastOffset;
while (offset >= 0) {
// the comment length is a 2 bytes little-endian field located 20
// bytes after the signature.
var commentLength = this.reader.byteAt(offset + 20) +
(this.reader.byteAt(offset + 21) << 8);
if (offset + 22 + commentLength === fileLength) {
return offset;
}
// not the genuine record (the signature is likely part of the
// comment): keep scanning backward.
offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END, offset - 1);
}
return lastOffset;
},
/**
* Read the end of central directory.
*/
readEndOfCentral: function() {
var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);
var offset = this.findEndOfCentral();
if (offset < 0) {
// Check if the content is a truncated zip or complete garbage.
// A "LOCAL_FILE_HEADER" is not required at the beginning (auto
Expand Down
22 changes: 22 additions & 0 deletions test/asserts/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,28 @@ QUnit.module("load", function () {
})["catch"](JSZipTestUtils.assertNoError);
});

QUnit.test("load a zip whose comment contains the end of central directory signature", function (assert) {
var done = assert.async();
// "PK\x05\x06" is the end of central directory signature: it can
// legitimately appear inside the archive comment and must not be
// mistaken for the real record (APPNOTE.TXT §4.3.16).
var comment = "\x50\x4b\x05\x06" + "Z".repeat(18);
var zip = new JSZip();
zip.file("a.txt", "hi");
zip.generateAsync({type: "binarystring", comment: comment})
.then(function (generated) {
return JSZip.loadAsync(generated);
})
.then(function (loaded) {
assert.equal(loaded.comment, comment, "the archive comment was correctly read.");
return loaded.file("a.txt").async("string");
})
.then(function (content) {
assert.equal(content, "hi", "the zip was correctly read.");
done();
})["catch"](JSZipTestUtils.assertNoError);
});

// zip -0 extra_attributes.zip Hello.txt
JSZipTestUtils.testZipFile("zip with extra attributes", "ref/extra_attributes.zip", function(assert, file) {
var done = assert.async();
Expand Down