3.0.0 -> 3.0.1
[bootswatch] / bower_components / bootstrap / docs-assets / js / jszip.js
1 /**
2
3 JSZip - A Javascript class for generating and reading zip files
4 <http://stuartk.com/jszip>
5
6 (c) 2009-2012 Stuart Knightley <stuart [at] stuartk.com>
7 Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
8
9 Usage:
10    zip = new JSZip();
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");
15
16    base64zip = zip.generate();
17
18 **/
19 "use strict";
20
21 /**
22  * Representation a of zip file in js
23  * @constructor
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).
26  */
27 var JSZip = function(data, options) {
28    // object containing the files :
29    // {
30    //   "folder/" : {...},
31    //   "folder/data.txt" : {...}
32    // }
33    this.files = {};
34
35    // Where we are in the hierarchy
36    this.root = "";
37
38    if (data) {
39       this.load(data, options);
40    }
41 };
42
43 JSZip.signature = {
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"
50 };
51
52 // Default properties for a new file
53 JSZip.defaults = {
54    base64: false,
55    binary: false,
56    dir: false,
57    date: null,
58    compression: null
59 };
60
61 /*
62  * List features that require a modern browser, and if the current browser support them.
63  */
64 JSZip.support = {
65    // contains true if JSZip can read/generate ArrayBuffer, false otherwise.
66    arraybuffer : (function(){
67       return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
68    })(),
69    // contains true if JSZip can read/generate nodejs Buffer, false otherwise.
70    nodebuffer : (function(){
71       return typeof Buffer !== "undefined";
72    })(),
73    // contains true if JSZip can read/generate Uint8Array, false otherwise.
74    uint8array : (function(){
75       return typeof Uint8Array !== "undefined";
76    })(),
77    // contains true if JSZip can read/generate Blob, false otherwise.
78    blob : (function(){
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") {
87          return false;
88       }
89       var buffer = new ArrayBuffer(0);
90       try {
91          return new Blob([buffer], { type: "application/zip" }).size === 0;
92       }
93       catch(e) {}
94
95       try {
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;
100       }
101       catch(e) {}
102
103       return false;
104    })()
105 };
106
107 JSZip.prototype = (function () {
108    var textEncoder, textDecoder;
109    if (
110       JSZip.support.uint8array &&
111       typeof TextEncoder === "function" &&
112       typeof TextDecoder === "function"
113    ) {
114       textEncoder = new TextEncoder("utf-8");
115       textDecoder = new TextDecoder("utf-8");
116    }
117
118    /**
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.
122     */
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;
128
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);
137             }
138          }
139       }
140       return file._data;
141    };
142
143    /**
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.
147     */
148    var getBinaryData = function (file) {
149       var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
150       if (type === "string") {
151          if (!file.options.binary) {
152             // unicode text !
153             // unicode string => binary string is a painful process, check if we can avoid it.
154             if (textEncoder) {
155                return textEncoder.encode(result);
156             }
157             if (JSZip.support.nodebuffer) {
158                return new Buffer(result, "utf-8");
159             }
160          }
161          return file.asBinary();
162       }
163       return result;
164    }
165
166    /**
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.
170     */
171    var dataToString = function (asUTF8) {
172       var result = getRawData(this);
173       if (result === null || typeof result === "undefined") {
174          return "";
175       }
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);
179       }
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);
184       } else {
185          // no utf8 transformation, do the array => string step.
186          result = JSZip.utils.transformTo("string", result);
187       }
188
189       if (!asUTF8 && !this.options.binary) {
190          result = JSZip.prototype.utf8encode(result);
191       }
192       return result;
193    };
194    /**
195     * A simple object representing a file in the zip file.
196     * @constructor
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
200     */
201    var ZipObject = function (name, data, options) {
202       this.name = name;
203       this._data = data;
204       this.options = options;
205    };
206
207    ZipObject.prototype = {
208       /**
209        * Return the content as UTF8 string.
210        * @return {string} the UTF8 string.
211        */
212       asText : function () {
213          return dataToString.call(this, true);
214       },
215       /**
216        * Returns the binary content.
217        * @return {string} the content as binary.
218        */
219       asBinary : function () {
220          return dataToString.call(this, false);
221       },
222       /**
223        * Returns the content as a nodejs Buffer.
224        * @return {Buffer} the content as a Buffer.
225        */
226       asNodeBuffer : function () {
227          var result = getBinaryData(this);
228          return JSZip.utils.transformTo("nodebuffer", result);
229       },
230       /**
231        * Returns the content as an Uint8Array.
232        * @return {Uint8Array} the content as an Uint8Array.
233        */
234       asUint8Array : function () {
235          var result = getBinaryData(this);
236          return JSZip.utils.transformTo("uint8array", result);
237       },
238       /**
239        * Returns the content as an ArrayBuffer.
240        * @return {ArrayBuffer} the content as an ArrayBufer.
241        */
242       asArrayBuffer : function () {
243          return this.asUint8Array().buffer;
244       }
245    };
246
247    /**
248     * Transform an integer into a string in hexadecimal.
249     * @private
250     * @param {number} dec the number to convert.
251     * @param {number} bytes the number of bytes to generate.
252     * @returns {string} the result.
253     */
254    var decToHex = function(dec, bytes) {
255       var hex = "", i;
256       for(i = 0; i < bytes; i++) {
257          hex += String.fromCharCode(dec&0xff);
258          dec=dec>>>8;
259       }
260       return hex;
261    };
262
263    /**
264     * Merge the objects passed as parameters into a new one.
265     * @private
266     * @param {...Object} var_args All objects to merge.
267     * @return {Object} a new object with the data of the others.
268     */
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];
275             }
276          }
277       }
278       return result;
279    };
280
281    /**
282     * Transforms the (incomplete) options from the user into the complete
283     * set of options to create a file.
284     * @private
285     * @param {Object} o the options from the user.
286     * @return {Object} the complete set of options.
287     */
288    var prepareFileAttrs = function (o) {
289       o = o || {};
290       if (o.base64 === true && o.binary == null) {
291          o.binary = true;
292       }
293       o = extend(o, JSZip.defaults);
294       o.date = o.date || new Date();
295       if (o.compression !== null) o.compression = o.compression.toUpperCase();
296
297       return o;
298    };
299
300    /**
301     * Add a file in the current folder.
302     * @private
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.
307     */
308    var fileAdd = function (name, data, o) {
309       // be sure sub folders exist
310       var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
311       if (parent) {
312          folderAdd.call(this, parent);
313       }
314
315       o = prepareFileAttrs(o);
316
317       if (o.dir || data === null || typeof data === "undefined") {
318          o.base64 = false;
319          o.binary = false;
320          data = null;
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);
328             }
329          }
330       } else { // arraybuffer, uint8array, ...
331          o.base64 = false;
332          o.binary = true;
333
334          if (!dataType && !(data instanceof JSZip.CompressedObject)) {
335             throw new Error("The data of '" + name + "' is in an unsupported format !");
336          }
337
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);
341          }
342       }
343
344       return this.files[name] = new ZipObject(name, data, o);
345    };
346
347
348    /**
349     * Find the parent folder of the path.
350     * @private
351     * @param {string} path the path to use
352     * @return {string} the parent folder, or ""
353     */
354    var parentFolder = function (path) {
355       if (path.slice(-1) == '/') {
356          path = path.substring(0, path.length - 1);
357       }
358       var lastSlash = path.lastIndexOf('/');
359       return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
360    };
361
362    /**
363     * Add a (sub) folder in the current folder.
364     * @private
365     * @param {string} name the folder's name
366     * @return {Object} the new folder.
367     */
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)
372       }
373
374       // Does this folder already exist?
375       if (!this.files[name]) {
376          fileAdd.call(this, name, null, {dir:true});
377       }
378       return this.files[name];
379    };
380
381    /**
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.
386     */
387    var generateCompressedObjectFrom = function (file, compression) {
388       var result = new JSZip.CompressedObject(), content;
389
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;
394
395          if (result.uncompressedSize === 0 || file.options.dir) {
396             compression = JSZip.compressions['STORE'];
397             result.compressedContent = "";
398             result.crc32 = 0;
399          } else if (file._data.compressionMethod === compression.magic) {
400             result.compressedContent = file._data.getCompressedContent();
401          } else {
402             content = file._data.getContent()
403             // need to decompress / recompress
404             result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
405          }
406       } else {
407          // have uncompressed data
408          content = getBinaryData(file);
409          if (!content || content.length === 0 || file.options.dir) {
410             compression = JSZip.compressions['STORE'];
411             content = "";
412          }
413          result.uncompressedSize = content.length;
414          result.crc32 = this.crc32(content);
415          result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
416       }
417
418       result.compressedSize = result.compressedContent.length;
419       result.compressionMethod = compression.magic;
420
421       return result;
422    };
423
424    /**
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.
431     */
432    var generateZipParts = function(name, file, compressedObject, offset) {
433       var data = compressedObject.compressedContent,
434           utfEncodedFileName = this.utf8encode(file.name),
435           useUTF8 = utfEncodedFileName !== file.name,
436           o       = file.options,
437           dosTime,
438           dosDate;
439
440       // date
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
444
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;
450
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();
456
457
458       var header = "";
459
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);
471       // crc-32
472       header += decToHex(compressedObject.crc32, 4);
473       // compressed size
474       header += decToHex(compressedObject.compressedSize, 4);
475       // uncompressed size
476       header += decToHex(compressedObject.uncompressedSize, 4);
477       // file name length
478       header += decToHex(utfEncodedFileName.length, 2);
479       // extra field length
480       header += "\x00\x00";
481
482
483       var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
484
485       var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
486       // version made by (00: DOS)
487       "\x14\x00" +
488       // file header (common to file and central directory)
489       header +
490       // file comment length
491       "\x00\x00" +
492       // disk number start
493       "\x00\x00" +
494       // internal file attributes TODO
495       "\x00\x00" +
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) +
500       // file name
501       utfEncodedFileName;
502
503
504       return {
505          fileRecord : fileRecord,
506          dirRecord : dirRecord,
507          compressedObject : compressedObject
508       };
509    };
510
511    /**
512     * An object to write any content to a string.
513     * @constructor
514     */
515    var StringWriter = function () {
516       this.data = [];
517    };
518    StringWriter.prototype = {
519       /**
520        * Append any content to the current string.
521        * @param {Object} input the content to add.
522        */
523       append : function (input) {
524          input = JSZip.utils.transformTo("string", input);
525          this.data.push(input);
526       },
527       /**
528        * Finalize the construction an return the result.
529        * @return {string} the generated string.
530        */
531       finalize : function () {
532          return this.data.join("");
533       }
534    };
535    /**
536     * An object to write any content to an Uint8Array.
537     * @constructor
538     * @param {number} length The length of the array.
539     */
540    var Uint8ArrayWriter = function (length) {
541       this.data = new Uint8Array(length);
542       this.index = 0;
543    };
544    Uint8ArrayWriter.prototype = {
545       /**
546        * Append any content to the current array.
547        * @param {Object} input the content to add.
548        */
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;
555          }
556       },
557       /**
558        * Finalize the construction an return the result.
559        * @return {Uint8Array} the generated array.
560        */
561       finalize : function () {
562          return this.data;
563       }
564    };
565
566    // return the actual prototype of JSZip
567    return {
568       /**
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
575        */
576       load : function (stream, options) {
577          throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
578       },
579
580       /**
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.
586        */
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);
598             }
599          }
600          return result;
601       },
602
603       /**
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).
611        */
612       file : function(name, data, o) {
613          if (arguments.length === 1) {
614             if (JSZip.utils.isRegExp(name)) {
615                var regexp = name;
616                return this.filter(function(relativePath, file) {
617                   return !file.options.dir && regexp.test(relativePath);
618                });
619             } else { // text
620                return this.filter(function (relativePath, file) {
621                   return !file.options.dir && relativePath === name;
622                })[0]||null;
623             }
624          } else { // more than one argument : we have data !
625             name = this.root+name;
626             fileAdd.call(this, name, data, o);
627          }
628          return this;
629       },
630
631       /**
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.
635        */
636       folder : function(arg) {
637          if (!arg) {
638             return this;
639          }
640
641          if (JSZip.utils.isRegExp(arg)) {
642             return this.filter(function(relativePath, file) {
643                return file.options.dir && arg.test(relativePath);
644             });
645          }
646
647          // else, name is a new folder
648          var name = this.root + arg;
649          var newFolder = folderAdd.call(this, name);
650
651          // Allow chaining by returning a new object with this folder as the root
652          var ret = this.clone();
653          ret.root = newFolder.name;
654          return ret;
655       },
656
657       /**
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
661        */
662       remove : function(name) {
663          name = this.root + name;
664          var file = this.files[name];
665          if (!file) {
666             // Look for any folders
667             if (name.slice(-1) != "/") {
668                name += "/";
669             }
670             file = this.files[name];
671          }
672
673          if (file) {
674             if (!file.options.dir) {
675                // file
676                delete this.files[name];
677             } else {
678                // folder
679                var kids = this.filter(function (relativePath, file) {
680                   return file.name.slice(0, name.length) === name;
681                });
682                for (var i = 0; i < kids.length; i++) {
683                   delete this.files[kids[i].name];
684                }
685             }
686          }
687
688          return this;
689       },
690
691       /**
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
698        */
699       generate : function(options) {
700          options = extend(options || {}, {
701             base64 : true,
702             compression : "STORE",
703             type : "base64"
704          });
705
706          JSZip.utils.checkSupport(options.type);
707
708          var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
709
710
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];
715
716             var compressionName = file.options.compression || options.compression.toUpperCase();
717             var compression = JSZip.compressions[compressionName];
718             if (!compression) {
719                throw new Error(compressionName + " is not a valid compression method !");
720             }
721
722             var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
723
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);
728          }
729
730          var dirEnd = "";
731
732          // end of central dir signature
733          dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
734          // number of this disk
735          "\x00\x00" +
736          // number of the disk with the start of the central directory
737          "\x00\x00" +
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
747          "\x00\x00";
748
749
750          // we have all the parts (and the total length)
751          // time to create a writer !
752          switch(options.type.toLowerCase()) {
753             case "uint8array" :
754             case "arraybuffer" :
755             case "blob" :
756             case "nodebuffer" :
757                writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
758                break;
759             case "base64" :
760             default : // case "string" :
761                writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
762                break;
763          }
764
765          for (i = 0; i < zipData.length; i++) {
766             writer.append(zipData[i].fileRecord);
767             writer.append(zipData[i].compressedObject.compressedContent);
768          }
769          for (i = 0; i < zipData.length; i++) {
770             writer.append(zipData[i].dirRecord);
771          }
772
773          writer.append(dirEnd);
774
775          var zip = writer.finalize();
776
777
778
779          switch(options.type.toLowerCase()) {
780             // case "zip is an Uint8Array"
781             case "uint8array" :
782             case "arraybuffer" :
783             case "nodebuffer" :
784                return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
785             case "blob" :
786                return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
787
788             // case "zip is a string"
789             case "base64" :
790                return (options.base64) ? JSZip.base64.encode(zip) : zip;
791             default : // case "string" :
792                return zip;
793          }
794       },
795
796       /**
797        *
798        *  Javascript crc32
799        *  http://www.webtoolkit.info/
800        *
801        */
802       crc32 : function crc32(input, crc) {
803          if (typeof input === "undefined" || !input.length) {
804             return 0;
805          }
806
807          var isArray = JSZip.utils.getTypeOf(input) !== "string";
808
809          var table = [
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
874          ];
875
876          if (typeof(crc) == "undefined") { crc = 0; }
877          var x = 0;
878          var y = 0;
879          var byte = 0;
880
881          crc = crc ^ (-1);
882          for( var i = 0, iTop = input.length; i < iTop; i++ ) {
883             byte = isArray ? input[i] : input.charCodeAt(i);
884             y = ( crc ^ byte ) & 0xFF;
885             x = table[y];
886             crc = ( crc >>> 8 ) ^ x;
887          }
888
889          return crc ^ (-1);
890       },
891
892       // Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
893       clone : function() {
894          var newObj = new JSZip();
895          for (var i in this) {
896             if (typeof this[i] !== "function") {
897                newObj[i] = this[i];
898             }
899          }
900          return newObj;
901       },
902
903
904       /**
905        * http://www.webtoolkit.info/javascript-utf8.html
906        */
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.
911          if (textEncoder) {
912             var u8 = textEncoder.encode(string);
913             return JSZip.utils.transformTo("string", u8);
914          }
915          if (JSZip.support.nodebuffer) {
916             return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
917          }
918
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;
922
923          for (var n = 0; n < string.length; n++) {
924
925             var c = string.charCodeAt(n);
926
927             if (c < 128) {
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);
932             } else {
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);
936             }
937
938          }
939
940          return result.join("");
941       },
942
943       /**
944        * http://www.webtoolkit.info/javascript-utf8.html
945        */
946       utf8decode : function (input) {
947          var result = [], resIndex = 0;
948          var type = JSZip.utils.getTypeOf(input);
949          var isArray = type !== "string";
950          var i = 0;
951          var c = 0, c1 = 0, c2 = 0, c3 = 0;
952
953          // check if we can use the TextDecoder API
954          // see http://encoding.spec.whatwg.org/#api
955          if (textDecoder) {
956             return textDecoder.decode(
957                JSZip.utils.transformTo("uint8array", input)
958             );
959          }
960          if (JSZip.support.nodebuffer) {
961             return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
962          }
963
964          while ( i < input.length ) {
965
966             c = isArray ? input[i] : input.charCodeAt(i);
967
968             if (c < 128) {
969                result[resIndex++] = String.fromCharCode(c);
970                i++;
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));
974                i += 2;
975             } else {
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));
979                i += 3;
980             }
981
982          }
983
984          return result.join("");
985       }
986    };
987 }());
988
989 /*
990  * Compression methods
991  * This object is filled in as follow :
992  * name : {
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.
998  * }
999  *
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.
1002  */
1003 JSZip.compressions = {
1004    "STORE" : {
1005       magic : "\x00\x00",
1006       compress : function (content) {
1007          return content; // no compression
1008       },
1009       uncompress : function (content) {
1010          return content; // no compression
1011       },
1012       compressInputType : null,
1013       uncompressInputType : null
1014    }
1015 };
1016
1017 (function () {
1018    JSZip.utils = {
1019       /**
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.
1023        */
1024       string2binary : function (str) {
1025          var result = "";
1026          for (var i = 0; i < str.length; i++) {
1027             result += String.fromCharCode(str.charCodeAt(i) & 0xff);
1028          }
1029          return result;
1030       },
1031       /**
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.
1037        */
1038       string2Uint8Array : function (str) {
1039          return JSZip.utils.transformTo("uint8array", str);
1040       },
1041
1042       /**
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.
1048        */
1049       uint8Array2String : function (array) {
1050          return JSZip.utils.transformTo("string", array);
1051       },
1052       /**
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.
1057        */
1058       arrayBuffer2Blob : function (buffer) {
1059          JSZip.utils.checkSupport("blob");
1060
1061          try {
1062             // Blob constructor
1063             return new Blob([buffer], { type: "application/zip" });
1064          }
1065          catch(e) {}
1066
1067          try {
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');
1073          }
1074          catch(e) {}
1075
1076          // well, fuck ?!
1077          throw new Error("Bug : can't construct the Blob.");
1078       },
1079       /**
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.
1084        */
1085       string2Blob : function (str) {
1086          var buffer = JSZip.utils.transformTo("arraybuffer", str);
1087          return JSZip.utils.arrayBuffer2Blob(buffer);
1088       }
1089    };
1090
1091    /**
1092     * The identity function.
1093     * @param {Object} input the input.
1094     * @return {Object} the same input.
1095     */
1096    function identity(input) {
1097       return input;
1098    };
1099
1100    /**
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.
1105     */
1106    function stringToArrayLike(str, array) {
1107       for (var i = 0; i < str.length; ++i) {
1108          array[i] = str.charCodeAt(i) & 0xFF;
1109       }
1110       return array;
1111    };
1112
1113    /**
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.
1117     */
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 !).
1124       //
1125       // result += String.fromCharCode(array[i]); generate too many strings !
1126       //
1127       // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
1128       var chunk = 65536;
1129       var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
1130
1131       var canUseApply = true;
1132       try {
1133          switch(type) {
1134             case "uint8array":
1135                String.fromCharCode.apply(null, new Uint8Array(0));
1136                break;
1137             case "nodebuffer":
1138                String.fromCharCode.apply(null, new Buffer(0));
1139                break;
1140          }
1141       } catch(e) {
1142          canUseApply = false;
1143       }
1144
1145       // no apply : slow and painful algorithm
1146       // default browser on android 4.*
1147       if (!canUseApply) {
1148          var resultStr = "";
1149          for(var i = 0; i < array.length;i++) {
1150             resultStr += String.fromCharCode(array[i]);
1151          }
1152          return resultStr;
1153       }
1154
1155       while (k < len && chunk > 1) {
1156          try {
1157             if (type === "array" || type === "nodebuffer") {
1158                result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
1159             } else {
1160                result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
1161             }
1162             k += chunk;
1163          } catch (e) {
1164             chunk = Math.floor(chunk / 2);
1165          }
1166       }
1167       return result.join("");
1168    };
1169
1170    /**
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.
1175     */
1176    function arrayLikeToArrayLike(arrayFrom, arrayTo) {
1177       for(var i = 0; i < arrayFrom.length; i++) {
1178          arrayTo[i] = arrayFrom[i];
1179       }
1180       return arrayTo;
1181    };
1182
1183    // a matrix containing functions to transform everything into everything.
1184    var transform = {};
1185
1186    // string to ?
1187    transform["string"] = {
1188       "string" : identity,
1189       "array" : function (input) {
1190          return stringToArrayLike(input, new Array(input.length));
1191       },
1192       "arraybuffer" : function (input) {
1193          return transform["string"]["uint8array"](input).buffer;
1194       },
1195       "uint8array" : function (input) {
1196          return stringToArrayLike(input, new Uint8Array(input.length));
1197       },
1198       "nodebuffer" : function (input) {
1199          return stringToArrayLike(input, new Buffer(input.length));
1200       }
1201    };
1202
1203    // array to ?
1204    transform["array"] = {
1205       "string" : arrayLikeToString,
1206       "array" : identity,
1207       "arraybuffer" : function (input) {
1208          return (new Uint8Array(input)).buffer;
1209       },
1210       "uint8array" : function (input) {
1211          return new Uint8Array(input);
1212       },
1213       "nodebuffer" : function (input) {
1214          return new Buffer(input);
1215       }
1216    };
1217
1218    // arraybuffer to ?
1219    transform["arraybuffer"] = {
1220       "string" : function (input) {
1221          return arrayLikeToString(new Uint8Array(input));
1222       },
1223       "array" : function (input) {
1224          return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
1225       },
1226       "arraybuffer" : identity,
1227       "uint8array" : function (input) {
1228          return new Uint8Array(input);
1229       },
1230       "nodebuffer" : function (input) {
1231          return new Buffer(new Uint8Array(input));
1232       }
1233    };
1234
1235    // uint8array to ?
1236    transform["uint8array"] = {
1237       "string" : arrayLikeToString,
1238       "array" : function (input) {
1239          return arrayLikeToArrayLike(input, new Array(input.length));
1240       },
1241       "arraybuffer" : function (input) {
1242          return input.buffer;
1243       },
1244       "uint8array" : identity,
1245       "nodebuffer" : function(input) {
1246          return new Buffer(input);
1247       }
1248    };
1249
1250    // nodebuffer to ?
1251    transform["nodebuffer"] = {
1252       "string" : arrayLikeToString,
1253       "array" : function (input) {
1254          return arrayLikeToArrayLike(input, new Array(input.length));
1255       },
1256       "arraybuffer" : function (input) {
1257          return transform["nodebuffer"]["uint8array"](input).buffer;
1258       },
1259       "uint8array" : function (input) {
1260          return arrayLikeToArrayLike(input, new Uint8Array(input.length));
1261       },
1262       "nodebuffer" : identity
1263    };
1264
1265    /**
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.
1272     */
1273    JSZip.utils.transformTo = function (outputType, input) {
1274       if (!input) {
1275          // undefined, null, etc
1276          // an empty string won't harm.
1277          input = "";
1278       }
1279       if (!outputType) {
1280          return input;
1281       }
1282       JSZip.utils.checkSupport(outputType);
1283       var inputType = JSZip.utils.getTypeOf(input);
1284       var result = transform[inputType][outputType](input);
1285       return result;
1286    };
1287
1288    /**
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.
1293     */
1294    JSZip.utils.getTypeOf = function (input) {
1295       if (typeof input === "string") {
1296          return "string";
1297       }
1298       if (Object.prototype.toString.call(input) === "[object Array]") {
1299          return "array";
1300       }
1301       if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
1302          return "nodebuffer";
1303       }
1304       if (JSZip.support.uint8array && input instanceof Uint8Array) {
1305          return "uint8array";
1306       }
1307       if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
1308          return "arraybuffer";
1309       }
1310    };
1311
1312    /**
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,
1316     * false otherwise
1317     */
1318    JSZip.utils.isRegExp = function (object) {
1319       return Object.prototype.toString.call(object) === "[object RegExp]";
1320    };
1321
1322    /**
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.
1326     */
1327    JSZip.utils.checkSupport = function (type) {
1328       var supported = true;
1329       switch (type.toLowerCase()) {
1330          case "uint8array":
1331             supported = JSZip.support.uint8array;
1332          break;
1333          case "arraybuffer":
1334             supported = JSZip.support.arraybuffer;
1335          break;
1336          case "nodebuffer":
1337             supported = JSZip.support.nodebuffer;
1338          break;
1339          case "blob":
1340             supported = JSZip.support.blob;
1341          break;
1342       }
1343       if (!supported) {
1344          throw new Error(type + " is not supported by this browser");
1345       }
1346    };
1347
1348
1349 })();
1350
1351 (function (){
1352    /**
1353     * Represents an entry in the zip.
1354     * The content may or may not be compressed.
1355     * @constructor
1356     */
1357    JSZip.CompressedObject = function () {
1358          this.compressedSize = 0;
1359          this.uncompressedSize = 0;
1360          this.crc32 = 0;
1361          this.compressionMethod = null;
1362          this.compressedContent = null;
1363    };
1364
1365    JSZip.CompressedObject.prototype = {
1366       /**
1367        * Return the decompressed content in an unspecified format.
1368        * The format will depend on the decompressor.
1369        * @return {Object} the decompressed content.
1370        */
1371       getContent : function () {
1372          return null; // see implementation
1373       },
1374       /**
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.
1378        */
1379       getCompressedContent : function () {
1380          return null; // see implementation
1381       }
1382    };
1383 })();
1384
1385 /**
1386  *
1387  *  Base64 encode / decode
1388  *  http://www.webtoolkit.info/
1389  *
1390  *  Hacked so that it doesn't utf8 en/decode everything
1391  **/
1392 JSZip.base64 = (function() {
1393    // private property
1394    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1395
1396    return {
1397       // public method for encoding
1398       encode : function(input, utf8) {
1399          var output = "";
1400          var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
1401          var i = 0;
1402
1403          while (i < input.length) {
1404
1405             chr1 = input.charCodeAt(i++);
1406             chr2 = input.charCodeAt(i++);
1407             chr3 = input.charCodeAt(i++);
1408
1409             enc1 = chr1 >> 2;
1410             enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
1411             enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
1412             enc4 = chr3 & 63;
1413
1414             if (isNaN(chr2)) {
1415                enc3 = enc4 = 64;
1416             } else if (isNaN(chr3)) {
1417                enc4 = 64;
1418             }
1419
1420             output = output +
1421                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
1422                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
1423
1424          }
1425
1426          return output;
1427       },
1428
1429       // public method for decoding
1430       decode : function(input, utf8) {
1431          var output = "";
1432          var chr1, chr2, chr3;
1433          var enc1, enc2, enc3, enc4;
1434          var i = 0;
1435
1436          input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
1437
1438          while (i < input.length) {
1439
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++));
1444
1445             chr1 = (enc1 << 2) | (enc2 >> 4);
1446             chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
1447             chr3 = ((enc3 & 3) << 6) | enc4;
1448
1449             output = output + String.fromCharCode(chr1);
1450
1451             if (enc3 != 64) {
1452                output = output + String.fromCharCode(chr2);
1453             }
1454             if (enc4 != 64) {
1455                output = output + String.fromCharCode(chr3);
1456             }
1457
1458          }
1459
1460          return output;
1461
1462       }
1463    };
1464 }());
1465
1466 // enforcing Stuk's coding style
1467 // vim: set shiftwidth=3 softtabstop=3: