3 JSZip - A Javascript class for generating and reading zip files
4 <http://stuartk.com/jszip>
6 (c) 2009-2012 Stuart Knightley <stuart [at] stuartk.com>
7 Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
11 zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing");
12 zip.folder("images").file("smile.gif", base64Data, {base64: true});
13 zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")});
14 zip.remove("tempfile");
16 base64zip = zip.generate();
22 * Representation a of zip file in js
24 * @param {String=|ArrayBuffer=|Uint8Array=|Buffer=} data the data to load, if any (optional).
25 * @param {Object=} options the options for creating this objects (optional).
27 var JSZip = function(data, options) {
28 // object containing the files :
31 // "folder/data.txt" : {...}
35 // Where we are in the hierarchy
39 this.load(data, options);
44 LOCAL_FILE_HEADER : "\x50\x4b\x03\x04",
45 CENTRAL_FILE_HEADER : "\x50\x4b\x01\x02",
46 CENTRAL_DIRECTORY_END : "\x50\x4b\x05\x06",
47 ZIP64_CENTRAL_DIRECTORY_LOCATOR : "\x50\x4b\x06\x07",
48 ZIP64_CENTRAL_DIRECTORY_END : "\x50\x4b\x06\x06",
49 DATA_DESCRIPTOR : "\x50\x4b\x07\x08"
52 // Default properties for a new file
62 * List features that require a modern browser, and if the current browser support them.
65 // contains true if JSZip can read/generate ArrayBuffer, false otherwise.
66 arraybuffer : (function(){
67 return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
69 // contains true if JSZip can read/generate nodejs Buffer, false otherwise.
70 nodebuffer : (function(){
71 return typeof Buffer !== "undefined";
73 // contains true if JSZip can read/generate Uint8Array, false otherwise.
74 uint8array : (function(){
75 return typeof Uint8Array !== "undefined";
77 // contains true if JSZip can read/generate Blob, false otherwise.
79 // the spec started with BlobBuilder then replaced it with a construtor for Blob.
80 // Result : we have browsers that :
81 // * know the BlobBuilder (but with prefix)
82 // * know the Blob constructor
83 // * know about Blob but not about how to build them
84 // About the "=== 0" test : if given the wrong type, it may be converted to a string.
85 // Instead of an empty content, we will get "[object Uint8Array]" for example.
86 if (typeof ArrayBuffer === "undefined") {
89 var buffer = new ArrayBuffer(0);
91 return new Blob([buffer], { type: "application/zip" }).size === 0;
96 var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
97 window.MozBlobBuilder || window.MSBlobBuilder)();
98 builder.append(buffer);
99 return builder.getBlob('application/zip').size === 0;
107 JSZip.prototype = (function () {
108 var textEncoder, textDecoder;
110 JSZip.support.uint8array &&
111 typeof TextEncoder === "function" &&
112 typeof TextDecoder === "function"
114 textEncoder = new TextEncoder("utf-8");
115 textDecoder = new TextDecoder("utf-8");
119 * Returns the raw data of a ZipObject, decompress the content if necessary.
120 * @param {ZipObject} file the file to use.
121 * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
123 var getRawData = function (file) {
124 if (file._data instanceof JSZip.CompressedObject) {
125 file._data = file._data.getContent();
126 file.options.binary = true;
127 file.options.base64 = false;
129 if (JSZip.utils.getTypeOf(file._data) === "uint8array") {
130 var copy = file._data;
131 // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
132 // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
133 file._data = new Uint8Array(copy.length);
134 // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
135 if (copy.length !== 0) {
136 file._data.set(copy, 0);
144 * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
145 * @param {ZipObject} file the file to use.
146 * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
148 var getBinaryData = function (file) {
149 var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
150 if (type === "string") {
151 if (!file.options.binary) {
153 // unicode string => binary string is a painful process, check if we can avoid it.
155 return textEncoder.encode(result);
157 if (JSZip.support.nodebuffer) {
158 return new Buffer(result, "utf-8");
161 return file.asBinary();
167 * Transform this._data into a string.
168 * @param {function} filter a function String -> String, applied if not null on the result.
169 * @return {String} the string representing this._data.
171 var dataToString = function (asUTF8) {
172 var result = getRawData(this);
173 if (result === null || typeof result === "undefined") {
176 // if the data is a base64 string, we decode it before checking the encoding !
177 if (this.options.base64) {
178 result = JSZip.base64.decode(result);
180 if (asUTF8 && this.options.binary) {
181 // JSZip.prototype.utf8decode supports arrays as input
182 // skip to array => string step, utf8decode will do it.
183 result = JSZip.prototype.utf8decode(result);
185 // no utf8 transformation, do the array => string step.
186 result = JSZip.utils.transformTo("string", result);
189 if (!asUTF8 && !this.options.binary) {
190 result = JSZip.prototype.utf8encode(result);
195 * A simple object representing a file in the zip file.
197 * @param {string} name the name of the file
198 * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
199 * @param {Object} options the options of the file
201 var ZipObject = function (name, data, options) {
204 this.options = options;
207 ZipObject.prototype = {
209 * Return the content as UTF8 string.
210 * @return {string} the UTF8 string.
212 asText : function () {
213 return dataToString.call(this, true);
216 * Returns the binary content.
217 * @return {string} the content as binary.
219 asBinary : function () {
220 return dataToString.call(this, false);
223 * Returns the content as a nodejs Buffer.
224 * @return {Buffer} the content as a Buffer.
226 asNodeBuffer : function () {
227 var result = getBinaryData(this);
228 return JSZip.utils.transformTo("nodebuffer", result);
231 * Returns the content as an Uint8Array.
232 * @return {Uint8Array} the content as an Uint8Array.
234 asUint8Array : function () {
235 var result = getBinaryData(this);
236 return JSZip.utils.transformTo("uint8array", result);
239 * Returns the content as an ArrayBuffer.
240 * @return {ArrayBuffer} the content as an ArrayBufer.
242 asArrayBuffer : function () {
243 return this.asUint8Array().buffer;
248 * Transform an integer into a string in hexadecimal.
250 * @param {number} dec the number to convert.
251 * @param {number} bytes the number of bytes to generate.
252 * @returns {string} the result.
254 var decToHex = function(dec, bytes) {
256 for(i = 0; i < bytes; i++) {
257 hex += String.fromCharCode(dec&0xff);
264 * Merge the objects passed as parameters into a new one.
266 * @param {...Object} var_args All objects to merge.
267 * @return {Object} a new object with the data of the others.
269 var extend = function () {
270 var result = {}, i, attr;
271 for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
272 for (attr in arguments[i]) {
273 if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
274 result[attr] = arguments[i][attr];
282 * Transforms the (incomplete) options from the user into the complete
283 * set of options to create a file.
285 * @param {Object} o the options from the user.
286 * @return {Object} the complete set of options.
288 var prepareFileAttrs = function (o) {
290 if (o.base64 === true && o.binary == null) {
293 o = extend(o, JSZip.defaults);
294 o.date = o.date || new Date();
295 if (o.compression !== null) o.compression = o.compression.toUpperCase();
301 * Add a file in the current folder.
303 * @param {string} name the name of the file
304 * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
305 * @param {Object} o the options of the file
306 * @return {Object} the new file.
308 var fileAdd = function (name, data, o) {
309 // be sure sub folders exist
310 var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
312 folderAdd.call(this, parent);
315 o = prepareFileAttrs(o);
317 if (o.dir || data === null || typeof data === "undefined") {
321 } else if (dataType === "string") {
322 if (o.binary && !o.base64) {
323 // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
324 if (o.optimizedBinaryString !== true) {
325 // this is a string, not in a base64 format.
326 // Be sure that this is a correct "binary string"
327 data = JSZip.utils.string2binary(data);
330 } else { // arraybuffer, uint8array, ...
334 if (!dataType && !(data instanceof JSZip.CompressedObject)) {
335 throw new Error("The data of '" + name + "' is in an unsupported format !");
338 // special case : it's way easier to work with Uint8Array than with ArrayBuffer
339 if (dataType === "arraybuffer") {
340 data = JSZip.utils.transformTo("uint8array", data);
344 return this.files[name] = new ZipObject(name, data, o);
349 * Find the parent folder of the path.
351 * @param {string} path the path to use
352 * @return {string} the parent folder, or ""
354 var parentFolder = function (path) {
355 if (path.slice(-1) == '/') {
356 path = path.substring(0, path.length - 1);
358 var lastSlash = path.lastIndexOf('/');
359 return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
363 * Add a (sub) folder in the current folder.
365 * @param {string} name the folder's name
366 * @return {Object} the new folder.
368 var folderAdd = function (name) {
369 // Check the name ends with a /
370 if (name.slice(-1) != "/") {
371 name += "/"; // IE doesn't like substr(-1)
374 // Does this folder already exist?
375 if (!this.files[name]) {
376 fileAdd.call(this, name, null, {dir:true});
378 return this.files[name];
382 * Generate a JSZip.CompressedObject for a given zipOject.
383 * @param {ZipObject} file the object to read.
384 * @param {JSZip.compression} compression the compression to use.
385 * @return {JSZip.CompressedObject} the compressed result.
387 var generateCompressedObjectFrom = function (file, compression) {
388 var result = new JSZip.CompressedObject(), content;
390 // the data has not been decompressed, we might reuse things !
391 if (file._data instanceof JSZip.CompressedObject) {
392 result.uncompressedSize = file._data.uncompressedSize;
393 result.crc32 = file._data.crc32;
395 if (result.uncompressedSize === 0 || file.options.dir) {
396 compression = JSZip.compressions['STORE'];
397 result.compressedContent = "";
399 } else if (file._data.compressionMethod === compression.magic) {
400 result.compressedContent = file._data.getCompressedContent();
402 content = file._data.getContent()
403 // need to decompress / recompress
404 result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
407 // have uncompressed data
408 content = getBinaryData(file);
409 if (!content || content.length === 0 || file.options.dir) {
410 compression = JSZip.compressions['STORE'];
413 result.uncompressedSize = content.length;
414 result.crc32 = this.crc32(content);
415 result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
418 result.compressedSize = result.compressedContent.length;
419 result.compressionMethod = compression.magic;
425 * Generate the various parts used in the construction of the final zip file.
426 * @param {string} name the file name.
427 * @param {ZipObject} file the file content.
428 * @param {JSZip.CompressedObject} compressedObject the compressed object.
429 * @param {number} offset the current offset from the start of the zip file.
430 * @return {object} the zip parts.
432 var generateZipParts = function(name, file, compressedObject, offset) {
433 var data = compressedObject.compressedContent,
434 utfEncodedFileName = this.utf8encode(file.name),
435 useUTF8 = utfEncodedFileName !== file.name,
441 // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
442 // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
443 // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
445 dosTime = o.date.getHours();
446 dosTime = dosTime << 6;
447 dosTime = dosTime | o.date.getMinutes();
448 dosTime = dosTime << 5;
449 dosTime = dosTime | o.date.getSeconds() / 2;
451 dosDate = o.date.getFullYear() - 1980;
452 dosDate = dosDate << 4;
453 dosDate = dosDate | (o.date.getMonth() + 1);
454 dosDate = dosDate << 5;
455 dosDate = dosDate | o.date.getDate();
460 // version needed to extract
461 header += "\x0A\x00";
462 // general purpose bit flag
463 // set bit 11 if utf8
464 header += useUTF8 ? "\x00\x08" : "\x00\x00";
465 // compression method
466 header += compressedObject.compressionMethod;
467 // last mod file time
468 header += decToHex(dosTime, 2);
469 // last mod file date
470 header += decToHex(dosDate, 2);
472 header += decToHex(compressedObject.crc32, 4);
474 header += decToHex(compressedObject.compressedSize, 4);
476 header += decToHex(compressedObject.uncompressedSize, 4);
478 header += decToHex(utfEncodedFileName.length, 2);
479 // extra field length
480 header += "\x00\x00";
483 var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
485 var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
486 // version made by (00: DOS)
488 // file header (common to file and central directory)
490 // file comment length
494 // internal file attributes TODO
496 // external file attributes
497 (file.options.dir===true?"\x10\x00\x00\x00":"\x00\x00\x00\x00")+
498 // relative offset of local header
499 decToHex(offset, 4) +
505 fileRecord : fileRecord,
506 dirRecord : dirRecord,
507 compressedObject : compressedObject
512 * An object to write any content to a string.
515 var StringWriter = function () {
518 StringWriter.prototype = {
520 * Append any content to the current string.
521 * @param {Object} input the content to add.
523 append : function (input) {
524 input = JSZip.utils.transformTo("string", input);
525 this.data.push(input);
528 * Finalize the construction an return the result.
529 * @return {string} the generated string.
531 finalize : function () {
532 return this.data.join("");
536 * An object to write any content to an Uint8Array.
538 * @param {number} length The length of the array.
540 var Uint8ArrayWriter = function (length) {
541 this.data = new Uint8Array(length);
544 Uint8ArrayWriter.prototype = {
546 * Append any content to the current array.
547 * @param {Object} input the content to add.
549 append : function (input) {
550 if (input.length !== 0) {
551 // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
552 input = JSZip.utils.transformTo("uint8array", input);
553 this.data.set(input, this.index);
554 this.index += input.length;
558 * Finalize the construction an return the result.
559 * @return {Uint8Array} the generated array.
561 finalize : function () {
566 // return the actual prototype of JSZip
569 * Read an existing zip and merge the data in the current JSZip object.
570 * The implementation is in jszip-load.js, don't forget to include it.
571 * @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
572 * @param {Object} options Options for loading the stream.
573 * options.base64 : is the stream in base64 ? default : false
574 * @return {JSZip} the current JSZip object
576 load : function (stream, options) {
577 throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
581 * Filter nested files/folders with the specified function.
582 * @param {Function} search the predicate to use :
583 * function (relativePath, file) {...}
584 * It takes 2 arguments : the relative path and the file.
585 * @return {Array} An array of matching elements.
587 filter : function (search) {
588 var result = [], filename, relativePath, file, fileClone;
589 for (filename in this.files) {
590 if ( !this.files.hasOwnProperty(filename) ) { continue; }
591 file = this.files[filename];
592 // return a new object, don't let the user mess with our internal objects :)
593 fileClone = new ZipObject(file.name, file._data, extend(file.options));
594 relativePath = filename.slice(this.root.length, filename.length);
595 if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
596 search(relativePath, fileClone)) { // and the file matches the function
597 result.push(fileClone);
604 * Add a file to the zip file, or search a file.
605 * @param {string|RegExp} name The name of the file to add (if data is defined),
606 * the name of the file to find (if no data) or a regex to match files.
607 * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
608 * @param {Object} o File options
609 * @return {JSZip|Object|Array} this JSZip object (when adding a file),
610 * a file (when searching by string) or an array of files (when searching by regex).
612 file : function(name, data, o) {
613 if (arguments.length === 1) {
614 if (JSZip.utils.isRegExp(name)) {
616 return this.filter(function(relativePath, file) {
617 return !file.options.dir && regexp.test(relativePath);
620 return this.filter(function (relativePath, file) {
621 return !file.options.dir && relativePath === name;
624 } else { // more than one argument : we have data !
625 name = this.root+name;
626 fileAdd.call(this, name, data, o);
632 * Add a directory to the zip file, or search.
633 * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
634 * @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
636 folder : function(arg) {
641 if (JSZip.utils.isRegExp(arg)) {
642 return this.filter(function(relativePath, file) {
643 return file.options.dir && arg.test(relativePath);
647 // else, name is a new folder
648 var name = this.root + arg;
649 var newFolder = folderAdd.call(this, name);
651 // Allow chaining by returning a new object with this folder as the root
652 var ret = this.clone();
653 ret.root = newFolder.name;
658 * Delete a file, or a directory and all sub-files, from the zip
659 * @param {string} name the name of the file to delete
660 * @return {JSZip} this JSZip object
662 remove : function(name) {
663 name = this.root + name;
664 var file = this.files[name];
666 // Look for any folders
667 if (name.slice(-1) != "/") {
670 file = this.files[name];
674 if (!file.options.dir) {
676 delete this.files[name];
679 var kids = this.filter(function (relativePath, file) {
680 return file.name.slice(0, name.length) === name;
682 for (var i = 0; i < kids.length; i++) {
683 delete this.files[kids[i].name];
692 * Generate the complete zip file
693 * @param {Object} options the options to generate the zip file :
694 * - base64, (deprecated, use type instead) true to generate base64.
695 * - compression, "STORE" by default.
696 * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
697 * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
699 generate : function(options) {
700 options = extend(options || {}, {
702 compression : "STORE",
706 JSZip.utils.checkSupport(options.type);
708 var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
711 // first, generate all the zip parts.
712 for (var name in this.files) {
713 if ( !this.files.hasOwnProperty(name) ) { continue; }
714 var file = this.files[name];
716 var compressionName = file.options.compression || options.compression.toUpperCase();
717 var compression = JSZip.compressions[compressionName];
719 throw new Error(compressionName + " is not a valid compression method !");
722 var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
724 var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
725 localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
726 centralDirLength += zipPart.dirRecord.length;
727 zipData.push(zipPart);
732 // end of central dir signature
733 dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
734 // number of this disk
736 // number of the disk with the start of the central directory
738 // total number of entries in the central directory on this disk
739 decToHex(zipData.length, 2) +
740 // total number of entries in the central directory
741 decToHex(zipData.length, 2) +
742 // size of the central directory 4 bytes
743 decToHex(centralDirLength, 4) +
744 // offset of start of central directory with respect to the starting disk number
745 decToHex(localDirLength, 4) +
746 // .ZIP file comment length
750 // we have all the parts (and the total length)
751 // time to create a writer !
752 switch(options.type.toLowerCase()) {
757 writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
760 default : // case "string" :
761 writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
765 for (i = 0; i < zipData.length; i++) {
766 writer.append(zipData[i].fileRecord);
767 writer.append(zipData[i].compressedObject.compressedContent);
769 for (i = 0; i < zipData.length; i++) {
770 writer.append(zipData[i].dirRecord);
773 writer.append(dirEnd);
775 var zip = writer.finalize();
779 switch(options.type.toLowerCase()) {
780 // case "zip is an Uint8Array"
784 return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
786 return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
788 // case "zip is a string"
790 return (options.base64) ? JSZip.base64.encode(zip) : zip;
791 default : // case "string" :
799 * http://www.webtoolkit.info/
802 crc32 : function crc32(input, crc) {
803 if (typeof input === "undefined" || !input.length) {
807 var isArray = JSZip.utils.getTypeOf(input) !== "string";
810 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
811 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
812 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
813 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
814 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
815 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
816 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
817 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
818 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
819 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
820 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
821 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
822 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
823 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
824 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
825 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
826 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
827 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
828 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
829 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
830 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
831 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
832 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
833 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
834 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
835 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
836 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
837 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
838 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
839 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
840 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
841 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
842 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
843 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
844 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
845 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
846 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
847 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
848 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
849 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
850 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
851 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
852 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
853 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
854 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
855 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
856 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
857 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
858 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
859 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
860 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
861 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
862 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
863 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
864 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
865 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
866 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
867 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
868 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
869 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
870 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
871 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
872 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
873 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
876 if (typeof(crc) == "undefined") { crc = 0; }
882 for( var i = 0, iTop = input.length; i < iTop; i++ ) {
883 byte = isArray ? input[i] : input.charCodeAt(i);
884 y = ( crc ^ byte ) & 0xFF;
886 crc = ( crc >>> 8 ) ^ x;
892 // Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
894 var newObj = new JSZip();
895 for (var i in this) {
896 if (typeof this[i] !== "function") {
905 * http://www.webtoolkit.info/javascript-utf8.html
907 utf8encode : function (string) {
908 // TextEncoder + Uint8Array to binary string is faster than checking every bytes on long strings.
909 // http://jsperf.com/utf8encode-vs-textencoder
910 // On short strings (file names for example), the TextEncoder API is (currently) slower.
912 var u8 = textEncoder.encode(string);
913 return JSZip.utils.transformTo("string", u8);
915 if (JSZip.support.nodebuffer) {
916 return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
919 // array.join may be slower than string concatenation but generates less objects (less time spent garbage collecting).
920 // See also http://jsperf.com/array-direct-assignment-vs-push/31
921 var result = [], resIndex = 0;
923 for (var n = 0; n < string.length; n++) {
925 var c = string.charCodeAt(n);
928 result[resIndex++] = String.fromCharCode(c);
929 } else if ((c > 127) && (c < 2048)) {
930 result[resIndex++] = String.fromCharCode((c >> 6) | 192);
931 result[resIndex++] = String.fromCharCode((c & 63) | 128);
933 result[resIndex++] = String.fromCharCode((c >> 12) | 224);
934 result[resIndex++] = String.fromCharCode(((c >> 6) & 63) | 128);
935 result[resIndex++] = String.fromCharCode((c & 63) | 128);
940 return result.join("");
944 * http://www.webtoolkit.info/javascript-utf8.html
946 utf8decode : function (input) {
947 var result = [], resIndex = 0;
948 var type = JSZip.utils.getTypeOf(input);
949 var isArray = type !== "string";
951 var c = 0, c1 = 0, c2 = 0, c3 = 0;
953 // check if we can use the TextDecoder API
954 // see http://encoding.spec.whatwg.org/#api
956 return textDecoder.decode(
957 JSZip.utils.transformTo("uint8array", input)
960 if (JSZip.support.nodebuffer) {
961 return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
964 while ( i < input.length ) {
966 c = isArray ? input[i] : input.charCodeAt(i);
969 result[resIndex++] = String.fromCharCode(c);
971 } else if ((c > 191) && (c < 224)) {
972 c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
973 result[resIndex++] = String.fromCharCode(((c & 31) << 6) | (c2 & 63));
976 c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
977 c3 = isArray ? input[i+2] : input.charCodeAt(i+2);
978 result[resIndex++] = String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
984 return result.join("");
990 * Compression methods
991 * This object is filled in as follow :
993 * magic // the 2 bytes indentifying the compression method
994 * compress // function, take the uncompressed content and return it compressed.
995 * uncompress // function, take the compressed content and return it uncompressed.
996 * compressInputType // string, the type accepted by the compress method. null to accept everything.
997 * uncompressInputType // string, the type accepted by the uncompress method. null to accept everything.
1000 * STORE is the default compression method, so it's included in this file.
1001 * Other methods should go to separated files : the user wants modularity.
1003 JSZip.compressions = {
1006 compress : function (content) {
1007 return content; // no compression
1009 uncompress : function (content) {
1010 return content; // no compression
1012 compressInputType : null,
1013 uncompressInputType : null
1020 * Convert a string to a "binary string" : a string containing only char codes between 0 and 255.
1021 * @param {string} str the string to transform.
1022 * @return {String} the binary string.
1024 string2binary : function (str) {
1026 for (var i = 0; i < str.length; i++) {
1027 result += String.fromCharCode(str.charCodeAt(i) & 0xff);
1032 * Create a Uint8Array from the string.
1033 * @param {string} str the string to transform.
1034 * @return {Uint8Array} the typed array.
1035 * @throws {Error} an Error if the browser doesn't support the requested feature.
1036 * @deprecated : use JSZip.utils.transformTo instead.
1038 string2Uint8Array : function (str) {
1039 return JSZip.utils.transformTo("uint8array", str);
1043 * Create a string from the Uint8Array.
1044 * @param {Uint8Array} array the array to transform.
1045 * @return {string} the string.
1046 * @throws {Error} an Error if the browser doesn't support the requested feature.
1047 * @deprecated : use JSZip.utils.transformTo instead.
1049 uint8Array2String : function (array) {
1050 return JSZip.utils.transformTo("string", array);
1053 * Create a blob from the given ArrayBuffer.
1054 * @param {ArrayBuffer} buffer the buffer to transform.
1055 * @return {Blob} the result.
1056 * @throws {Error} an Error if the browser doesn't support the requested feature.
1058 arrayBuffer2Blob : function (buffer) {
1059 JSZip.utils.checkSupport("blob");
1063 return new Blob([buffer], { type: "application/zip" });
1068 // deprecated, browser only, old way
1069 var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
1070 window.MozBlobBuilder || window.MSBlobBuilder)();
1071 builder.append(buffer);
1072 return builder.getBlob('application/zip');
1077 throw new Error("Bug : can't construct the Blob.");
1080 * Create a blob from the given string.
1081 * @param {string} str the string to transform.
1082 * @return {Blob} the result.
1083 * @throws {Error} an Error if the browser doesn't support the requested feature.
1085 string2Blob : function (str) {
1086 var buffer = JSZip.utils.transformTo("arraybuffer", str);
1087 return JSZip.utils.arrayBuffer2Blob(buffer);
1092 * The identity function.
1093 * @param {Object} input the input.
1094 * @return {Object} the same input.
1096 function identity(input) {
1101 * Fill in an array with a string.
1102 * @param {String} str the string to use.
1103 * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
1104 * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
1106 function stringToArrayLike(str, array) {
1107 for (var i = 0; i < str.length; ++i) {
1108 array[i] = str.charCodeAt(i) & 0xFF;
1114 * Transform an array-like object to a string.
1115 * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
1116 * @return {String} the result.
1118 function arrayLikeToString(array) {
1119 // Performances notes :
1120 // --------------------
1121 // String.fromCharCode.apply(null, array) is the fastest, see
1122 // see http://jsperf.com/converting-a-uint8array-to-a-string/2
1123 // but the stack is limited (and we can get huge arrays !).
1125 // result += String.fromCharCode(array[i]); generate too many strings !
1127 // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
1129 var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
1131 var canUseApply = true;
1135 String.fromCharCode.apply(null, new Uint8Array(0));
1138 String.fromCharCode.apply(null, new Buffer(0));
1142 canUseApply = false;
1145 // no apply : slow and painful algorithm
1146 // default browser on android 4.*
1149 for(var i = 0; i < array.length;i++) {
1150 resultStr += String.fromCharCode(array[i]);
1155 while (k < len && chunk > 1) {
1157 if (type === "array" || type === "nodebuffer") {
1158 result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
1160 result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
1164 chunk = Math.floor(chunk / 2);
1167 return result.join("");
1171 * Copy the data from an array-like to an other array-like.
1172 * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
1173 * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
1174 * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
1176 function arrayLikeToArrayLike(arrayFrom, arrayTo) {
1177 for(var i = 0; i < arrayFrom.length; i++) {
1178 arrayTo[i] = arrayFrom[i];
1183 // a matrix containing functions to transform everything into everything.
1187 transform["string"] = {
1188 "string" : identity,
1189 "array" : function (input) {
1190 return stringToArrayLike(input, new Array(input.length));
1192 "arraybuffer" : function (input) {
1193 return transform["string"]["uint8array"](input).buffer;
1195 "uint8array" : function (input) {
1196 return stringToArrayLike(input, new Uint8Array(input.length));
1198 "nodebuffer" : function (input) {
1199 return stringToArrayLike(input, new Buffer(input.length));
1204 transform["array"] = {
1205 "string" : arrayLikeToString,
1207 "arraybuffer" : function (input) {
1208 return (new Uint8Array(input)).buffer;
1210 "uint8array" : function (input) {
1211 return new Uint8Array(input);
1213 "nodebuffer" : function (input) {
1214 return new Buffer(input);
1219 transform["arraybuffer"] = {
1220 "string" : function (input) {
1221 return arrayLikeToString(new Uint8Array(input));
1223 "array" : function (input) {
1224 return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
1226 "arraybuffer" : identity,
1227 "uint8array" : function (input) {
1228 return new Uint8Array(input);
1230 "nodebuffer" : function (input) {
1231 return new Buffer(new Uint8Array(input));
1236 transform["uint8array"] = {
1237 "string" : arrayLikeToString,
1238 "array" : function (input) {
1239 return arrayLikeToArrayLike(input, new Array(input.length));
1241 "arraybuffer" : function (input) {
1242 return input.buffer;
1244 "uint8array" : identity,
1245 "nodebuffer" : function(input) {
1246 return new Buffer(input);
1251 transform["nodebuffer"] = {
1252 "string" : arrayLikeToString,
1253 "array" : function (input) {
1254 return arrayLikeToArrayLike(input, new Array(input.length));
1256 "arraybuffer" : function (input) {
1257 return transform["nodebuffer"]["uint8array"](input).buffer;
1259 "uint8array" : function (input) {
1260 return arrayLikeToArrayLike(input, new Uint8Array(input.length));
1262 "nodebuffer" : identity
1266 * Transform an input into any type.
1267 * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
1268 * If no output type is specified, the unmodified input will be returned.
1269 * @param {String} outputType the output type.
1270 * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
1271 * @throws {Error} an Error if the browser doesn't support the requested output type.
1273 JSZip.utils.transformTo = function (outputType, input) {
1275 // undefined, null, etc
1276 // an empty string won't harm.
1282 JSZip.utils.checkSupport(outputType);
1283 var inputType = JSZip.utils.getTypeOf(input);
1284 var result = transform[inputType][outputType](input);
1289 * Return the type of the input.
1290 * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
1291 * @param {Object} input the input to identify.
1292 * @return {String} the (lowercase) type of the input.
1294 JSZip.utils.getTypeOf = function (input) {
1295 if (typeof input === "string") {
1298 if (Object.prototype.toString.call(input) === "[object Array]") {
1301 if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
1302 return "nodebuffer";
1304 if (JSZip.support.uint8array && input instanceof Uint8Array) {
1305 return "uint8array";
1307 if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
1308 return "arraybuffer";
1313 * Cross-window, cross-Node-context regular expression detection
1314 * @param {Object} object Anything
1315 * @return {Boolean} true if the object is a regular expression,
1318 JSZip.utils.isRegExp = function (object) {
1319 return Object.prototype.toString.call(object) === "[object RegExp]";
1323 * Throw an exception if the type is not supported.
1324 * @param {String} type the type to check.
1325 * @throws {Error} an Error if the browser doesn't support the requested type.
1327 JSZip.utils.checkSupport = function (type) {
1328 var supported = true;
1329 switch (type.toLowerCase()) {
1331 supported = JSZip.support.uint8array;
1334 supported = JSZip.support.arraybuffer;
1337 supported = JSZip.support.nodebuffer;
1340 supported = JSZip.support.blob;
1344 throw new Error(type + " is not supported by this browser");
1353 * Represents an entry in the zip.
1354 * The content may or may not be compressed.
1357 JSZip.CompressedObject = function () {
1358 this.compressedSize = 0;
1359 this.uncompressedSize = 0;
1361 this.compressionMethod = null;
1362 this.compressedContent = null;
1365 JSZip.CompressedObject.prototype = {
1367 * Return the decompressed content in an unspecified format.
1368 * The format will depend on the decompressor.
1369 * @return {Object} the decompressed content.
1371 getContent : function () {
1372 return null; // see implementation
1375 * Return the compressed content in an unspecified format.
1376 * The format will depend on the compressed conten source.
1377 * @return {Object} the compressed content.
1379 getCompressedContent : function () {
1380 return null; // see implementation
1387 * Base64 encode / decode
1388 * http://www.webtoolkit.info/
1390 * Hacked so that it doesn't utf8 en/decode everything
1392 JSZip.base64 = (function() {
1394 var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1397 // public method for encoding
1398 encode : function(input, utf8) {
1400 var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
1403 while (i < input.length) {
1405 chr1 = input.charCodeAt(i++);
1406 chr2 = input.charCodeAt(i++);
1407 chr3 = input.charCodeAt(i++);
1410 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
1411 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
1416 } else if (isNaN(chr3)) {
1421 _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
1422 _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
1429 // public method for decoding
1430 decode : function(input, utf8) {
1432 var chr1, chr2, chr3;
1433 var enc1, enc2, enc3, enc4;
1436 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
1438 while (i < input.length) {
1440 enc1 = _keyStr.indexOf(input.charAt(i++));
1441 enc2 = _keyStr.indexOf(input.charAt(i++));
1442 enc3 = _keyStr.indexOf(input.charAt(i++));
1443 enc4 = _keyStr.indexOf(input.charAt(i++));
1445 chr1 = (enc1 << 2) | (enc2 >> 4);
1446 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
1447 chr3 = ((enc3 & 3) << 6) | enc4;
1449 output = output + String.fromCharCode(chr1);
1452 output = output + String.fromCharCode(chr2);
1455 output = output + String.fromCharCode(chr3);
1466 // enforcing Stuk's coding style
1467 // vim: set shiftwidth=3 softtabstop=3: