Skip to content

Commit

Permalink
Merge pull request #266 from dduponchel/crx_issue_265
Browse files Browse the repository at this point in the history
Handle prepended data.
  • Loading branch information
dduponchel committed Mar 21, 2016
2 parents 585812b + b0e58c2 commit eed21cb
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 17 deletions.
7 changes: 4 additions & 3 deletions lib/arrayReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function ArrayReader(data) {
this.data = data;
this.length = this.data.length;
this.index = 0;
this.zero = 0;

for(var i = 0; i < this.data.length; i++) {
data[i] = data[i] & 0xFF;
Expand All @@ -17,7 +18,7 @@ ArrayReader.prototype = new DataReader();
* @see DataReader.byteAt
*/
ArrayReader.prototype.byteAt = function(i) {
return this.data[i];
return this.data[this.zero + i];
};
/**
* @see DataReader.lastIndexOfSignature
Expand All @@ -29,7 +30,7 @@ ArrayReader.prototype.lastIndexOfSignature = function(sig) {
sig3 = sig.charCodeAt(3);
for (var i = this.length - 4; 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;
return i - this.zero;
}
}

Expand All @@ -43,7 +44,7 @@ ArrayReader.prototype.readData = function(size) {
if(size === 0) {
return [];
}
var result = this.data.slice(this.index, this.index + size);
var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
this.index += size;
return result;
};
Expand Down
3 changes: 2 additions & 1 deletion lib/dataReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ function DataReader(data) {
this.data = null; // type : see implementation
this.length = 0;
this.index = 0;
this.zero = 0;
}
DataReader.prototype = {
/**
Expand All @@ -21,7 +22,7 @@ DataReader.prototype = {
* @throws {Error} an Error if the index is out of bounds.
*/
checkIndex: function(newIndex) {
if (this.length < newIndex || newIndex < 0) {
if (this.length < this.zero + newIndex || newIndex < 0) {
throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?");
}
},
Expand Down
3 changes: 2 additions & 1 deletion lib/nodeBufferReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ function NodeBufferReader(data) {
this.data = data;
this.length = this.data.length;
this.index = 0;
this.zero = 0;
}
NodeBufferReader.prototype = new Uint8ArrayReader();

Expand All @@ -13,7 +14,7 @@ NodeBufferReader.prototype = new Uint8ArrayReader();
*/
NodeBufferReader.prototype.readData = function(size) {
this.checkOffset(size);
var result = this.data.slice(this.index, this.index + size);
var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
this.index += size;
return result;
};
Expand Down
7 changes: 4 additions & 3 deletions lib/stringReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@ function StringReader(data, optimizedBinaryString) {
}
this.length = this.data.length;
this.index = 0;
this.zero = 0;
}
StringReader.prototype = new DataReader();
/**
* @see DataReader.byteAt
*/
StringReader.prototype.byteAt = function(i) {
return this.data.charCodeAt(i);
return this.data.charCodeAt(this.zero + i);
};
/**
* @see DataReader.lastIndexOfSignature
*/
StringReader.prototype.lastIndexOfSignature = function(sig) {
return this.data.lastIndexOf(sig);
return this.data.lastIndexOf(sig) - this.zero;
};
/**
* @see DataReader.readData
*/
StringReader.prototype.readData = function(size) {
this.checkOffset(size);
// this will work because the constructor applied the "& 0xff" mask.
var result = this.data.slice(this.index, this.index + size);
var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
this.index += size;
return result;
};
Expand Down
3 changes: 2 additions & 1 deletion lib/uint8ArrayReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function Uint8ArrayReader(data) {
this.data = data;
this.length = this.data.length;
this.index = 0;
this.zero = 0;
}
}
Uint8ArrayReader.prototype = new ArrayReader();
Expand All @@ -18,7 +19,7 @@ Uint8ArrayReader.prototype.readData = function(size) {
// in IE10, when using subarray(idx, idx), we get the array [0x00] instead of [].
return new Uint8Array(0);
}
var result = this.data.subarray(this.index, this.index + size);
var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size);
this.index += size;
return result;
};
Expand Down
67 changes: 59 additions & 8 deletions lib/zipEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ ZipEntries.prototype = {
throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")");
}
},
/**
* Check if the given signature is at the given index.
* @param {number} askedIndex the index to check.
* @param {string} expectedSignature the signature to expect.
* @return {boolean} true if the signature is here, false otherwise.
*/
isSignature: function(askedIndex, expectedSignature) {
var currentIndex = this.reader.index;
this.reader.setIndex(askedIndex);
var signature = this.reader.readString(4);
var result = signature === expectedSignature;
this.reader.setIndex(currentIndex);
return result;
},
/**
* Read the end of the central directory.
*/
Expand Down Expand Up @@ -129,24 +143,31 @@ ZipEntries.prototype = {
file.readCentralPart(this.reader);
this.files.push(file);
}

