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.
		
		
		
		
		
			
		
			
				
					
					
						
							294 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							294 lines
						
					
					
						
							11 KiB
						
					
					
				"use strict";
 | 
						|
var readerFor = require("./reader/readerFor");
 | 
						|
var utils = require("./utils");
 | 
						|
var CompressedObject = require("./compressedObject");
 | 
						|
var crc32fn = require("./crc32");
 | 
						|
var utf8 = require("./utf8");
 | 
						|
var compressions = require("./compressions");
 | 
						|
var support = require("./support");
 | 
						|
 | 
						|
var MADE_BY_DOS = 0x00;
 | 
						|
var MADE_BY_UNIX = 0x03;
 | 
						|
 | 
						|
/**
 | 
						|
 * Find a compression registered in JSZip.
 | 
						|
 * @param {string} compressionMethod the method magic to find.
 | 
						|
 * @return {Object|null} the JSZip compression object, null if none found.
 | 
						|
 */
 | 
						|
var findCompression = function(compressionMethod) {
 | 
						|
    for (var method in compressions) {
 | 
						|
        if (!Object.prototype.hasOwnProperty.call(compressions, method)) {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
        if (compressions[method].magic === compressionMethod) {
 | 
						|
            return compressions[method];
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
};
 | 
						|
 | 
						|
// class ZipEntry {{{
 | 
						|
/**
 | 
						|
 * An entry in the zip file.
 | 
						|
 * @constructor
 | 
						|
 * @param {Object} options Options of the current file.
 | 
						|
 * @param {Object} loadOptions Options for loading the stream.
 | 
						|
 */
 | 
						|
function ZipEntry(options, loadOptions) {
 | 
						|
    this.options = options;
 | 
						|
    this.loadOptions = loadOptions;
 | 
						|
}
 | 
						|
ZipEntry.prototype = {
 | 
						|
    /**
 | 
						|
     * say if the file is encrypted.
 | 
						|
     * @return {boolean} true if the file is encrypted, false otherwise.
 | 
						|
     */
 | 
						|
    isEncrypted: function() {
 | 
						|
        // bit 1 is set
 | 
						|
        return (this.bitFlag & 0x0001) === 0x0001;
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * say if the file has utf-8 filename/comment.
 | 
						|
     * @return {boolean} true if the filename/comment is in utf-8, false otherwise.
 | 
						|
     */
 | 
						|
    useUTF8: function() {
 | 
						|
        // bit 11 is set
 | 
						|
        return (this.bitFlag & 0x0800) === 0x0800;
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * Read the local part of a zip file and add the info in this object.
 | 
						|
     * @param {DataReader} reader the reader to use.
 | 
						|
     */
 | 
						|
    readLocalPart: function(reader) {
 | 
						|
        var compression, localExtraFieldsLength;
 | 
						|
 | 
						|
        // we already know everything from the central dir !
 | 
						|
        // If the central dir data are false, we are doomed.
 | 
						|
        // On the bright side, the local part is scary  : zip64, data descriptors, both, etc.
 | 
						|
        // The less data we get here, the more reliable this should be.
 | 
						|
        // Let's skip the whole header and dash to the data !
 | 
						|
        reader.skip(22);
 | 
						|
        // in some zip created on windows, the filename stored in the central dir contains \ instead of /.
 | 
						|
        // Strangely, the filename here is OK.
 | 
						|
        // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
 | 
						|
        // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
 | 
						|
        // Search "unzip mismatching "local" filename continuing with "central" filename version" on
 | 
						|
        // the internet.
 | 
						|
        //
 | 
						|
        // I think I see the logic here : the central directory is used to display
 | 
						|
        // content and the local directory is used to extract the files. Mixing / and \
 | 
						|
        // may be used to display \ to windows users and use / when extracting the files.
 | 
						|
        // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394
 | 
						|
        this.fileNameLength = reader.readInt(2);
 | 
						|
        localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
 | 
						|
        // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding.
 | 
						|
        this.fileName = reader.readData(this.fileNameLength);
 | 
						|
        reader.skip(localExtraFieldsLength);
 | 
						|
 | 
						|
        if (this.compressedSize === -1 || this.uncompressedSize === -1) {
 | 
						|
            throw new Error("Bug or corrupted zip : didn't get enough information from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)");
 | 
						|
        }
 | 
						|
 | 
						|
        compression = findCompression(this.compressionMethod);
 | 
						|
        if (compression === null) { // no compression found
 | 
						|
            throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")");
 | 
						|
        }
 | 
						|
        this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize));
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Read the central part of a zip file and add the info in this object.
 | 
						|
     * @param {DataReader} reader the reader to use.
 | 
						|
     */
 | 
						|
    readCentralPart: function(reader) {
 | 
						|
        this.versionMadeBy = reader.readInt(2);
 | 
						|
        reader.skip(2);
 | 
						|
        // this.versionNeeded = reader.readInt(2);
 | 
						|
        this.bitFlag = reader.readInt(2);
 | 
						|
        this.compressionMethod = reader.readString(2);
 | 
						|
        this.date = reader.readDate();
 | 
						|
        this.crc32 = reader.readInt(4);
 | 
						|
        this.compressedSize = reader.readInt(4);
 | 
						|
        this.uncompressedSize = reader.readInt(4);
 | 
						|
        var fileNameLength = reader.readInt(2);
 | 
						|
        this.extraFieldsLength = reader.readInt(2);
 | 
						|
        this.fileCommentLength = reader.readInt(2);
 | 
						|
        this.diskNumberStart = reader.readInt(2);
 | 
						|
        this.internalFileAttributes = reader.readInt(2);
 | 
						|
        this.externalFileAttributes = reader.readInt(4);
 | 
						|
        this.localHeaderOffset = reader.readInt(4);
 | 
						|
 | 
						|
        if (this.isEncrypted()) {
 | 
						|
            throw new Error("Encrypted zip are not supported");
 | 
						|
        }
 | 
						|
 | 
						|
        // will be read in the local part, see the comments there
 | 
						|
        reader.skip(fileNameLength);
 | 
						|
        this.readExtraFields(reader);
 | 
						|
        this.parseZIP64ExtraField(reader);
 | 
						|
        this.fileComment = reader.readData(this.fileCommentLength);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Parse the external file attributes and get the unix/dos permissions.
 | 
						|
     */
 | 
						|
    processAttributes: function () {
 | 
						|
        this.unixPermissions = null;
 | 
						|
        this.dosPermissions = null;
 | 
						|
        var madeBy = this.versionMadeBy >> 8;
 | 
						|
 | 
						|
        // Check if we have the DOS directory flag set.
 | 
						|
        // We look for it in the DOS and UNIX permissions
 | 
						|
        // but some unknown platform could set it as a compatibility flag.
 | 
						|
        this.dir = this.externalFileAttributes & 0x0010 ? true : false;
 | 
						|
 | 
						|
        if(madeBy === MADE_BY_DOS) {
 | 
						|
            // first 6 bits (0 to 5)
 | 
						|
            this.dosPermissions = this.externalFileAttributes & 0x3F;
 | 
						|
        }
 | 
						|
 | 
						|
        if(madeBy === MADE_BY_UNIX) {
 | 
						|
            this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF;
 | 
						|
            // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8);
 | 
						|
        }
 | 
						|
 | 
						|
        // fail safe : if the name ends with a / it probably means a folder
 | 
						|
        if (!this.dir && this.fileNameStr.slice(-1) === "/") {
 | 
						|
            this.dir = true;
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Parse the ZIP64 extra field and merge the info in the current ZipEntry.
 | 
						|
     * @param {DataReader} reader the reader to use.
 | 
						|
     */
 | 
						|
    parseZIP64ExtraField: function() {
 | 
						|
        if (!this.extraFields[0x0001]) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        // should be something, preparing the extra reader
 | 
						|
        var extraReader = readerFor(this.extraFields[0x0001].value);
 | 
						|
 | 
						|
        // I really hope that these 64bits integer can fit in 32 bits integer, because js
 | 
						|
        // won't let us have more.
 | 
						|
        if (this.uncompressedSize === utils.MAX_VALUE_32BITS) {
 | 
						|
            this.uncompressedSize = extraReader.readInt(8);
 | 
						|
        }
 | 
						|
        if (this.compressedSize === utils.MAX_VALUE_32BITS) {
 | 
						|
            this.compressedSize = extraReader.readInt(8);
 | 
						|
        }
 | 
						|
        if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) {
 | 
						|
            this.localHeaderOffset = extraReader.readInt(8);
 | 
						|
        }
 | 
						|
        if (this.diskNumberStart === utils.MAX_VALUE_32BITS) {
 | 
						|
            this.diskNumberStart = extraReader.readInt(4);
 | 
						|
        }
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * Read the central part of a zip file and add the info in this object.
 | 
						|
     * @param {DataReader} reader the reader to use.
 | 
						|
     */
 | 
						|
    readExtraFields: function(reader) {
 | 
						|
        var end = reader.index + this.extraFieldsLength,
 | 
						|
            extraFieldId,
 | 
						|
            extraFieldLength,
 | 
						|
            extraFieldValue;
 | 
						|
 | 
						|
        if (!this.extraFields) {
 | 
						|
            this.extraFields = {};
 | 
						|
        }
 | 
						|
 | 
						|
        while (reader.index + 4 < end) {
 | 
						|
            extraFieldId = reader.readInt(2);
 | 
						|
            extraFieldLength = reader.readInt(2);
 | 
						|
            extraFieldValue = reader.readData(extraFieldLength);
 | 
						|
 | 
						|
            this.extraFields[extraFieldId] = {
 | 
						|
                id: extraFieldId,
 | 
						|
                length: extraFieldLength,
 | 
						|
                value: extraFieldValue
 | 
						|
            };
 | 
						|
        }
 | 
						|
 | 
						|
        reader.setIndex(end);
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * Apply an UTF8 transformation if needed.
 | 
						|
     */
 | 
						|
    handleUTF8: function() {
 | 
						|
        var decodeParamType = support.uint8array ? "uint8array" : "array";
 | 
						|
        if (this.useUTF8()) {
 | 
						|
            this.fileNameStr = utf8.utf8decode(this.fileName);
 | 
						|
            this.fileCommentStr = utf8.utf8decode(this.fileComment);
 | 
						|
        } else {
 | 
						|
            var upath = this.findExtraFieldUnicodePath();
 | 
						|
            if (upath !== null) {
 | 
						|
                this.fileNameStr = upath;
 | 
						|
            } else {
 | 
						|
                // ASCII text or unsupported code page
 | 
						|
                var fileNameByteArray =  utils.transformTo(decodeParamType, this.fileName);
 | 
						|
                this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray);
 | 
						|
            }
 | 
						|
 | 
						|
            var ucomment = this.findExtraFieldUnicodeComment();
 | 
						|
            if (ucomment !== null) {
 | 
						|
                this.fileCommentStr = ucomment;
 | 
						|
            } else {
 | 
						|
                // ASCII text or unsupported code page
 | 
						|
                var commentByteArray =  utils.transformTo(decodeParamType, this.fileComment);
 | 
						|
                this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Find the unicode path declared in the extra field, if any.
 | 
						|
     * @return {String} the unicode path, null otherwise.
 | 
						|
     */
 | 
						|
    findExtraFieldUnicodePath: function() {
 | 
						|
        var upathField = this.extraFields[0x7075];
 | 
						|
        if (upathField) {
 | 
						|
            var extraReader = readerFor(upathField.value);
 | 
						|
 | 
						|
            // wrong version
 | 
						|
            if (extraReader.readInt(1) !== 1) {
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            // the crc of the filename changed, this field is out of date.
 | 
						|
            if (crc32fn(this.fileName) !== extraReader.readInt(4)) {
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            return utf8.utf8decode(extraReader.readData(upathField.length - 5));
 | 
						|
        }
 | 
						|
        return null;
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Find the unicode comment declared in the extra field, if any.
 | 
						|
     * @return {String} the unicode comment, null otherwise.
 | 
						|
     */
 | 
						|
    findExtraFieldUnicodeComment: function() {
 | 
						|
        var ucommentField = this.extraFields[0x6375];
 | 
						|
        if (ucommentField) {
 | 
						|
            var extraReader = readerFor(ucommentField.value);
 | 
						|
 | 
						|
            // wrong version
 | 
						|
            if (extraReader.readInt(1) !== 1) {
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            // the crc of the comment changed, this field is out of date.
 | 
						|
            if (crc32fn(this.fileComment) !== extraReader.readInt(4)) {
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            return utf8.utf8decode(extraReader.readData(ucommentField.length - 5));
 | 
						|
        }
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
};
 | 
						|
module.exports = ZipEntry;
 |