Mercurial > hg > LGMap
diff geotemco/lib/jszip/jszip-load.js @ 0:57bde4830927
first commit
author | Zoe Hong <zhong@mpiwg-berlin.mpg.de> |
---|---|
date | Tue, 24 Mar 2015 11:37:17 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/geotemco/lib/jszip/jszip-load.js Tue Mar 24 11:37:17 2015 +0100 @@ -0,0 +1,545 @@ +/** + +JSZip - A Javascript class for generating and reading zip files +<http://stuartk.com/jszip> + +(c) 2011 David Duponchel <d.duponchel@gmail.com> +Dual licenced under the MIT license or GPLv3. See LICENSE.markdown. + +**/ +/*global JSZip,JSZipBase64 */ +(function () { + + var MAX_VALUE_16BITS = 65535; + var MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1 + + /** + * Prettify a string read as binary. + * @param {string} str the string to prettify. + * @return {string} a pretty string. + */ + var pretty = function (str) { + var res = '', code, i; + for (i = 0; i < (str||"").length; i++) { + code = str.charCodeAt(i); + res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase(); + } + return res; + }; + + /** + * 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 JSZip.compressions) { + if( !JSZip.compressions.hasOwnProperty(method) ) { continue; } + if (JSZip.compressions[method].magic === compressionMethod) { + return JSZip.compressions[method]; + } + } + return null; + }; + + // class StreamReader {{{ + /** + * Read bytes from a stream. + * Developer tip : when debugging, a watch on pretty(this.reader.stream.slice(this.reader.index)) + * is very useful :) + * @constructor + * @param {String|ArrayBuffer|Uint8Array} stream the stream to read. + */ + function StreamReader(stream) { + this.stream = ""; + if (JSZip.support.uint8array && stream instanceof Uint8Array) { + this.stream = JSZip.utils.uint8Array2String(stream); + } else if (JSZip.support.arraybuffer && stream instanceof ArrayBuffer) { + var bufferView = new Uint8Array(stream); + this.stream = JSZip.utils.uint8Array2String(bufferView); + } else { + this.stream = JSZip.utils.string2binary(stream); + } + this.index = 0; + } + StreamReader.prototype = { + /** + * Check that the offset will not go too far. + * @param {string} offset the additional offset to check. + * @throws {Error} an Error if the offset is out of bounds. + */ + checkOffset : function (offset) { + this.checkIndex(this.index + offset); + }, + /** + * Check that the specifed index will not be too far. + * @param {string} newIndex the index to check. + * @throws {Error} an Error if the index is out of bounds. + */ + checkIndex : function (newIndex) { + if (this.stream.length < newIndex || newIndex < 0) { + throw new Error("End of stream reached (stream length = " + + this.stream.length + ", asked index = " + + (newIndex) + "). Corrupted zip ?"); + } + }, + /** + * Change the index. + * @param {number} newIndex The new index. + * @throws {Error} if the new index is out of the stream. + */ + setIndex : function (newIndex) { + this.checkIndex(newIndex); + this.index = newIndex; + }, + /** + * Skip the next n bytes. + * @param {number} n the number of bytes to skip. + * @throws {Error} if the new index is out of the stream. + */ + skip : function (n) { + this.setIndex(this.index + n); + }, + /** + * Get the byte at the specified index. + * @param {number} i the index to use. + * @return {number} a byte. + */ + byteAt : function(i) { + return this.stream.charCodeAt(i); + }, + /** + * Get the next number with a given byte size. + * @param {number} size the number of bytes to read. + * @return {number} the corresponding number. + */ + readInt : function (size) { + var result = 0, i; + this.checkOffset(size); + for(i = this.index + size - 1; i >= this.index; i--) { + result = (result << 8) + this.byteAt(i); + } + this.index += size; + return result; + }, + /** + * Get the next string with a given byte size. + * @param {number} size the number of bytes to read. + * @return {string} the corresponding string. + */ + readString : function (size) { + this.checkOffset(size); + // this will work because the constructor applied the "& 0xff" mask. + var result = this.stream.slice(this.index, this.index + size); + this.index += size; + return result; + }, + /** + * Get the next date. + * @return {Date} the date. + */ + readDate : function () { + var dostime = this.readInt(4); + return new Date( + ((dostime >> 25) & 0x7f) + 1980, // year + ((dostime >> 21) & 0x0f) - 1, // month + (dostime >> 16) & 0x1f, // day + (dostime >> 11) & 0x1f, // hour + (dostime >> 5) & 0x3f, // minute + (dostime & 0x1f) << 1); // second + } + }; + // }}} end of StreamReader + + // 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 {StreamReader} 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 + this.fileName = reader.readString(this.fileNameLength); + reader.skip(localExtraFieldsLength); + + if (this.compressedSize == -1 || this.uncompressedSize == -1) { + throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + + "(compressedSize == -1 || uncompressedSize == -1)"); + } + this.compressedFileData = reader.readString(this.compressedSize); + + compression = findCompression(this.compressionMethod); + if (compression === null) { // no compression found + throw new Error("Corrupted zip : compression " + pretty(this.compressionMethod) + + " unknown (inner file : " + this.fileName + ")"); + } + this.uncompressedFileData = compression.uncompress(this.compressedFileData); + + if (this.uncompressedFileData.length !== this.uncompressedSize) { + throw new Error("Bug : uncompressed data size mismatch"); + } + + if (this.loadOptions.checkCRC32 && JSZip.prototype.crc32(this.uncompressedFileData) !== this.crc32) { + throw new Error("Corrupted zip : CRC32 mismatch"); + } + }, + + /** + * Read the central part of a zip file and add the info in this object. + * @param {StreamReader} reader the reader to use. + */ + readCentralPart : function(reader) { + this.versionMadeBy = reader.readString(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); + this.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"); + } + + this.fileName = reader.readString(this.fileNameLength); + this.readExtraFields(reader); + this.parseZIP64ExtraField(reader); + this.fileComment = reader.readString(this.fileCommentLength); + + // warning, this is true only for zip with madeBy == DOS (plateform dependent feature) + this.dir = this.externalFileAttributes & 0x00000010 ? true : false; + }, + /** + * Parse the ZIP64 extra field and merge the info in the current ZipEntry. + * @param {StreamReader} reader the reader to use. + */ + parseZIP64ExtraField : function(reader) { + + if(!this.extraFields[0x0001]) { + return; + } + + // should be something, preparing the extra reader + var extraReader = new StreamReader(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 === MAX_VALUE_32BITS) { + this.uncompressedSize = extraReader.readInt(8); + } + if(this.compressedSize === MAX_VALUE_32BITS) { + this.compressedSize = extraReader.readInt(8); + } + if(this.localHeaderOffset === MAX_VALUE_32BITS) { + this.localHeaderOffset = extraReader.readInt(8); + } + if(this.diskNumberStart === MAX_VALUE_32BITS) { + this.diskNumberStart = extraReader.readInt(4); + } + }, + /** + * Read the central part of a zip file and add the info in this object. + * @param {StreamReader} reader the reader to use. + */ + readExtraFields : function(reader) { + var start = reader.index, + extraFieldId, + extraFieldLength, + extraFieldValue; + + this.extraFields = this.extraFields || {}; + + while (reader.index < start + this.extraFieldsLength) { + extraFieldId = reader.readInt(2); + extraFieldLength = reader.readInt(2); + extraFieldValue = reader.readString(extraFieldLength); + + this.extraFields[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + }, + /** + * Apply an UTF8 transformation if needed. + */ + handleUTF8 : function() { + if (this.useUTF8()) { + this.fileName = JSZip.prototype.utf8decode(this.fileName); + this.fileComment = JSZip.prototype.utf8decode(this.fileComment); + } + } + }; + // }}} end of ZipEntry + + // class ZipEntries {{{ + /** + * All the entries in the zip file. + * @constructor + * @param {String|ArrayBuffer|Uint8Array} data the binary stream to load. + * @param {Object} loadOptions Options for loading the stream. + */ + function ZipEntries(data, loadOptions) { + this.files = []; + this.loadOptions = loadOptions; + if (data) { + this.load(data); + } + } + ZipEntries.prototype = { + /** + * Check that the reader is on the speficied signature. + * @param {string} expectedSignature the expected signature. + * @throws {Error} if it is an other signature. + */ + checkSignature : function(expectedSignature) { + var signature = this.reader.readString(4); + if (signature !== expectedSignature) { + throw new Error("Corrupted zip or bug : unexpected signature " + + "(" + pretty(signature) + ", expected " + pretty(expectedSignature) + ")"); + } + }, + /** + * 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); + this.zipComment = this.reader.readString(this.zipCommentLength); + }, + /** + * 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.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.readString(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(JSZip.signature.LOCAL_FILE_HEADER); + file.readLocalPart(this.reader); + file.handleUTF8(); + } + }, + /** + * Read the central directory. + */ + readCentralDir : function() { + var file; + + this.reader.setIndex(this.centralDirOffset); + while(this.reader.readString(4) === JSZip.signature.CENTRAL_FILE_HEADER) { + file = new ZipEntry({ + zip64: this.zip64 + }, this.loadOptions); + file.readCentralPart(this.reader); + this.files.push(file); + } + }, + /** + * Read the end of central directory. + */ + readEndOfCentral : function() { + var offset = this.reader.stream.lastIndexOf(JSZip.signature.CENTRAL_DIRECTORY_END); + if (offset === -1) { + throw new Error("Corrupted zip : can't find end of central directory"); + } + this.reader.setIndex(offset); + this.checkSignature(JSZip.signature.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 === MAX_VALUE_16BITS + || this.diskWithCentralDirStart === MAX_VALUE_16BITS + || this.centralDirRecordsOnThisDisk === MAX_VALUE_16BITS + || this.centralDirRecords === MAX_VALUE_16BITS + || this.centralDirSize === MAX_VALUE_32BITS + || this.centralDirOffset === 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.stream.lastIndexOf(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + if (offset === -1) { + throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator"); + } + this.reader.setIndex(offset); + this.checkSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + this.readBlockZip64EndOfCentralLocator(); + + // now the zip64 EOCD record + this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); + this.checkSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_END); + this.readBlockZip64EndOfCentral(); + } + }, + /** + * Read a zip file and create ZipEntries. + * @param {String|ArrayBuffer|Uint8Array} data the binary string representing a zip file. + */ + load : function(data) { + this.reader = new StreamReader(data); + + this.readEndOfCentral(); + this.readCentralDir(); + this.readLocalFiles(); + } + }; + // }}} end of ZipEntries + + /** + * Implementation of the load method of JSZip. + * It uses the above classes to decode a zip file, and load every files. + * @param {String|ArrayBuffer|Uint8Array} data the data to load. + * @param {Object} options Options for loading the stream. + * options.base64 : is the stream in base64 ? default : false + */ + JSZip.prototype.load = function(data, options) { + var files, zipEntries, i, input; + options = options || {}; + if(options.base64) { + data = JSZipBase64.decode(data); + } + + zipEntries = new ZipEntries(data, options); + files = zipEntries.files; + for (i = 0; i < files.length; i++) { + input = files[i]; + this.file(input.fileName, input.uncompressedFileData, { + binary:true, + optimizedBinaryString:true, + date:input.date, + dir:input.dir + }); + } + + return this; + }; + +}()); +// enforcing Stuk's coding style +// vim: set shiftwidth=3 softtabstop=3 foldmethod=marker: