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 JSZip.prototype = (function () {
65 * Returns the raw data of a ZipObject, decompress the content if necessary.
66 * @param {ZipObject} file the file to use.
67 * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
69 var getRawData = function (file) {
70 if (file._data instanceof JSZip.CompressedObject) {
71 file._data = file._data.getContent();
72 file.options.binary = true;
73 file.options.base64 = false;
75 if (JSZip.utils.getTypeOf(file._data) === "uint8array") {
76 var copy = file._data;
77 // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
78 // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
79 file._data = new Uint8Array(copy.length);
80 // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
81 if (copy.length !== 0) {
82 file._data.set(copy, 0);
90 * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
91 * @param {ZipObject} file the file to use.
92 * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
94 var getBinaryData = function (file) {
95 var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
96 if (type === "string") {
97 if (!file.options.binary) {
99 // unicode string => binary string is a painful process, check if we can avoid it.
100 if (JSZip.support.uint8array && typeof TextEncoder === "function") {
101 return TextEncoder("utf-8").encode(result);
103 if (JSZip.support.nodebuffer) {
104 return new Buffer(result, "utf-8");
107 return file.asBinary();
113 * Transform this._data into a string.
114 * @param {function} filter a function String -> String, applied if not null on the result.
115 * @return {String} the string representing this._data.
117 var dataToString = function (asUTF8) {
118 var result = getRawData(this);
119 if (result === null || typeof result === "undefined") {
122 // if the data is a base64 string, we decode it before checking the encoding !
123 if (this.options.base64) {
124 result = JSZip.base64.decode(result);
126 if (asUTF8 && this.options.binary) {
127 // JSZip.prototype.utf8decode supports arrays as input
128 // skip to array => string step, utf8decode will do it.
129 result = JSZip.prototype.utf8decode(result);
131 // no utf8 transformation, do the array => string step.
132 result = JSZip.utils.transformTo("string", result);
135 if (!asUTF8 && !this.options.binary) {
136 result = JSZip.prototype.utf8encode(result);
141 * A simple object representing a file in the zip file.
143 * @param {string} name the name of the file
144 * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
145 * @param {Object} options the options of the file
147 var ZipObject = function (name, data, options) {
150 this.options = options;
153 ZipObject.prototype = {
155 * Return the content as UTF8 string.
156 * @return {string} the UTF8 string.
158 asText : function () {
159 return dataToString.call(this, true);
162 * Returns the binary content.
163 * @return {string} the content as binary.
165 asBinary : function () {
166 return dataToString.call(this, false);
169 * Returns the content as a nodejs Buffer.
170 * @return {Buffer} the content as a Buffer.
172 asNodeBuffer : function () {
173 var result = getBinaryData(this);
174 return JSZip.utils.transformTo("nodebuffer", result);
177 * Returns the content as an Uint8Array.
178 * @return {Uint8Array} the content as an Uint8Array.
180 asUint8Array : function () {
181 var result = getBinaryData(this);
182 return JSZip.utils.transformTo("uint8array", result);
185 * Returns the content as an ArrayBuffer.
186 * @return {ArrayBuffer} the content as an ArrayBufer.
188 asArrayBuffer : function () {
189 return this.asUint8Array().buffer;
194 * Transform an integer into a string in hexadecimal.
196 * @param {number} dec the number to convert.
197 * @param {number} bytes the number of bytes to generate.
198 * @returns {string} the result.
200 var decToHex = function(dec, bytes) {
202 for(i = 0; i < bytes; i++) {
203 hex += String.fromCharCode(dec&0xff);
210 * Merge the objects passed as parameters into a new one.
212 * @param {...Object} var_args All objects to merge.
213 * @return {Object} a new object with the data of the others.
215 var extend = function () {
216 var result = {}, i, attr;
217 for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
218 for (attr in arguments[i]) {
219 if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
220 result[attr] = arguments[i][attr];
228 * Transforms the (incomplete) options from the user into the complete
229 * set of options to create a file.
231 * @param {Object} o the options from the user.
232 * @return {Object} the complete set of options.
234 var prepareFileAttrs = function (o) {
236 if (o.base64 === true && o.binary == null) {
239 o = extend(o, JSZip.defaults);
240 o.date = o.date || new Date();
241 if (o.compression !== null) o.compression = o.compression.toUpperCase();
247 * Add a file in the current folder.
249 * @param {string} name the name of the file
250 * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
251 * @param {Object} o the options of the file
252 * @return {Object} the new file.
254 var fileAdd = function (name, data, o) {
255 // be sure sub folders exist
256 var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
258 folderAdd.call(this, parent);
261 o = prepareFileAttrs(o);
263 if (o.dir || data === null || typeof data === "undefined") {
267 } else if (dataType === "string") {
268 if (o.binary && !o.base64) {
269 // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
270 if (o.optimizedBinaryString !== true) {
271 // this is a string, not in a base64 format.
272 // Be sure that this is a correct "binary string"
273 data = JSZip.utils.string2binary(data);
276 } else { // arraybuffer, uint8array, ...
280 if (!dataType && !(data instanceof JSZip.CompressedObject)) {
281 throw new Error("The data of '" + name + "' is in an unsupported format !");
284 // special case : it's way easier to work with Uint8Array than with ArrayBuffer
285 if (dataType === "arraybuffer") {
286 data = JSZip.utils.transformTo("uint8array", data);
290 return this.files[name] = new ZipObject(name, data, o);
295 * Find the parent folder of the path.
297 * @param {string} path the path to use
298 * @return {string} the parent folder, or ""
300 var parentFolder = function (path) {
301 if (path.slice(-1) == '/') {
302 path = path.substring(0, path.length - 1);
304 var lastSlash = path.lastIndexOf('/');
305 return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
309 * Add a (sub) folder in the current folder.
311 * @param {string} name the folder's name
312 * @return {Object} the new folder.
314 var folderAdd = function (name) {
315 // Check the name ends with a /
316 if (name.slice(-1) != "/") {
317 name += "/"; // IE doesn't like substr(-1)
320 // Does this folder already exist?
321 if (!this.files[name]) {
322 fileAdd.call(this, name, null, {dir:true});
324 return this.files[name];
328 * Generate a JSZip.CompressedObject for a given zipOject.
329 * @param {ZipObject} file the object to read.
330 * @param {JSZip.compression} compression the compression to use.
331 * @return {JSZip.CompressedObject} the compressed result.
333 var generateCompressedObjectFrom = function (file, compression) {
334 var result = new JSZip.CompressedObject(), content;
336 // the data has not been decompressed, we might reuse things !
337 if (file._data instanceof JSZip.CompressedObject) {
338 result.uncompressedSize = file._data.uncompressedSize;
339 result.crc32 = file._data.crc32;
341 if (result.uncompressedSize === 0 || file.options.dir) {
342 compression = JSZip.compressions['STORE'];
343 result.compressedContent = "";
345 } else if (file._data.compressionMethod === compression.magic) {
346 result.compressedContent = file._data.getCompressedContent();
348 content = file._data.getContent()
349 // need to decompress / recompress
350 result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
353 // have uncompressed data
354 content = getBinaryData(file);
355 if (!content || content.length === 0 || file.options.dir) {
356 compression = JSZip.compressions['STORE'];
359 result.uncompressedSize = content.length;
360 result.crc32 = this.crc32(content);
361 result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
364 result.compressedSize = result.compressedContent.length;
365 result.compressionMethod = compression.magic;
371 * Generate the various parts used in the construction of the final zip file.
372 * @param {string} name the file name.
373 * @param {ZipObject} file the file content.
374 * @param {JSZip.CompressedObject} compressedObject the compressed object.
375 * @param {number} offset the current offset from the start of the zip file.
376 * @return {object} the zip parts.
378 var generateZipParts = function(name, file, compressedObject, offset) {
379 var data = compressedObject.compressedContent,
380 utfEncodedFileName = this.utf8encode(file.name),
381 useUTF8 = utfEncodedFileName !== file.name,
387 // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
388 // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
389 // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
391 dosTime = o.date.getHours();
392 dosTime = dosTime << 6;
393 dosTime = dosTime | o.date.getMinutes();
394 dosTime = dosTime << 5;
395 dosTime = dosTime | o.date.getSeconds() / 2;
397 dosDate = o.date.getFullYear() - 1980;
398 dosDate = dosDate << 4;
399 dosDate = dosDate | (o.date.getMonth() + 1);
400 dosDate = dosDate << 5;
401 dosDate = dosDate | o.date.getDate();
406 // version needed to extract
407 header += "\x0A\x00";
408 // general purpose bit flag
409 // set bit 11 if utf8
410 header += useUTF8 ? "\x00\x08" : "\x00\x00";
411 // compression method
412 header += compressedObject.compressionMethod;
413 // last mod file time
414 header += decToHex(dosTime, 2);
415 // last mod file date
416 header += decToHex(dosDate, 2);
418 header += decToHex(compressedObject.crc32, 4);
420 header += decToHex(compressedObject.compressedSize, 4);
422 header += decToHex(compressedObject.uncompressedSize, 4);
424 header += decToHex(utfEncodedFileName.length, 2);
425 // extra field length
426 header += "\x00\x00";
429 var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
431 var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
432 // version made by (00: DOS)
434 // file header (common to file and central directory)
436 // file comment length
440 // internal file attributes TODO
442 // external file attributes
443 (file.options.dir===true?"\x10\x00\x00\x00":"\x00\x00\x00\x00")+
444 // relative offset of local header
445 decToHex(offset, 4) +
451 fileRecord : fileRecord,
452 dirRecord : dirRecord,
453 compressedObject : compressedObject
458 * An object to write any content to a string.
461 var StringWriter = function () {
464 StringWriter.prototype = {
466 * Append any content to the current string.
467 * @param {Object} input the content to add.
469 append : function (input) {
470 input = JSZip.utils.transformTo("string", input);
471 this.data.push(input);
474 * Finalize the construction an return the result.
475 * @return {string} the generated string.
477 finalize : function () {
478 return this.data.join("");
482 * An object to write any content to an Uint8Array.
484 * @param {number} length The length of the array.
486 var Uint8ArrayWriter = function (length) {
487 this.data = new Uint8Array(length);
490 Uint8ArrayWriter.prototype = {
492 * Append any content to the current array.
493 * @param {Object} input the content to add.
495 append : function (input) {
496 if (input.length !== 0) {
497 // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
498 input = JSZip.utils.transformTo("uint8array", input);
499 this.data.set(input, this.index);
500 this.index += input.length;
504 * Finalize the construction an return the result.
505 * @return {Uint8Array} the generated array.
507 finalize : function () {
512 // return the actual prototype of JSZip
515 * Read an existing zip and merge the data in the current JSZip object.
516 * The implementation is in jszip-load.js, don't forget to include it.
517 * @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
518 * @param {Object} options Options for loading the stream.
519 * options.base64 : is the stream in base64 ? default : false
520 * @return {JSZip} the current JSZip object
522 load : function (stream, options) {
523 throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
527 * Filter nested files/folders with the specified function.
528 * @param {Function} search the predicate to use :
529 * function (relativePath, file) {...}
530 * It takes 2 arguments : the relative path and the file.
531 * @return {Array} An array of matching elements.
533 filter : function (search) {
534 var result = [], filename, relativePath, file, fileClone;
535 for (filename in this.files) {
536 if ( !this.files.hasOwnProperty(filename) ) { continue; }
537 file = this.files[filename];
538 // return a new object, don't let the user mess with our internal objects :)
539 fileClone = new ZipObject(file.name, file._data, extend(file.options));
540 relativePath = filename.slice(this.root.length, filename.length);
541 if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
542 search(relativePath, fileClone)) { // and the file matches the function
543 result.push(fileClone);
550 * Add a file to the zip file, or search a file.
551 * @param {string|RegExp} name The name of the file to add (if data is defined),
552 * the name of the file to find (if no data) or a regex to match files.
553 * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
554 * @param {Object} o File options
555 * @return {JSZip|Object|Array} this JSZip object (when adding a file),
556 * a file (when searching by string) or an array of files (when searching by regex).
558 file : function(name, data, o) {
559 if (arguments.length === 1) {
560 if (name instanceof RegExp) {
562 return this.filter(function(relativePath, file) {
563 return !file.options.dir && regexp.test(relativePath);
566 return this.filter(function (relativePath, file) {
567 return !file.options.dir && relativePath === name;
570 } else { // more than one argument : we have data !
571 name = this.root+name;
572 fileAdd.call(this, name, data, o);
578 * Add a directory to the zip file, or search.
579 * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
580 * @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
582 folder : function(arg) {
587 if (arg instanceof RegExp) {
588 return this.filter(function(relativePath, file) {
589 return file.options.dir && arg.test(relativePath);
593 // else, name is a new folder
594 var name = this.root + arg;
595 var newFolder = folderAdd.call(this, name);
597 // Allow chaining by returning a new object with this folder as the root
598 var ret = this.clone();
599 ret.root = newFolder.name;
604 * Delete a file, or a directory and all sub-files, from the zip
605 * @param {string} name the name of the file to delete
606 * @return {JSZip} this JSZip object
608 remove : function(name) {
609 name = this.root + name;
610 var file = this.files[name];
612 // Look for any folders
613 if (name.slice(-1) != "/") {
616 file = this.files[name];
620 if (!file.options.dir) {
622 delete this.files[name];
625 var kids = this.filter(function (relativePath, file) {
626 return file.name.slice(0, name.length) === name;
628 for (var i = 0; i < kids.length; i++) {
629 delete this.files[kids[i].name];
638 * Generate the complete zip file
639 * @param {Object} options the options to generate the zip file :
640 * - base64, (deprecated, use type instead) true to generate base64.
641 * - compression, "STORE" by default.
642 * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
643 * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
645 generate : function(options) {
646 options = extend(options || {}, {
648 compression : "STORE",
652 JSZip.utils.checkSupport(options.type);
654 var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
657 // first, generate all the zip parts.
658 for (var name in this.files) {
659 if ( !this.files.hasOwnProperty(name) ) { continue; }
660 var file = this.files[name];
662 var compressionName = file.compression || options.compression.toUpperCase();
663 var compression = JSZip.compressions[compressionName];
665 throw new Error(compressionName + " is not a valid compression method !");
668 var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
670 var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
671 localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
672 centralDirLength += zipPart.dirRecord.length;
673 zipData.push(zipPart);
678 // end of central dir signature
679 dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
680 // number of this disk
682 // number of the disk with the start of the central directory
684 // total number of entries in the central directory on this disk
685 decToHex(zipData.length, 2) +
686 // total number of entries in the central directory
687 decToHex(zipData.length, 2) +
688 // size of the central directory 4 bytes
689 decToHex(centralDirLength, 4) +
690 // offset of start of central directory with respect to the starting disk number
691 decToHex(localDirLength, 4) +
692 // .ZIP file comment length
696 // we have all the parts (and the total length)
697 // time to create a writer !
698 switch(options.type.toLowerCase()) {
703 writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
706 default : // case "string" :
707 writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
711 for (i = 0; i < zipData.length; i++) {
712 writer.append(zipData[i].fileRecord);
713 writer.append(zipData[i].compressedObject.compressedContent);
715 for (i = 0; i < zipData.length; i++) {
716 writer.append(zipData[i].dirRecord);
719 writer.append(dirEnd);
721 var zip = writer.finalize();
725 switch(options.type.toLowerCase()) {
726 // case "zip is an Uint8Array"
730 return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
732 return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
734 // case "zip is a string"
736 return (options.base64) ? JSZip.base64.encode(zip) : zip;
737 default : // case "string" :
745 * http://www.webtoolkit.info/
748 crc32 : function crc32(input, crc) {
749 if (typeof input === "undefined" || !input.length) {
753 var isArray = JSZip.utils.getTypeOf(input) !== "string";
756 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
757 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
758 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
759 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
760 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
761 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
762 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
763 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
764 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
765 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
766 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
767 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
768 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
769 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
770 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
771 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
772 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
773 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
774 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
775 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
776 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
777 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
778 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
779 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
780 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
781 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
782 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
783 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
784 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
785 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
786 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
787 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
788 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
789 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
790 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
791 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
792 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
793 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
794 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
795 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
796 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
797 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
798 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
799 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
800 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
801 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
802 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
803 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
804 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
805 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
806 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
807 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
808 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
809 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
810 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
811 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
812 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
813 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
814 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
815 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
816 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
817 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
818 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
819 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
822 if (typeof(crc) == "undefined") { crc = 0; }
828 for( var i = 0, iTop = input.length; i < iTop; i++ ) {
829 byte = isArray ? input[i] : input.charCodeAt(i);
830 y = ( crc ^ byte ) & 0xFF;
832 crc = ( crc >>> 8 ) ^ x;
838 // Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
840 var newObj = new JSZip();
841 for (var i in this) {
842 if (typeof this[i] !== "function") {
851 * http://www.webtoolkit.info/javascript-utf8.html
853 utf8encode : function (string) {
854 // TextEncoder + Uint8Array to binary string is faster than checking every bytes on long strings.
855 // http://jsperf.com/utf8encode-vs-textencoder
856 // On short strings (file names for example), the TextEncoder API is (currently) slower.
857 if (JSZip.support.uint8array && typeof TextEncoder === "function") {
858 var u8 = TextEncoder("utf-8").encode(string);
859 return JSZip.utils.transformTo("string", u8);
861 if (JSZip.support.nodebuffer) {
862 return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
865 // array.join may be slower than string concatenation but generates less objects (less time spent garbage collecting).
866 // See also http://jsperf.com/array-direct-assignment-vs-push/31
867 var result = [], resIndex = 0;
869 for (var n = 0; n < string.length; n++) {
871 var c = string.charCodeAt(n);
874 result[resIndex++] = String.fromCharCode(c);
875 } else if ((c > 127) && (c < 2048)) {
876 result[resIndex++] = String.fromCharCode((c >> 6) | 192);
877 result[resIndex++] = String.fromCharCode((c & 63) | 128);
879 result[resIndex++] = String.fromCharCode((c >> 12) | 224);
880 result[resIndex++] = String.fromCharCode(((c >> 6) & 63) | 128);
881 result[resIndex++] = String.fromCharCode((c & 63) | 128);
886 return result.join("");
890 * http://www.webtoolkit.info/javascript-utf8.html
892 utf8decode : function (input) {
893 var result = [], resIndex = 0;
894 var type = JSZip.utils.getTypeOf(input);
895 var isArray = type !== "string";
897 var c = 0, c1 = 0, c2 = 0, c3 = 0;
899 // check if we can use the TextDecoder API
900 // see http://encoding.spec.whatwg.org/#api
901 if (JSZip.support.uint8array && typeof TextDecoder === "function") {
902 return TextDecoder("utf-8").decode(
903 JSZip.utils.transformTo("uint8array", input)
906 if (JSZip.support.nodebuffer) {
907 return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
910 while ( i < input.length ) {
912 c = isArray ? input[i] : input.charCodeAt(i);
915 result[resIndex++] = String.fromCharCode(c);
917 } else if ((c > 191) && (c < 224)) {
918 c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
919 result[resIndex++] = String.fromCharCode(((c & 31) << 6) | (c2 & 63));
922 c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
923 c3 = isArray ? input[i+2] : input.charCodeAt(i+2);
924 result[resIndex++] = String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
930 return result.join("");
936 * Compression methods
937 * This object is filled in as follow :
939 * magic // the 2 bytes indentifying the compression method
940 * compress // function, take the uncompressed content and return it compressed.
941 * uncompress // function, take the compressed content and return it uncompressed.
942 * compressInputType // string, the type accepted by the compress method. null to accept everything.
943 * uncompressInputType // string, the type accepted by the uncompress method. null to accept everything.
946 * STORE is the default compression method, so it's included in this file.
947 * Other methods should go to separated files : the user wants modularity.
949 JSZip.compressions = {
952 compress : function (content) {
953 return content; // no compression
955 uncompress : function (content) {
956 return content; // no compression
958 compressInputType : null,
959 uncompressInputType : null
964 * List features that require a modern browser, and if the current browser support them.
967 // contains true if JSZip can read/generate ArrayBuffer, false otherwise.
968 arraybuffer : (function(){
969 return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
971 // contains true if JSZip can read/generate nodejs Buffer, false otherwise.
972 nodebuffer : (function(){
973 return typeof Buffer !== "undefined";
975 // contains true if JSZip can read/generate Uint8Array, false otherwise.
976 uint8array : (function(){
977 return typeof Uint8Array !== "undefined";
979 // contains true if JSZip can read/generate Blob, false otherwise.
981 // the spec started with BlobBuilder then replaced it with a construtor for Blob.
982 // Result : we have browsers that :
983 // * know the BlobBuilder (but with prefix)
984 // * know the Blob constructor
985 // * know about Blob but not about how to build them
986 // About the "=== 0" test : if given the wrong type, it may be converted to a string.
987 // Instead of an empty content, we will get "[object Uint8Array]" for example.
988 if (typeof ArrayBuffer === "undefined") {
991 var buffer = new ArrayBuffer(0);
993 return new Blob([buffer], { type: "application/zip" }).size === 0;
998 var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
999 window.MozBlobBuilder || window.MSBlobBuilder)();
1000 builder.append(buffer);
1001 return builder.getBlob('application/zip').size === 0;
1012 * Convert a string to a "binary string" : a string containing only char codes between 0 and 255.
1013 * @param {string} str the string to transform.
1014 * @return {String} the binary string.
1016 string2binary : function (str) {
1018 for (var i = 0; i < str.length; i++) {
1019 result += String.fromCharCode(str.charCodeAt(i) & 0xff);
1024 * Create a Uint8Array from the string.
1025 * @param {string} str the string to transform.
1026 * @return {Uint8Array} the typed array.
1027 * @throws {Error} an Error if the browser doesn't support the requested feature.
1028 * @deprecated : use JSZip.utils.transformTo instead.
1030 string2Uint8Array : function (str) {
1031 return JSZip.utils.transformTo("uint8array", str);
1035 * Create a string from the Uint8Array.
1036 * @param {Uint8Array} array the array to transform.
1037 * @return {string} the string.
1038 * @throws {Error} an Error if the browser doesn't support the requested feature.
1039 * @deprecated : use JSZip.utils.transformTo instead.
1041 uint8Array2String : function (array) {
1042 return JSZip.utils.transformTo("string", array);
1045 * Create a blob from the given ArrayBuffer.
1046 * @param {ArrayBuffer} buffer the buffer to transform.
1047 * @return {Blob} the result.
1048 * @throws {Error} an Error if the browser doesn't support the requested feature.
1050 arrayBuffer2Blob : function (buffer) {
1051 JSZip.utils.checkSupport("blob");
1055 return new Blob([buffer], { type: "application/zip" });
1060 // deprecated, browser only, old way
1061 var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
1062 window.MozBlobBuilder || window.MSBlobBuilder)();
1063 builder.append(buffer);
1064 return builder.getBlob('application/zip');
1069 throw new Error("Bug : can't construct the Blob.");
1072 * Create a blob from the given string.
1073 * @param {string} str the string to transform.
1074 * @return {Blob} the result.
1075 * @throws {Error} an Error if the browser doesn't support the requested feature.
1077 string2Blob : function (str) {
1078 var buffer = JSZip.utils.transformTo("arraybuffer", str);
1079 return JSZip.utils.arrayBuffer2Blob(buffer);
1084 * The identity function.
1085 * @param {Object} input the input.
1086 * @return {Object} the same input.
1088 function identity(input) {
1093 * Fill in an array with a string.
1094 * @param {String} str the string to use.
1095 * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
1096 * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
1098 function stringToArrayLike(str, array) {
1099 for (var i = 0; i < str.length; ++i) {
1100 array[i] = str.charCodeAt(i) & 0xFF;
1106 * Transform an array-like object to a string.
1107 * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
1108 * @return {String} the result.
1110 function arrayLikeToString(array) {
1111 // Performances notes :
1112 // --------------------
1113 // String.fromCharCode.apply(null, array) is the fastest, see
1114 // see http://jsperf.com/converting-a-uint8array-to-a-string/2
1115 // but the stack is limited (and we can get huge arrays !).
1117 // result += String.fromCharCode(array[i]); generate too many strings !
1119 // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
1121 var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
1123 while (k < len && chunk > 1) {
1125 if (type === "array" || type === "nodebuffer") {
1126 result.push(String.fromCharCode.apply(null, array.slice(k, Math.max(k + chunk, len))));
1128 result.push(String.fromCharCode.apply(null, array.subarray(k, k + chunk)));
1132 chunk = Math.floor(chunk / 2);
1135 return result.join("");
1139 * Copy the data from an array-like to an other array-like.
1140 * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
1141 * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
1142 * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
1144 function arrayLikeToArrayLike(arrayFrom, arrayTo) {
1145 for(var i = 0; i < arrayFrom.length; i++) {
1146 arrayTo[i] = arrayFrom[i];
1151 // a matrix containing functions to transform everything into everything.
1155 transform["string"] = {
1156 "string" : identity,
1157 "array" : function (input) {
1158 return stringToArrayLike(input, new Array(input.length));
1160 "arraybuffer" : function (input) {
1161 return transform["string"]["uint8array"](input).buffer;
1163 "uint8array" : function (input) {
1164 return stringToArrayLike(input, new Uint8Array(input.length));
1166 "nodebuffer" : function (input) {
1167 return stringToArrayLike(input, new Buffer(input.length));
1172 transform["array"] = {
1173 "string" : arrayLikeToString,
1175 "arraybuffer" : function (input) {
1176 return (new Uint8Array(input)).buffer;
1178 "uint8array" : function (input) {
1179 return new Uint8Array(input);
1181 "nodebuffer" : function (input) {
1182 return new Buffer(input);
1187 transform["arraybuffer"] = {
1188 "string" : function (input) {
1189 return arrayLikeToString(new Uint8Array(input));
1191 "array" : function (input) {
1192 return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
1194 "arraybuffer" : identity,
1195 "uint8array" : function (input) {
1196 return new Uint8Array(input);
1198 "nodebuffer" : function (input) {
1199 return new Buffer(new Uint8Array(input));
1204 transform["uint8array"] = {
1205 "string" : arrayLikeToString,
1206 "array" : function (input) {
1207 return arrayLikeToArrayLike(input, new Array(input.length));
1209 "arraybuffer" : function (input) {
1210 return input.buffer;
1212 "uint8array" : identity,
1213 "nodebuffer" : function(input) {
1214 return new Buffer(input);
1219 transform["nodebuffer"] = {
1220 "string" : arrayLikeToString,
1221 "array" : function (input) {
1222 return arrayLikeToArrayLike(input, new Array(input.length));
1224 "arraybuffer" : function (input) {
1225 return transform["nodebuffer"]["uint8array"](input).buffer;
1227 "uint8array" : function (input) {
1228 return arrayLikeToArrayLike(input, new Uint8Array(input.length));
1230 "nodebuffer" : identity
1234 * Transform an input into any type.
1235 * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
1236 * If no output type is specified, the unmodified input will be returned.
1237 * @param {String} outputType the output type.
1238 * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
1239 * @throws {Error} an Error if the browser doesn't support the requested output type.
1241 JSZip.utils.transformTo = function (outputType, input) {
1243 // undefined, null, etc
1244 // an empty string won't harm.
1250 JSZip.utils.checkSupport(outputType);
1251 var inputType = JSZip.utils.getTypeOf(input);
1252 var result = transform[inputType][outputType](input);
1257 * Return the type of the input.
1258 * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
1259 * @param {Object} input the input to identify.
1260 * @return {String} the (lowercase) type of the input.
1262 JSZip.utils.getTypeOf = function (input) {
1263 if (typeof input === "string") {
1266 if (input instanceof Array) {
1269 if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
1270 return "nodebuffer";
1272 if (JSZip.support.uint8array && input instanceof Uint8Array) {
1273 return "uint8array";
1275 if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
1276 return "arraybuffer";
1281 * Throw an exception if the type is not supported.
1282 * @param {String} type the type to check.
1283 * @throws {Error} an Error if the browser doesn't support the requested type.
1285 JSZip.utils.checkSupport = function (type) {
1286 var supported = true;
1287 switch (type.toLowerCase()) {
1289 supported = JSZip.support.uint8array;
1292 supported = JSZip.support.arraybuffer;
1295 supported = JSZip.support.nodebuffer;
1298 supported = JSZip.support.blob;
1302 throw new Error(type + " is not supported by this browser");
1311 * Represents an entry in the zip.
1312 * The content may or may not be compressed.
1315 JSZip.CompressedObject = function () {
1316 this.compressedSize = 0;
1317 this.uncompressedSize = 0;
1319 this.compressionMethod = null;
1320 this.compressedContent = null;
1323 JSZip.CompressedObject.prototype = {
1325 * Return the decompressed content in an unspecified format.
1326 * The format will depend on the decompressor.
1327 * @return {Object} the decompressed content.
1329 getContent : function () {
1330 return null; // see implementation
1333 * Return the compressed content in an unspecified format.
1334 * The format will depend on the compressed conten source.
1335 * @return {Object} the compressed content.
1337 getCompressedContent : function () {
1338 return null; // see implementation
1345 * Base64 encode / decode
1346 * http://www.webtoolkit.info/
1348 * Hacked so that it doesn't utf8 en/decode everything
1350 JSZip.base64 = (function() {
1352 var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1355 // public method for encoding
1356 encode : function(input, utf8) {
1358 var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
1361 while (i < input.length) {
1363 chr1 = input.charCodeAt(i++);
1364 chr2 = input.charCodeAt(i++);
1365 chr3 = input.charCodeAt(i++);
1368 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
1369 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
1374 } else if (isNaN(chr3)) {
1379 _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
1380 _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
1387 // public method for decoding
1388 decode : function(input, utf8) {
1390 var chr1, chr2, chr3;
1391 var enc1, enc2, enc3, enc4;
1394 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
1396 while (i < input.length) {
1398 enc1 = _keyStr.indexOf(input.charAt(i++));
1399 enc2 = _keyStr.indexOf(input.charAt(i++));
1400 enc3 = _keyStr.indexOf(input.charAt(i++));
1401 enc4 = _keyStr.indexOf(input.charAt(i++));
1403 chr1 = (enc1 << 2) | (enc2 >> 4);
1404 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
1405 chr3 = ((enc3 & 3) << 6) | enc4;
1407 output = output + String.fromCharCode(chr1);
1410 output = output + String.fromCharCode(chr2);
1413 output = output + String.fromCharCode(chr3);
1424 // enforcing Stuk's coding style
1425 // vim: set shiftwidth=3 softtabstop=3: