You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					262 lines
				
				12 KiB
			
		
		
			
		
	
	
					262 lines
				
				12 KiB
			| 
											2 years ago
										 | "use strict"; | ||
|  | var readerFor = require("./reader/readerFor"); | ||
|  | var utils = require("./utils"); | ||
|  | var sig = require("./signature"); | ||
|  | var ZipEntry = require("./zipEntry"); | ||
|  | 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; |