UNPKG

@progress/jszip-esm

Version:

JSZip fork with bundler-friendly packaging

1,649 lines (1,458 loc) 122 kB
import { Deflate, Inflate } from '@progress/pako-esm'; /** * Let the user use/change some implementations. */ var external = { Promise: Promise }; var support = { base64: true, array: true, string: true, nodebuffer: false, nodestream: false, get arraybuffer() { return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"; }, // Returns true if JSZip can read/generate Uint8Array, false otherwise. get uint8array() { return typeof Uint8Array !== "undefined"; }, get blob() { return blob(); } }; var blob = function() { var supported; if (typeof ArrayBuffer === "undefined") { supported = false; } else { var buffer = new ArrayBuffer(0); try { supported = new Blob([ buffer ], { type: "application/zip" }).size === 0; } catch (e) { supported = false; } } blob = function () { return supported; }; return supported; }; // private property var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; // public method for encoding var encode = function(input) { var output = []; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0, len = input.length, remainingBytes = len; var isArray = typeof input !== "string"; while (i < input.length) { remainingBytes = len - i; if (!isArray) { chr1 = input.charCodeAt(i++); chr2 = i < len ? input.charCodeAt(i++) : 0; chr3 = i < len ? input.charCodeAt(i++) : 0; } else { chr1 = input[i++]; chr2 = i < len ? input[i++] : 0; chr3 = i < len ? input[i++] : 0; } enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = remainingBytes > 1 ? (((chr2 & 15) << 2) | (chr3 >> 6)) : 64; enc4 = remainingBytes > 2 ? (chr3 & 63) : 64; output.push(_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4)); } return output.join(""); }; // public method for decoding var decode = function(input) { var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0, resultIndex = 0; var dataUrlPrefix = "data:"; if (input.substr(0, dataUrlPrefix.length) === dataUrlPrefix) { // This is a common error: people give a data url // (data:image/png;base64,iVBOR...) with a {base64: true} and // wonders why things don't work. // We can detect that the string input looks like a data url but we // *can't* be sure it is one: removing everything up to the comma would // be too dangerous. throw new Error("Invalid base64 input, it looks like a data url."); } input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); var totalLength = input.length * 3 / 4; if(input.charAt(input.length - 1) === _keyStr.charAt(64)) { totalLength--; } if(input.charAt(input.length - 2) === _keyStr.charAt(64)) { totalLength--; } if (totalLength % 1 !== 0) { // totalLength is not an integer, the length does not match a valid // base64 content. That can happen if: // - the input is not a base64 content // - the input is *almost* a base64 content, with a extra chars at the // beginning or at the end // - the input uses a base64 letiant (base64url for example) throw new Error("Invalid base64 input, bad content length."); } var output; if (support.uint8array) { output = new Uint8Array(totalLength|0); } else { output = new Array(totalLength|0); } while (i < input.length) { enc1 = _keyStr.indexOf(input.charAt(i++)); enc2 = _keyStr.indexOf(input.charAt(i++)); enc3 = _keyStr.indexOf(input.charAt(i++)); enc4 = _keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output[resultIndex++] = chr1; if (enc3 !== 64) { output[resultIndex++] = chr2; } if (enc4 !== 64) { output[resultIndex++] = chr3; } } return output; }; /** * Convert a string that pass as a "binary string": it should represent a byte * array but may have > 255 char codes. Be sure to take only the first byte * and returns the byte array. * @param {String} str the string to transform. * @return {Array|Uint8Array} the string in a binary format. */ function string2binary(str) { var result = null; if (support.uint8array) { result = new Uint8Array(str.length); } else { result = new Array(str.length); } return stringToArrayLike(str, result); } /** * Create a new blob with the given content and the given type. * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use * an Uint8Array because the stock browser of android 4 won't accept it (it * will be silently converted to a string, "[object Uint8Array]"). * * Use only ONE part to build the blob to avoid a memory leak in IE11 / Edge: * when a large amount of Array is used to create the Blob, the amount of * memory consumed is nearly 100 times the original data amount. * * @param {String} type the mime type of the blob. * @return {Blob} the created blob. */ var newBlob = function(part, type) { checkSupport("blob"); // Blob constructor return new Blob([part], { type: type }); }; /** * The identity function. * @param {Object} input the input. * @return {Object} the same input. */ function identity(input) { return input; } /** * Fill in an array with a string. * @param {String} str the string to use. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated). * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array. */ function stringToArrayLike(str, array) { for (var i = 0; i < str.length; ++i) { array[i] = str.charCodeAt(i) & 0xFF; } return array; } /** * Transform an array of int into a string, chunk by chunk. * See the performances notes on arrayLikeToString. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. * @param {String} type the type of the array. * @param {Integer} chunk the chunk size. * @return {String} the resulting string. * @throws Error if the chunk is too big for the stack. */ function stringifyByChunk(array, type, chunk) { var result = [], k = 0, len = array.length; // shortcut if (len <= chunk) { return String.fromCharCode.apply(null, array); } while (k < len) { if (type === "array") { result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len)))); } else { result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len)))); } k += chunk; } return result.join(""); } /** * Call String.fromCharCode on every item in the array. * This is the naive implementation, which generate A LOT of intermediate string. * This should be used when everything else fail. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. * @return {String} the result. */ function stringifyByChar(array) { var resultStr = ""; for(var i = 0; i < array.length; i++) { resultStr += String.fromCharCode(array[i]); } return resultStr; } /** * true if the browser accepts to use String.fromCharCode on Uint8Array */ var fromCharCodeSupportsTypedArrays = function () { var supported; try { supported = support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1; } catch (e) { supported = false; } fromCharCodeSupportsTypedArrays = function () { return supported; }; return supported; }; /** * Transform an array-like object to a string. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. * @return {String} the result. */ function arrayLikeToString(array) { // Performances notes : // -------------------- // String.fromCharCode.apply(null, array) is the fastest, see // see http://jsperf.com/converting-a-uint8array-to-a-string/2 // but the stack is limited (and we can get huge arrays !). // // result += String.fromCharCode(array[i]); generate too many strings ! // // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 // TODO : we now have workers that split the work. Do we still need that ? var chunk = 65536, type = getTypeOf(array), canUseApply = true; if (type === "uint8array") { canUseApply = fromCharCodeSupportsTypedArrays(); } if (canUseApply) { while (chunk > 1) { try { return stringifyByChunk(array, type, chunk); } catch (e) { chunk = Math.floor(chunk / 2); } } } // no apply or chunk error : slow and painful algorithm // default browser on android 4.* return stringifyByChar(array); } var applyFromCharCode = arrayLikeToString; /** * Copy the data from an array-like to an other array-like. * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array. * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated. * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array. */ function arrayLikeToArrayLike(arrayFrom, arrayTo) { for (var i = 0; i < arrayFrom.length; i++) { arrayTo[i] = arrayFrom[i]; } return arrayTo; } // a matrix containing functions to transform everything into everything. var transform = { // string to ? "string": { "string": identity, "array": function(input) { return stringToArrayLike(input, new Array(input.length)); }, "arraybuffer": function(input) { return transform["string"]["uint8array"](input).buffer; }, "uint8array": function(input) { return stringToArrayLike(input, new Uint8Array(input.length)); } }, // array to ? "array": { "string": arrayLikeToString, "array": identity, "arraybuffer": function(input) { return (new Uint8Array(input)).buffer; }, "uint8array": function(input) { return new Uint8Array(input); } }, // arraybuffer to ? "arraybuffer": { "string": function(input) { return arrayLikeToString(new Uint8Array(input)); }, "array": function(input) { return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); }, "arraybuffer": identity, "uint8array": function(input) { return new Uint8Array(input); } }, // uint8array to ? "uint8array": { "string": arrayLikeToString, "array": function(input) { return arrayLikeToArrayLike(input, new Array(input.length)); }, "arraybuffer": function(input) { return input.buffer; }, "uint8array": identity } }; /** * Transform an input into any type. * The supported output type are : string, array, uint8array, arraybuffer. * If no output type is specified, the unmodified input will be returned. * @param {String} outputType the output type. * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert. * @throws {Error} an Error if the browser doesn't support the requested output type. */ var transformTo = function(outputType, input) { if (!input) { // undefined, null, etc // an empty string won't harm. input = ""; } if (!outputType) { return input; } checkSupport(outputType); var inputType = getTypeOf(input); var result = transform[inputType][outputType](input); return result; }; /** * Resolve all relative path components, "." and "..", in a path. If these relative components * traverse above the root then the resulting path will only contain the final path component. * * All empty components, e.g. "//", are removed. * @param {string} path A path with / or \ separators * @returns {string} The path with all relative path components resolved. */ var resolve = function(path) { var parts = path.split("/"); var result = []; for (var index = 0; index < parts.length; index++) { var part = parts[index]; // Allow the first and last component to be empty for trailing slashes. if (part === "." || (part === "" && index !== 0 && index !== parts.length - 1)) { continue; } else if (part === "..") { result.pop(); } else { result.push(part); } } return result.join("/"); }; /** * Return the type of the input. * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer. * @param {Object} input the input to identify. * @return {String} the (lowercase) type of the input. */ var getTypeOf = function(input) { if (typeof input === "string") { return "string"; } if (Object.prototype.toString.call(input) === "[object Array]") { return "array"; } if (support.uint8array && input instanceof Uint8Array) { return "uint8array"; } if (support.arraybuffer && input instanceof ArrayBuffer) { return "arraybuffer"; } }; /** * Throw an exception if the type is not supported. * @param {String} type the type to check. * @throws {Error} an Error if the browser doesn't support the requested type. */ var checkSupport = function(type) { var supported = support[type.toLowerCase()]; if (!supported) { throw new Error(type + " is not supported by this platform"); } }; 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; }; /** * Defer the call of a function. * @param {Function} callback the function to call asynchronously. * @param {Array} args the arguments to give to the callback. */ var delay = function(callback, args, self) { setTimeout(function() { callback.apply(self || null, args || []); }, 0); }; /** * Merge the objects passed as parameters into a new one. * @private * @param {...Object} var_args All objects to merge. * @return {Object} a new object with the data of the others. */ var extend = function() { var arguments$1 = arguments; var result = {}, i, attr; for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers for (attr in arguments[i]) { if (Object.hasOwnProperty.call(arguments$1[i], attr) && typeof result[attr] === "undefined") { result[attr] = arguments$1[i][attr]; } } } return result; }; /** * Transform arbitrary content into a Promise. * @param {String} name a name for the content being processed. * @param {Object} inputData the content to process. * @param {Boolean} isBinary true if the content is not an unicode string * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character. * @param {Boolean} isBase64 true if the string content is encoded with base64. * @return {Promise} a promise in a format usable by JSZip. */ var prepareContent = function(name, inputData, isBinary, isOptimizedBinaryString, isBase64) { // if inputData is already a promise, this flatten it. var promise = external.Promise.resolve(inputData).then(function(data) { var isBlob = support.blob && (data instanceof Blob || ['[object File]', '[object Blob]'].indexOf(Object.prototype.toString.call(data)) !== -1); if (isBlob && typeof FileReader !== "undefined") { return new external.Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function(e) { resolve(e.target.result); }; reader.onerror = function(e) { reject(e.target.error); }; reader.readAsArrayBuffer(data); }); } else { return data; } }); return promise.then(function(data) { var dataType = getTypeOf(data); if (!dataType) { return external.Promise.reject( new Error("Can't read the data of '" + name + "'. Is it " + "in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?") ); } // special case : it's way easier to work with Uint8Array than with ArrayBuffer if (dataType === "arraybuffer") { data = transformTo("uint8array", data); } else if (dataType === "string") { if (isBase64) { data = decode(data); } else if (isBinary) { // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask if (isOptimizedBinaryString !== true) { // this is a string, not in a base64 format. // Be sure that this is a correct "binary string" data = string2binary(data); } } } return data; }); }; /** * A worker that does nothing but passing chunks to the next one. This is like * a nodejs stream but with some differences. On the good side : * - it works on IE 6-9 without any issue / polyfill * - it weights less than the full dependencies bundled with browserify * - it forwards errors (no need to declare an error handler EVERYWHERE) * * A chunk is an object with 2 attributes : `meta` and `data`. The former is an * object containing anything (`percent` for example), see each worker for more * details. The latter is the real data (String, Uint8Array, etc). * * @constructor * @param {String} name the name of the stream (mainly used for debugging purposes) */ var GenericWorker = function GenericWorker(name) { // the name of the worker this.name = name || "default"; // an object containing metadata about the workers chain this.streamInfo = {}; // an error which happened when the worker was paused this.generatedError = null; // an object containing metadata to be merged by this worker into the general metadata this.extraStreamInfo = {}; // true if the stream is paused (and should not do anything), false otherwise this.isPaused = true; // true if the stream is finished (and should not do anything), false otherwise this.isFinished = false; // true if the stream is locked to prevent further structure updates (pipe), false otherwise this.isLocked = false; // the event listeners this._listeners = { 'data':[], 'end':[], 'error':[] }; // the previous worker, if any this.previous = null; }; /** * Push a chunk to the next workers. * @param {Object} chunk the chunk to push */ GenericWorker.prototype.push = function push (chunk) { this.emit("data", chunk); }; /** * End the stream. * @return {Boolean} true if this call ended the worker, false otherwise. */ GenericWorker.prototype.end = function end () { if (this.isFinished) { return false; } this.flush(); try { this.emit("end"); this.cleanUp(); this.isFinished = true; } catch (e) { this.emit("error", e); } return true; }; /** * End the stream with an error. * @param {Error} e the error which caused the premature end. * @return {Boolean} true if this call ended the worker with an error, false otherwise. */ GenericWorker.prototype.error = function error (e) { if (this.isFinished) { return false; } if(this.isPaused) { this.generatedError = e; } else { this.isFinished = true; this.emit("error", e); // in the workers chain exploded in the middle of the chain, // the error event will go downward but we also need to notify // workers upward that there has been an error. if(this.previous) { this.previous.error(e); } this.cleanUp(); } return true; }; /** * Add a callback on an event. * @param {String} name the name of the event (data, end, error) * @param {Function} listener the function to call when the event is triggered * @return {GenericWorker} the current object for chainability */ GenericWorker.prototype.on = function on (name, listener) { this._listeners[name].push(listener); return this; }; /** * Clean any references when a worker is ending. */ GenericWorker.prototype.cleanUp = function cleanUp () { this.streamInfo = this.generatedError = this.extraStreamInfo = null; this._listeners = []; }; /** * Trigger an event. This will call registered callback with the provided arg. * @param {String} name the name of the event (data, end, error) * @param {Object} arg the argument to call the callback with. */ GenericWorker.prototype.emit = function emit (name, arg) { if (this._listeners[name]) { for(var i = 0; i < this._listeners[name].length; i++) { this._listeners[name][i].call(this, arg); } } }; /** * Chain a worker with an other. * @param {Worker} next the worker receiving events from the current one. * @return {worker} the next worker for chainability */ GenericWorker.prototype.pipe = function pipe (next) { return next.registerPrevious(this); }; /** * Same as `pipe` in the other direction. * Using an API with `pipe(next)` is very easy. * Implementing the API with the point of view of the next one registering * a source is easier, see the ZipFileWorker. * @param {Worker} previous the previous worker, sending events to this one * @return {Worker} the current worker for chainability */ GenericWorker.prototype.registerPrevious = function registerPrevious (previous) { if (this.isLocked) { throw new Error("The stream '" + this + "' has already been used."); } // sharing the streamInfo... this.streamInfo = previous.streamInfo; // ... and adding our own bits this.mergeStreamInfo(); this.previous = previous; var self = this; previous.on('data', function (chunk) { self.processChunk(chunk); }); previous.on('end', function () { self.end(); }); previous.on('error', function (e) { self.error(e); }); return this; }; /** * Pause the stream so it doesn't send events anymore. * @return {Boolean} true if this call paused the worker, false otherwise. */ GenericWorker.prototype.pause = function pause () { if(this.isPaused || this.isFinished) { return false; } this.isPaused = true; if(this.previous) { this.previous.pause(); } return true; }; /** * Resume a paused stream. * @return {Boolean} true if this call resumed the worker, false otherwise. */ GenericWorker.prototype.resume = function resume () { if(!this.isPaused || this.isFinished) { return false; } this.isPaused = false; // if true, the worker tried to resume but failed var withError = false; if(this.generatedError) { this.error(this.generatedError); withError = true; } if(this.previous) { this.previous.resume(); } return !withError; }; /** * Flush any remaining bytes as the stream is ending. */ GenericWorker.prototype.flush = function flush () {}; /** * Process a chunk. This is usually the method overridden. * @param {Object} chunk the chunk to process. */ GenericWorker.prototype.processChunk = function processChunk (chunk) { this.push(chunk); }; /** * Add a key/value to be added in the workers chain streamInfo once activated. * @param {String} key the key to use * @param {Object} value the associated value * @return {Worker} the current worker for chainability */ GenericWorker.prototype.withStreamInfo = function withStreamInfo (key, value) { this.extraStreamInfo[key] = value; this.mergeStreamInfo(); return this; }; /** * Merge this worker's streamInfo into the chain's streamInfo. */ GenericWorker.prototype.mergeStreamInfo = function mergeStreamInfo () { for(var key in this.extraStreamInfo) { if (!this.extraStreamInfo.hasOwnProperty(key)) { continue; } this.streamInfo[key] = this.extraStreamInfo[key]; } }; /** * Lock the stream to prevent further updates on the workers chain. * After calling this method, all calls to pipe will fail. */ GenericWorker.prototype.lock = function lock () { if (this.isLocked) { throw new Error("The stream '" + this + "' has already been used."); } this.isLocked = true; if (this.previous) { this.previous.lock(); } }; /** * * Pretty print the workers chain. */ GenericWorker.prototype.toString = function toString () { var me = "Worker " + this.name; if (this.previous) { return this.previous + " -> " + me; } else { return me; } }; /** * The following functions come from pako, from pako/lib/utils/strings * released under the MIT license, see pako https://github.com/nodeca/pako/ */ // Returns the utf8 lengths (calculated by first byte of sequence) // Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, // because max possible codepoint is 0x10ffff var utf8len = function(c) { var _utf8len = new Array(256); for (var i = 0; i < 256; i++) { _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); } _utf8len[254] = _utf8len[254] = 1; // Invalid sequence start // Memoize table after first call utf8len = function(c) { return _utf8len[c]; }; return _utf8len[c]; }; // convert string to array (typed, when possible) var string2buf = function (str) { var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; // count binary size for (m_pos = 0; m_pos < str_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { c2 = str.charCodeAt(m_pos+1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; } // allocate buffer if (support.uint8array) { buf = new Uint8Array(buf_len); } else { buf = new Array(buf_len); } // convert for (i=0, m_pos = 0; i < buf_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { c2 = str.charCodeAt(m_pos+1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } if (c < 0x80) { /* one byte */ buf[i++] = c; } else if (c < 0x800) { /* two bytes */ buf[i++] = 0xC0 | (c >>> 6); buf[i++] = 0x80 | (c & 0x3f); } else if (c < 0x10000) { /* three bytes */ buf[i++] = 0xE0 | (c >>> 12); buf[i++] = 0x80 | (c >>> 6 & 0x3f); buf[i++] = 0x80 | (c & 0x3f); } else { /* four bytes */ buf[i++] = 0xf0 | (c >>> 18); buf[i++] = 0x80 | (c >>> 12 & 0x3f); buf[i++] = 0x80 | (c >>> 6 & 0x3f); buf[i++] = 0x80 | (c & 0x3f); } } return buf; }; // Calculate max possible position in utf8 buffer, // that will not break sequence. If that's not possible // - (very small limits) return max size as is. // // buf[] - utf8 bytes array // max - length limit (mandatory); var utf8border = function(buf, max) { var pos; max = max || buf.length; if (max > buf.length) { max = buf.length; } // go back from last position, until start of sequence found pos = max-1; while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } // Fuckup - very small and broken sequence, // return max, because we should return something anyway. if (pos < 0) { return max; } // If we came to start of buffer - that means vuffer is too small, // return max too. if (pos === 0) { return max; } return (pos + utf8len(buf[pos]) > max) ? pos : max; }; // convert array to string var buf2string = function (buf) { var i, out, c, c_len; var len = buf.length; // Reserve max possible length (2 words per char) // NB: by unknown reasons, Array is significantly faster for // String.fromCharCode.apply than Uint16Array. var utf16buf = new Array(len*2); for (out=0, i=0; i<len;) { c = buf[i++]; // quick process ascii if (c < 0x80) { utf16buf[out++] = c; continue; } c_len = utf8len(c); // skip 5 & 6 byte codes if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } // apply mask on first byte c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; // join the rest while (c_len > 1 && i < len) { c = (c << 6) | (buf[i++] & 0x3f); c_len--; } // terminated by end of string? if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } if (c < 0x10000) { utf16buf[out++] = c; } else { c -= 0x10000; utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); utf16buf[out++] = 0xdc00 | (c & 0x3ff); } } // shrinkBuf(utf16buf, out) if (utf16buf.length !== out) { if(utf16buf.subarray) { utf16buf = utf16buf.subarray(0, out); } else { utf16buf.length = out; } } // return String.fromCharCode.apply(null, utf16buf); return applyFromCharCode(utf16buf); }; // That's all for the pako functions. /** * Transform a javascript string into an array (typed if possible) of bytes, * UTF-8 encoded. * @param {String} str the string to encode * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string. */ var utf8encode = function utf8encode(str) { return string2buf(str); }; /** * Transform a bytes array (or a representation) representing an UTF-8 encoded * string into a javascript string. * @param {Array|Uint8Array|Buffer} buf the data de decode * @return {String} the decoded string. */ var utf8decode = function utf8decode(buf) { buf = transformTo(support.uint8array ? "uint8array" : "array", buf); return buf2string(buf); }; /** * A worker to decode utf8 encoded binary chunks into string chunks. * @constructor */ var Utf8DecodeWorker = /*@__PURE__*/(function (GenericWorker) { function Utf8DecodeWorker() { GenericWorker.call(this, "utf-8 decode"); // the last bytes if a chunk didn't end with a complete codepoint. this.leftOver = null; } Utf8DecodeWorker.__proto__ = GenericWorker; Utf8DecodeWorker.prototype = Object.create( GenericWorker.prototype ); Utf8DecodeWorker.prototype.constructor = Utf8DecodeWorker; /** * @see GenericWorker.processChunk */ Utf8DecodeWorker.prototype.processChunk = function processChunk (chunk) { var data = transformTo(support.uint8array ? "uint8array" : "array", chunk.data); // 1st step, re-use what's left of the previous chunk if (this.leftOver && this.leftOver.length) { if(support.uint8array) { var previousData = data; data = new Uint8Array(previousData.length + this.leftOver.length); data.set(this.leftOver, 0); data.set(previousData, this.leftOver.length); } else { data = this.leftOver.concat(data); } this.leftOver = null; } var nextBoundary = utf8border(data); var usableData = data; if (nextBoundary !== data.length) { if (support.uint8array) { usableData = data.subarray(0, nextBoundary); this.leftOver = data.subarray(nextBoundary, data.length); } else { usableData = data.slice(0, nextBoundary); this.leftOver = data.slice(nextBoundary, data.length); } } this.push({ data : utf8decode(usableData), meta : chunk.meta }); }; /** * @see GenericWorker.flush */ Utf8DecodeWorker.prototype.flush = function flush () { if (this.leftOver && this.leftOver.length) { this.push({ data : utf8decode(this.leftOver), meta : {} }); this.leftOver = null; } }; return Utf8DecodeWorker; }(GenericWorker)); /** * A worker to endcode string chunks into utf8 encoded binary chunks. * @constructor */ var Utf8EncodeWorker = /*@__PURE__*/(function (GenericWorker) { function Utf8EncodeWorker() { GenericWorker.call(this, "utf-8 encode"); } Utf8EncodeWorker.__proto__ = GenericWorker; Utf8EncodeWorker.prototype = Object.create( GenericWorker.prototype ); Utf8EncodeWorker.prototype.constructor = Utf8EncodeWorker; /** * @see GenericWorker.processChunk */ Utf8EncodeWorker.prototype.processChunk = function processChunk (chunk) { this.push({ data: utf8encode(chunk.data), meta: chunk.meta }); }; return Utf8EncodeWorker; }(GenericWorker)); /** * A worker which convert chunks to a specified type. * @constructor * @param {String} destType the destination type. */ var ConvertWorker = /*@__PURE__*/(function (GenericWorker) { function ConvertWorker(destType) { GenericWorker.call(this, "ConvertWorker to " + destType); this.destType = destType; } ConvertWorker.__proto__ = GenericWorker; ConvertWorker.prototype = Object.create( GenericWorker.prototype ); ConvertWorker.prototype.constructor = ConvertWorker; /** * @see GenericWorker.processChunk */ ConvertWorker.prototype.processChunk = function processChunk (chunk) { this.push({ data: transformTo(this.destType, chunk.data), meta: chunk.meta }); }; return ConvertWorker; }(GenericWorker)); /** * Apply the final transformation of the data. If the user wants a Blob for * example, it's easier to work with an U8intArray and finally do the * ArrayBuffer/Blob conversion. * @param {String} type the name of the final type * @param {String|Uint8Array|Buffer} content the content to transform * @param {String} mimeType the mime type of the content, if applicable. * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format. */ function transformZipOutput(type, content, mimeType) { switch(type) { case "blob" : return newBlob(transformTo("arraybuffer", content), mimeType); case "base64" : return encode(content); default : return transformTo(type, content); } } /** * Concatenate an array of data of the given type. * @param {String} type the type of the data in the given array. * @param {Array} dataArray the array containing the data chunks to concatenate * @return {String|Uint8Array|Buffer} the concatenated data * @throws Error if the asked type is unsupported */ function concat (type, dataArray) { var i, index = 0, res = null, totalLength = 0; for(i = 0; i < dataArray.length; i++) { totalLength += dataArray[i].length; } switch(type) { case "string": return dataArray.join(""); case "array": return Array.prototype.concat.apply([], dataArray); case "uint8array": res = new Uint8Array(totalLength); for(i = 0; i < dataArray.length; i++) { res.set(dataArray[i], index); index += dataArray[i].length; } return res; default: throw new Error("concat : unsupported type '" + type + "'"); } } /** * Listen a StreamHelper, accumulate its content and concatenate it into a * complete block. * @param {StreamHelper} helper the helper to use. * @param {Function} updateCallback a callback called on each update. Called * with one arg : * - the metadata linked to the update received. * @return Promise the promise for the accumulation. */ function accumulate(helper, updateCallback) { return new external.Promise(function (resolve, reject){ var dataArray = []; var chunkType = helper._internalType, resultType = helper._outputType, mimeType = helper._mimeType; helper .on('data', function (data, meta) { dataArray.push(data); if(updateCallback) { updateCallback(meta); } }) .on('error', function(err) { dataArray = []; reject(err); }) .on('end', function (){ try { var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType); resolve(result); } catch (e) { reject(e); } dataArray = []; }) .resume(); }); } /** * An helper to easily use workers outside of JSZip. * @constructor * @param {Worker} worker the worker to wrap * @param {String} outputType the type of data expected by the use * @param {String} mimeType the mime type of the content, if applicable. */ var StreamHelper = function StreamHelper (worker, outputType, mimeType) { var internalType = outputType; switch(outputType) { case "blob": case "arraybuffer": internalType = "uint8array"; break; case "base64": internalType = "string"; break; } try { // the type used internally this._internalType = internalType; // the type used to output results this._outputType = outputType; // the mime type this._mimeType = mimeType; checkSupport(internalType); this._worker = worker.pipe(new ConvertWorker(internalType)); // the last workers can be rewired without issues but we need to // prevent any updates on previous workers. worker.lock(); } catch(e) { this._worker = new GenericWorker("error"); this._worker.error(e); } }; /** * Listen a StreamHelper, accumulate its content and concatenate it into a * complete block. * @param {Function} updateCb the update callback. * @return Promise the promise for the accumulation. */ StreamHelper.prototype.accumulate = function accumulate$1 (updateCb) { return accumulate(this, updateCb); }; /** * Add a listener on an event triggered on a stream. * @param {String} evt the name of the event * @param {Function} fn the listener * @return {StreamHelper} the current helper. */ StreamHelper.prototype.on = function on (evt, fn) { var self = this; if(evt === "data") { this._worker.on(evt, function (chunk) { fn.call(self, chunk.data, chunk.meta); }); } else { this._worker.on(evt, function () { delay(fn, arguments, self); }); } return this; }; /** * Resume the flow of chunks. * @return {StreamHelper} the current helper. */ StreamHelper.prototype.resume = function resume () { delay(this._worker.resume, [], this._worker); return this; }; /** * Pause the flow of chunks. * @return {StreamHelper} the current helper. */ StreamHelper.prototype.pause = function pause () { this._worker.pause(); return this; }; var base64 = false; var binary = false; var dir = false; var createFolders = true; var date = null; var compression = null; var compressionOptions = null; var comment = null; var unixPermissions = null; var dosPermissions = null; var defaults = /*#__PURE__*/Object.freeze({ __proto__: null, base64: base64, binary: binary, comment: comment, compression: compression, compressionOptions: compressionOptions, createFolders: createFolders, date: date, dir: dir, dosPermissions: dosPermissions, unixPermissions: unixPermissions }); // the size of the generated chunks // TODO expose this as a public variable var DEFAULT_BLOCK_SIZE = 16 * 1024; /** * A worker that reads a content and emits chunks. * @constructor * @param {Promise} dataP the promise of the data to split */ var DataWorker = /*@__PURE__*/(function (GenericWorker) { function DataWorker(dataP) { GenericWorker.call(this, "DataWorker"); var self = this; this.dataIsReady = false; this.index = 0; this.max = 0; this.data = null; this.type = ""; this._tickScheduled = false; dataP.then(function (data) { self.dataIsReady = true; self.data = data; self.max = data && data.length || 0; self.type = getTypeOf(data); if(!self.isPaused) { self._tickAndRepeat(); } }, function (e) { self.error(e); }); } DataWorker.__proto__ = GenericWorker; DataWorker.prototype = Object.create( GenericWorker.prototype ); DataWorker.prototype.constructor = DataWorker; /** * @see GenericWorker.cleanUp */ DataWorker.prototype.cleanUp = function cleanUp () { GenericWorker.prototype.cleanUp.call(this); this.data = null; }; /** * @see GenericWorker.resume */ DataWorker.prototype.resume = function resume () { if(!GenericWorker.prototype.resume.call(this)) { return false; } if (!this._tickScheduled && this.dataIsReady) { this._tickScheduled = true; delay(this._tickAndRepeat, [], this); } return true; }; /** * Trigger a tick a schedule an other call to this function. */ DataWorker.prototype._tickAndRepeat = function _tickAndRepeat () { this._tickScheduled = false; if(this.isPaused || this.isFinished) { return; } this._tick(); if(!this.isFinished) { delay(this._tickAndRepeat, [], this); this._tickScheduled = true; } }; /** * Read and push a chunk. */ DataWorker.prototype._tick = function _tick () { if(this.isPaused || this.isFinished) { return false; } var size = DEFAULT_BLOCK_SIZE; var data = null, nextIndex = Math.min(this.max, this.index + size); if (this.index >= this.max) { // EOF return this.end(); } else { switch(this.type) { case "string": data = this.data.substring(this.index, nextIndex); break; case "uint8array": data = this.data.subarray(this.index, nextIndex); break; case "array": data = this.data.slice(this.index, nextIndex); break; } this.index = nextIndex; return this.push({ data : data, meta : { percent : this.max ? this.index / this.max * 100 : 0 } }); } }; return DataWorker; }(GenericWorker)); /** * A worker which calculate the total length of the data flowing through. * @constructor * @param {String} propName the name used to expose the length */ var DataLengthProbe = /*@__PURE__*/(function (GenericWorker) { function DataLengthProbe(propName) { GenericWorker.call(this, "DataLengthProbe for " + propName); this.propName = propName; this.withStreamInfo(propName, 0); } DataLengthProbe.__proto__ = GenericWorker; DataLengthProbe.prototype = Object.create( GenericWorker.prototype ); DataLengthProbe.prototype.constructor = DataLengthProbe; /** * @see GenericWorker.processChunk */ DataLengthProbe.prototype.processChunk = function processChunk (chunk) { if (chunk) { var length = this.streamInfo[this.propName] || 0; this.streamInfo[this.propName] = length + chunk.data.length; } GenericWorker.prototype.processChunk.call(this, chunk); }; return DataLengthProbe; }(GenericWorker)); /** * The following functions come from pako, from pako/lib/zlib/crc32.js * released under the MIT license, see pako https://github.com/nodeca/pako/ */ var makeTable = function() { // Use ordinary array, since untyped makes no boost here var table = []; for (var n =0; n < 256; n++){ var c = n; for(var k =0; k < 8; k++){ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } table[n] = c; } // Memoize table on first call. makeTable = function() { return table; }; return table; }; function crc32(crc, buf, len, pos) { var t = makeTable(); var end = pos + len; crc = crc ^ (-1); for (var i = pos; i < end; i++ ) { crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; } return (crc ^ (-1)); // >>> 0; } // That's all for the pako functions. /** * Compute the crc32 of a string. * This is almost the same as the function crc32, but for strings. Using the * same function for the two use cases leads to horrible performances. * @param {Number} crc the starting value of the crc. * @param {String} str the string to use. * @param {Number} len the length of the string. * @param {Number} pos the starting position for the crc32 computation. * @return {Number} the computed crc32. */ function crc32str(crc, str, len, pos) { var t = makeTable(); var end = pos + len; crc = crc ^ (-1); for (var i = pos; i < end; i++ ) { crc = (crc >>> 8) ^ t[(crc ^ str.charCodeAt(i)) & 0xFF]; } return (crc ^ (-1)); // >>> 0; } function crc32wrapper(input, crc) { if (typeof input === "undefined" || !input.length) { return 0; } var isArray = getTypeOf(input) !== "string"; if (isArray) { return crc32(crc | 0, input, input.length, 0); } else { return crc32str(crc | 0, input, input.length, 0); } } /** * A worker which calculate the crc32 of the data flowing through. * @constructor */ var Crc32Probe = /*@__PURE__*/(function (GenericWorker) { function Crc32Probe() { GenericWorker.call(this, "Crc32Probe"); this.withStreamInfo("crc32", 0); } Crc32Probe.__proto__ = GenericWorker; Crc32Probe.prototype = Object.create( GenericWorker.prototype ); Crc32Probe.prototype.constructor = Crc32Probe; /** * @see GenericWorker.processChunk */ Crc32Probe.prototype.processChunk = function processChunk (chunk) { this.streamInfo.crc32 = crc32wrapper(chunk.data, this.streamInfo.crc32 || 0); this.push(chunk); }; return Crc32Probe; }(GenericWorker)); /** * Represent a compressed object, with everything needed to decompress it. * @constructor * @param {number} compressedSize the size of the data compressed. * @param {number} uncompressedSize the size of the data after decompression. * @param {number} crc32 the crc32 of the decompressed file. * @param {object} compression the type of compression, see lib/compressions.js. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the compressed data. */ var CompressedObject = function CompressedObject(compressedSize, uncompressedSize, crc32, compression, data) { this.compressedSize = compressedSize; this.uncompressedSize = uncompressedSize; this.crc32 = crc32; this.compression = compression; this.compressedContent = data; }; /** * Create a worker to get the uncompressed content. * @return {GenericWorker} the worker. */ CompressedObject.prototype.getContentWorker = function getContentWorker () { var worker = new DataWorker(external.Promise.resolve(this.compressedContent)) .pipe(this.compression.uncompressWorker()) .pipe(new DataLengthProbe("data_length")); var that = this; worker.on("end", function () { if(this.streamInfo['data_length'] !== that.uncompressedSize) { throw new Error("Bug : uncompressed data size mismatch"); } }); return worker; }; /** * Create a worker to get the compressed content. * @return {GenericWorker} the worker. */ CompressedObject.prototype.getCompressedWorker = function getCompressedWorker () { return new DataWorker(external.Promise.resolve(this.compressedContent)) .withStreamInfo("compressedSize", this.compressedSize) .withStreamInfo("uncompressedSize", this.uncompressedSize) .withStreamInfo("crc32", this.crc32) .withStreamInfo("compression", this.compression) ; }; /** * Chain the given worker with other workers to compress the content with the * given compress