| 'use strict'; |
| var readerFor = require('./reader/readerFor'); |
| var utils = require('./utils'); |
| var sig = require('./signature'); |
| var ZipEntry = require('./zipEntry'); |
| var utf8 = require('./utf8'); |
| var support = require('./support'); |
| // class ZipEntries {{{ |
| /** |
| * All the entries in the zip file. |
| * @constructor |
| * @param {Object} loadOptions Options for loading the stream. |
| */ |
| function ZipEntries(loadOptions) { |
| this.files = []; |
| this.loadOptions = loadOptions; |
| } |
| ZipEntries.prototype = { |
| /** |
| * Check that the reader is on the specified signature. |
| * @param {string} expectedSignature the expected signature. |
| * @throws {Error} if it is an other signature. |
| */ |
| checkSignature: function(expectedSignature) { |
| if (!this.reader.readAndCheckSignature(expectedSignature)) { |
| this.reader.index -= 4; |
| var signature = this.reader.readString(4); |
| 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. |
| */ |
| readBlockEndOfCentral: function() { |
| this.diskNumber = this.reader.readInt(2); |
| this.diskWithCentralDirStart = this.reader.readInt(2); |
| this.centralDirRecordsOnThisDisk = this.reader.readInt(2); |
| this.centralDirRecords = this.reader.readInt(2); |
| this.centralDirSize = this.reader.readInt(4); |
| this.centralDirOffset = this.reader.readInt(4); |
| |
| this.zipCommentLength = this.reader.readInt(2); |
| // warning : the encoding depends of the system locale |
| // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. |
| // On a windows machine, this field is encoded with the localized windows code page. |
| var zipComment = this.reader.readData(this.zipCommentLength); |
| var decodeParamType = support.uint8array ? "uint8array" : "array"; |
| // To get consistent behavior with the generation part, we will assume that |
| // this is utf8 encoded unless specified otherwise. |
| var decodeContent = utils.transformTo(decodeParamType, zipComment); |
| this.zipComment = this.loadOptions.decodeFileName(decodeContent); |
| }, |
| /** |
| * Read the end of the Zip 64 central directory. |
| * Not merged with the method readEndOfCentral : |
| * The end of central can coexist with its Zip64 brother, |
| * I don't want to read the wrong number of bytes ! |
| */ |
| readBlockZip64EndOfCentral: function() { |
| this.zip64EndOfCentralSize = this.reader.readInt(8); |
| this.reader.skip(4); |
| // this.versionMadeBy = this.reader.readString(2); |
| // this.versionNeeded = this.reader.readInt(2); |
| this.diskNumber = this.reader.readInt(4); |
| this.diskWithCentralDirStart = this.reader.readInt(4); |
| this.centralDirRecordsOnThisDisk = this.reader.readInt(8); |
| this.centralDirRecords = this.reader.readInt(8); |
| this.centralDirSize = this.reader.readInt(8); |
| this.centralDirOffset = this.reader.readInt(8); |
| |
| this.zip64ExtensibleData = {}; |
| var extraDataSize = this.zip64EndOfCentralSize - 44, |
| index = 0, |
| extraFieldId, |
| extraFieldLength, |
| extraFieldValue; |
| while (index < extraDataSize) { |
| extraFieldId = this.reader.readInt(2); |
| extraFieldLength = this.reader.readInt(4); |
| extraFieldValue = this.reader.readData(extraFieldLength); |
| this.zip64ExtensibleData[extraFieldId] = { |
| id: extraFieldId, |
| length: extraFieldLength, |
| value: extraFieldValue |
| }; |
| } |
| }, |
| /** |
| * Read the end of the Zip 64 central directory locator. |
| */ |
| readBlockZip64EndOfCentralLocator: function() { |
| this.diskWithZip64CentralDirStart = this.reader.readInt(4); |
| this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8); |
| this.disksCount = this.reader.readInt(4); |
| if (this.disksCount > 1) { |
| throw new Error("Multi-volumes zip are not supported"); |
| } |
| }, |
| /** |
| * Read the local files, based on the offset read in the central part. |
| */ |
| readLocalFiles: function() { |
| var i, file; |
| for (i = 0; i < this.files.length; i++) { |
| file = this.files[i]; |
| this.reader.setIndex(file.localHeaderOffset); |
| this.checkSignature(sig.LOCAL_FILE_HEADER); |
| file.readLocalPart(this.reader); |
| file.handleUTF8(); |
| file.processAttributes(); |
| } |
| }, |
| /** |
| * Read the central directory. |
| */ |
| readCentralDir: function() { |
| var file; |
| |
| this.reader.setIndex(this.centralDirOffset); |
| while (this.reader.readAndCheckSignature(sig.CENTRAL_FILE_HEADER)) { |
| file = new ZipEntry({ |
| zip64: this.zip64 |
| }, this.loadOptions); |
| 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 < 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 = !this.isSignature(0, sig.LOCAL_FILE_HEADER); |
| |
| if (isGarbage) { |
| throw new Error("Can't find end of central directory : is this a zip file ? " + |
| "If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"); |
| } else { |
| throw new Error("Corrupted zip: can't find end of central directory"); |
| } |
| |
| } |
| this.reader.setIndex(offset); |
| var endOfCentralDirOffset = offset; |
| this.checkSignature(sig.CENTRAL_DIRECTORY_END); |
| this.readBlockEndOfCentral(); |
| |
| |
| /* extract from the zip spec : |
| 4) If one of the fields in the end of central directory |
| record is too small to hold required data, the field |
| should be set to -1 (0xFFFF or 0xFFFFFFFF) and the |
| ZIP64 format record should be created. |
| 5) The end of central directory record and the |
| Zip64 end of central directory locator record must |
| reside on the same disk when splitting or spanning |
| an archive. |
| */ |
| if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) { |
| this.zip64 = true; |
| |
| /* |
| Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from |
| the zip file can fit into a 32bits integer. This cannot be solved : JavaScript represents |
| all numbers as 64-bit double precision IEEE 754 floating point numbers. |
| So, we have 53bits for integers and bitwise operations treat everything as 32bits. |
| see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators |
| and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5 |
| */ |
| |
| // should look for a zip64 EOCD locator |
| offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); |
| 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) { |
| this.reader = readerFor(data); |
| }, |
| /** |
| * Read a zip file and create ZipEntries. |
| * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file. |
| */ |
| load: function(data) { |
| this.prepareReader(data); |
| this.readEndOfCentral(); |
| this.readCentralDir(); |
| this.readLocalFiles(); |
| } |
| }; |
| // }}} end of ZipEntries |
| module.exports = ZipEntries; |