if (this.centralDirRecords !== this.files.length) {
if (this.centralDirRecords !== 0 && this.files.length === 0) {
// We expected some records but couldn't find ANY.
// This is really suspicious, as if something went wrong.
throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length);
} else {
// We found some records but not all.
// Something is wrong but we got something for the user: no error here.
// console.warn("expected", this.centralDirRecords, "records in central dir, got", this.files.length);
}
}
},
/**
* Read the end of central directory.
*/
readEndOfCentral: function() {
var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);
if (offset === -1) {
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
// extractible zip for example) but it can give a good hint.
// If an ajax request was used without responseType, we will also
// get unreadable data.
var isGarbage = true;
try {
this.reader.setIndex(0);
this.checkSignature(sig.LOCAL_FILE_HEADER);
isGarbage = false;
} catch (e) {}
var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER);

if (isGarbage) {
throw new Error("Can't find end of central directory : is this a zip file ? " +
Expand All @@ -156,6 +177,7 @@ ZipEntries.prototype = {
}
}
this.reader.setIndex(offset);
var endOfCentralDirOffset = offset;
this.checkSignature(sig.CENTRAL_DIRECTORY_END);
this.readBlockEndOfCentral();

Expand Down Expand Up @@ -184,18 +206,47 @@ ZipEntries.prototype = {

// should look for a zip64 EOCD locator
offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
if (offset === -1) {
if (offset < 0) {
throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");
}
this.reader.setIndex(offset);
this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
this.readBlockZip64EndOfCentralLocator();

// now the zip64 EOCD record
if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) {
// console.warn("ZIP64 end of central directory not where expected.");
this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
if (this.relativeOffsetEndOfZip64CentralDir < 0) {
throw new Error("Corrupted zip : can't find the ZIP64 end of central directory");
}
}
this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
this.readBlockZip64EndOfCentral();
}

var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize;
if (this.zip64) {
expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator
expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize;
}

var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset;

if (extraBytes > 0) {
// console.warn(extraBytes, "extra bytes at beginning or within zipfile");
if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) {
// The offsets seem wrong, but we have something at the specified offset.
// So… we keep it.
} else {
// the offset is wrong, update the "zero" of the reader
// this happens if data has been prepended (crx files for example)
this.reader.zero = extraBytes;
}
} else if (extraBytes < 0) {
throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes.");
}
},
prepareReader: function(data) {
var type = utils.getTypeOf(data);
Expand Down
Binary file added test/ref/all_appended_bytes.zip
Binary file not shown.
Binary file added test/ref/all_missing_bytes.zip
Binary file not shown.
Binary file added test/ref/all_prepended_bytes.zip
Binary file not shown.
Binary file added test/ref/zip64_appended_bytes.zip
Binary file not shown.
Binary file added test/ref/zip64_missing_bytes.zip
Binary file not shown.
Binary file added test/ref/zip64_prepended_bytes.zip
Binary file not shown.
44 changes: 44 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,26 @@ test("truncated zip file", function() {
}
});

// dd if=all.zip of=all_missing_bytes.zip bs=32 skip=1
testZipFile("zip file with missing bytes", "ref/all_missing_bytes.zip", function(file) {
try {
var zip = new JSZip(file);
ok(false, "no exception were thrown");
} catch(e) {
ok(e.message.match("Corrupted zip"), "the error message is useful");
}
});

// dd if=zip64.zip of=zip64_missing_bytes.zip bs=32 skip=1
testZipFile("zip64 file with missing bytes", "ref/zip64_missing_bytes.zip", function(file) {
try {
var zip = new JSZip(file);
ok(false, "no exception were thrown");
} catch(e) {
ok(e.message.match("Corrupted zip"), "the error message is useful");
}
});

test("not a zip file", function() {
try {
var zip = new JSZip("I'm not a zip file");
Expand Down Expand Up @@ -1520,6 +1540,30 @@ testZipFile("permissions on windows : file created by izarc, reloaded", "ref/per
testZipFile("permissions on windows : file created by winrar", "ref/permissions/windows_winrar.zip", assertDosPermissions);
testZipFile("permissions on windows : file created by winrar, reloaded", "ref/permissions/windows_winrar.zip", reloadAndAssertDosPermissions);

// cat Hello.txt all.zip > all_prepended_bytes.zip
testZipFile("zip file with prepended bytes", "ref/all_prepended_bytes.zip", function(file) {
var zip = new JSZip(file);
equal(zip.file("Hello.txt").asText(), "Hello World\n", "the zip was correctly read.");
});

// cat all.zip Hello.txt > all_appended_bytes.zip
testZipFile("zip file with appended bytes", "ref/all_appended_bytes.zip", function(file) {
var zip = new JSZip(file);
equal(zip.file("Hello.txt").asText(), "Hello World\n", "the zip was correctly read.");
});

// cat Hello.txt zip64.zip > zip64_prepended_bytes.zip
testZipFile("zip64 file with extra bytes", "ref/zip64_prepended_bytes.zip", function(file) {
var zip = new JSZip(file);
equal(zip.file("Hello.txt").asText(), "Hello World\n", "the zip was correctly read.");
});

// cat zip64.zip Hello.txt > zip64_appended_bytes.zip
testZipFile("zip64 file with extra bytes", "ref/zip64_appended_bytes.zip", function(file) {
var zip = new JSZip(file);
equal(zip.file("Hello.txt").asText(), "Hello World\n", "the zip was correctly read.");
});

// }}} Load file

QUnit.module("Load complex files"); // {{{
Expand Down

0 comments on commit eed21cb

Please sign in to comment.