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
			| 
											2 years ago
										 | "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; |