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
			| 
								 
											3 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;
							 |