update bootstrap to 3.0.0-rc2
[bootswatch] / bower_components / bootstrap / 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 JSZip.prototype = (function () {
63
64    /**
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.
68     */
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;
74
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);
83             }
84          }
85       }
86       return file._data;
87    };
88
89    /**
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.
93     */
94    var getBinaryData = function (file) {
95       var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
96       if (type === "string") {
97          if (!file.options.binary) {
98             // unicode text !
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);
102             }
103             if (JSZip.support.nodebuffer) {
104                return new Buffer(result, "utf-8");
105             }
106          }
107          return file.asBinary();
108       }
109       return result;
110    }
111
112    /**
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.
116     */
117    var dataToString = function (asUTF8) {
118       var result = getRawData(this);
119       if (result === null || typeof result === "undefined") {
120          return "";
121       }
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);
125       }
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);
130       } else {
131          // no utf8 transformation, do the array => string step.
132          result = JSZip.utils.transformTo("string", result);
133       }
134
135       if (!asUTF8 && !this.options.binary) {
136          result = JSZip.prototype.utf8encode(result);
137       }
138       return result;
139    };
140    /**
141     * A simple object representing a file in the zip file.
142     * @constructor
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
146     */
147    var ZipObject = function (name, data, options) {
148       this.name = name;
149       this._data = data;
150       this.options = options;
151    };
152
153    ZipObject.prototype = {
154       /**
155        * Return the content as UTF8 string.
156        * @return {string} the UTF8 string.
157        */
158       asText : function () {
159          return dataToString.call(this, true);
160       },
161       /**
162        * Returns the binary content.
163        * @return {string} the content as binary.
164        */
165       asBinary : function () {
166          return dataToString.call(this, false);
167       },
168       /**
169        * Returns the content as a nodejs Buffer.
170        * @return {Buffer} the content as a Buffer.
171        */
172       asNodeBuffer : function () {
173          var result = getBinaryData(this);
174          return JSZip.utils.transformTo("nodebuffer", result);
175       },
176       /**
177        * Returns the content as an Uint8Array.
178        * @return {Uint8Array} the content as an Uint8Array.
179        */
180       asUint8Array : function () {
181          var result = getBinaryData(this);
182          return JSZip.utils.transformTo("uint8array", result);
183       },
184       /**
185        * Returns the content as an ArrayBuffer.
186        * @return {ArrayBuffer} the content as an ArrayBufer.
187        */
188       asArrayBuffer : function () {
189          return this.asUint8Array().buffer;
190       }
191    };
192
193    /**
194     * Transform an integer into a string in hexadecimal.
195     * @private
196     * @param {number} dec the number to convert.
197     * @param {number} bytes the number of bytes to generate.
198     * @returns {string} the result.
199     */
200    var decToHex = function(dec, bytes) {
201       var hex = "", i;
202       for(i = 0; i < bytes; i++) {
203          hex += String.fromCharCode(dec&0xff);
204          dec=dec>>>8;
205       }
206       return hex;
207    };
208
209    /**
210     * Merge the objects passed as parameters into a new one.
211     * @private
212     * @param {...Object} var_args All objects to merge.
213     * @return {Object} a new object with the data of the others.
214     */
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];
221             }
222          }
223       }
224       return result;
225    };
226
227    /**
228     * Transforms the (incomplete) options from the user into the complete
229     * set of options to create a file.
230     * @private
231     * @param {Object} o the options from the user.
232     * @return {Object} the complete set of options.
233     */
234    var prepareFileAttrs = function (o) {
235       o = o || {};
236       if (o.base64 === true && o.binary == null) {
237          o.binary = true;
238       }
239       o = extend(o, JSZip.defaults);
240       o.date = o.date || new Date();
241       if (o.compression !== null) o.compression = o.compression.toUpperCase();
242
243       return o;
244    };
245
246    /**
247     * Add a file in the current folder.
248     * @private
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.
253     */
254    var fileAdd = function (name, data, o) {
255       // be sure sub folders exist
256       var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
257       if (parent) {
258          folderAdd.call(this, parent);
259       }
260
261       o = prepareFileAttrs(o);
262
263       if (o.dir || data === null || typeof data === "undefined") {
264          o.base64 = false;
265          o.binary = false;
266          data = null;
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);
274             }
275          }
276       } else { // arraybuffer, uint8array, ...
277          o.base64 = false;
278          o.binary = true;
279
280          if (!dataType && !(data instanceof JSZip.CompressedObject)) {
281             throw new Error("The data of '" + name + "' is in an unsupported format !");
282          }
283
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);
287          }
288       }
289
290       return this.files[name] = new ZipObject(name, data, o);
291    };
292
293
294    /**
295     * Find the parent folder of the path.
296     * @private
297     * @param {string} path the path to use
298     * @return {string} the parent folder, or ""
299     */
300    var parentFolder = function (path) {
301       if (path.slice(-1) == '/') {
302          path = path.substring(0, path.length - 1);
303       }
304       var lastSlash = path.lastIndexOf('/');
305       return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
306    };
307
308    /**
309     * Add a (sub) folder in the current folder.
310     * @private
311     * @param {string} name the folder's name
312     * @return {Object} the new folder.
313     */
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)
318       }
319
320       // Does this folder already exist?
321       if (!this.files[name]) {
322          fileAdd.call(this, name, null, {dir:true});
323       }
324       return this.files[name];
325    };
326
327    /**
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.
332     */
333    var generateCompressedObjectFrom = function (file, compression) {
334       var result = new JSZip.CompressedObject(), content;
335
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;
340
341          if (result.uncompressedSize === 0 || file.options.dir) {
342             compression = JSZip.compressions['STORE'];
343             result.compressedContent = "";
344             result.crc32 = 0;
345          } else if (file._data.compressionMethod === compression.magic) {
346             result.compressedContent = file._data.getCompressedContent();
347          } else {
348             content = file._data.getContent()
349             // need to decompress / recompress
350             result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
351          }
352       } else {
353          // have uncompressed data
354          content = getBinaryData(file);
355          if (!content || content.length === 0 || file.options.dir) {
356             compression = JSZip.compressions['STORE'];
357             content = "";
358          }
359          result.uncompressedSize = content.length;
360          result.crc32 = this.crc32(content);
361          result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
362       }
363
364       result.compressedSize = result.compressedContent.length;
365       result.compressionMethod = compression.magic;
366
367       return result;
368    };
369
370    /**
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.
377     */
378    var generateZipParts = function(name, file, compressedObject, offset) {
379       var data = compressedObject.compressedContent,
380           utfEncodedFileName = this.utf8encode(file.name),
381           useUTF8 = utfEncodedFileName !== file.name,
382           o       = file.options,
383           dosTime,
384           dosDate;
385
386       // date
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
390
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;
396
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();
402
403
404       var header = "";
405
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);
417       // crc-32
418       header += decToHex(compressedObject.crc32, 4);
419       // compressed size
420       header += decToHex(compressedObject.compressedSize, 4);
421       // uncompressed size
422       header += decToHex(compressedObject.uncompressedSize, 4);
423       // file name length
424       header += decToHex(utfEncodedFileName.length, 2);
425       // extra field length
426       header += "\x00\x00";
427
428
429       var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
430
431       var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
432       // version made by (00: DOS)
433       "\x14\x00" +
434       // file header (common to file and central directory)
435       header +
436       // file comment length
437       "\x00\x00" +
438       // disk number start
439       "\x00\x00" +
440       // internal file attributes TODO
441       "\x00\x00" +
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) +
446       // file name
447       utfEncodedFileName;
448
449
450       return {
451          fileRecord : fileRecord,
452          dirRecord : dirRecord,
453          compressedObject : compressedObject
454       };
455    };
456
457    /**
458     * An object to write any content to a string.
459     * @constructor
460     */
461    var StringWriter = function () {
462       this.data = [];
463    };
464    StringWriter.prototype = {
465       /**
466        * Append any content to the current string.
467        * @param {Object} input the content to add.
468        */
469       append : function (input) {
470          input = JSZip.utils.transformTo("string", input);
471          this.data.push(input);
472       },
473       /**
474        * Finalize the construction an return the result.
475        * @return {string} the generated string.
476        */
477       finalize : function () {
478          return this.data.join("");
479       }
480    };
481    /**
482     * An object to write any content to an Uint8Array.
483     * @constructor
484     * @param {number} length The length of the array.
485     */
486    var Uint8ArrayWriter = function (length) {
487       this.data = new Uint8Array(length);
488       this.index = 0;
489    };
490    Uint8ArrayWriter.prototype = {
491       /**
492        * Append any content to the current array.
493        * @param {Object} input the content to add.
494        */
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;
501          }
502       },
503       /**
504        * Finalize the construction an return the result.
505        * @return {Uint8Array} the generated array.
506        */
507       finalize : function () {
508          return this.data;
509       }
510    };
511
512    // return the actual prototype of JSZip
513    return {
514       /**
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
521        */
522       load : function (stream, options) {
523          throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
524       },
525
526       /**
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.
532        */
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);
544             }
545          }
546          return result;
547       },
548
549       /**
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).
557        */
558       file : function(name, data, o) {
559          if (arguments.length === 1) {
560             if (name instanceof RegExp) {
561                var regexp = name;
562                return this.filter(function(relativePath, file) {
563                   return !file.options.dir && regexp.test(relativePath);
564                });
565             } else { // text
566                return this.filter(function (relativePath, file) {
567                   return !file.options.dir && relativePath === name;
568                })[0]||null;
569             }
570          } else { // more than one argument : we have data !
571             name = this.root+name;
572             fileAdd.call(this, name, data, o);
573          }
574          return this;
575       },
576
577       /**
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.
581        */
582       folder : function(arg) {
583          if (!arg) {
584             return this;
585          }
586
587          if (arg instanceof RegExp) {
588             return this.filter(function(relativePath, file) {
589                return file.options.dir && arg.test(relativePath);
590             });
591          }
592
593          // else, name is a new folder
594          var name = this.root + arg;
595          var newFolder = folderAdd.call(this, name);
596
597          // Allow chaining by returning a new object with this folder as the root
598          var ret = this.clone();
599          ret.root = newFolder.name;
600          return ret;
601       },
602
603       /**
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
607        */
608       remove : function(name) {
609          name = this.root + name;
610          var file = this.files[name];
611          if (!file) {
612             // Look for any folders
613             if (name.slice(-1) != "/") {
614                name += "/";
615             }
616             file = this.files[name];
617          }
618
619          if (file) {
620             if (!file.options.dir) {
621                // file
622                delete this.files[name];
623             } else {
624                // folder
625                var kids = this.filter(function (relativePath, file) {
626                   return file.name.slice(0, name.length) === name;
627                });
628                for (var i = 0; i < kids.length; i++) {
629                   delete this.files[kids[i].name];
630                }
631             }
632          }
633
634          return this;
635       },
636
637       /**
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
644        */
645       generate : function(options) {
646          options = extend(options || {}, {
647             base64 : true,
648             compression : "STORE",
649             type : "base64"
650          });
651
652          JSZip.utils.checkSupport(options.type);
653
654          var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
655
656
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];
661
662             var compressionName = file.compression || options.compression.toUpperCase();
663             var compression = JSZip.compressions[compressionName];
664             if (!compression) {
665                throw new Error(compressionName + " is not a valid compression method !");
666             }
667
668             var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
669
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);
674          }
675
676          var dirEnd = "";
677
678          // end of central dir signature
679          dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
680          // number of this disk
681          "\x00\x00" +
682          // number of the disk with the start of the central directory
683          "\x00\x00" +
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
693          "\x00\x00";
694
695
696          // we have all the parts (and the total length)
697          // time to create a writer !
698          switch(options.type.toLowerCase()) {
699             case "uint8array" :
700             case "arraybuffer" :
701             case "blob" :
702             case "nodebuffer" :
703                writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
704                break;
705             case "base64" :
706             default : // case "string" :
707                writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
708                break;
709          }
710
711          for (i = 0; i < zipData.length; i++) {
712             writer.append(zipData[i].fileRecord);
713             writer.append(zipData[i].compressedObject.compressedContent);
714          }
715          for (i = 0; i < zipData.length; i++) {
716             writer.append(zipData[i].dirRecord);
717          }
718
719          writer.append(dirEnd);
720
721          var zip = writer.finalize();
722
723
724
725          switch(options.type.toLowerCase()) {
726             // case "zip is an Uint8Array"
727             case "uint8array" :
728             case "arraybuffer" :
729             case "nodebuffer" :
730                return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
731             case "blob" :
732                return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
733
734             // case "zip is a string"
735             case "base64" :
736                return (options.base64) ? JSZip.base64.encode(zip) : zip;
737             default : // case "string" :
738                return zip;
739          }
740       },
741
742       /**
743        *
744        *  Javascript crc32
745        *  http://www.webtoolkit.info/
746        *
747        */
748       crc32 : function crc32(input, crc) {
749          if (typeof input === "undefined" || !input.length) {
750             return 0;
751          }
752
753          var isArray = JSZip.utils.getTypeOf(input) !== "string";
754
755          var table = [
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
820          ];
821
822          if (typeof(crc) == "undefined") { crc = 0; }
823          var x = 0;
824          var y = 0;
825          var byte = 0;
826
827          crc = crc ^ (-1);
828          for( var i = 0, iTop = input.length; i < iTop; i++ ) {
829             byte = isArray ? input[i] : input.charCodeAt(i);
830             y = ( crc ^ byte ) & 0xFF;
831             x = table[y];
832             crc = ( crc >>> 8 ) ^ x;
833          }
834
835          return crc ^ (-1);
836       },
837
838       // Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
839       clone : function() {
840          var newObj = new JSZip();
841          for (var i in this) {
842             if (typeof this[i] !== "function") {
843                newObj[i] = this[i];
844             }
845          }
846          return newObj;
847       },
848
849
850       /**
851        * http://www.webtoolkit.info/javascript-utf8.html
852        */
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);
860          }
861          if (JSZip.support.nodebuffer) {
862             return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
863          }
864
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;
868
869          for (var n = 0; n < string.length; n++) {
870
871             var c = string.charCodeAt(n);
872
873             if (c < 128) {
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);
878             } else {
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);
882             }
883
884          }
885
886          return result.join("");
887       },
888
889       /**
890        * http://www.webtoolkit.info/javascript-utf8.html
891        */
892       utf8decode : function (input) {
893          var result = [], resIndex = 0;
894          var type = JSZip.utils.getTypeOf(input);
895          var isArray = type !== "string";
896          var i = 0;
897          var c = 0, c1 = 0, c2 = 0, c3 = 0;
898
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)
904             );
905          }
906          if (JSZip.support.nodebuffer) {
907             return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
908          }
909
910          while ( i < input.length ) {
911
912             c = isArray ? input[i] : input.charCodeAt(i);
913
914             if (c < 128) {
915                result[resIndex++] = String.fromCharCode(c);
916                i++;
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));
920                i += 2;
921             } else {
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));
925                i += 3;
926             }
927
928          }
929
930          return result.join("");
931       }
932    };
933 }());
934
935 /*
936  * Compression methods
937  * This object is filled in as follow :
938  * name : {
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.
944  * }
945  *
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.
948  */
949 JSZip.compressions = {
950    "STORE" : {
951       magic : "\x00\x00",
952       compress : function (content) {
953          return content; // no compression
954       },
955       uncompress : function (content) {
956          return content; // no compression
957       },
958       compressInputType : null,
959       uncompressInputType : null
960    }
961 };
962
963 /*
964  * List features that require a modern browser, and if the current browser support them.
965  */
966 JSZip.support = {
967    // contains true if JSZip can read/generate ArrayBuffer, false otherwise.
968    arraybuffer : (function(){
969       return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
970    })(),
971    // contains true if JSZip can read/generate nodejs Buffer, false otherwise.
972    nodebuffer : (function(){
973       return typeof Buffer !== "undefined";
974    })(),
975    // contains true if JSZip can read/generate Uint8Array, false otherwise.
976    uint8array : (function(){
977       return typeof Uint8Array !== "undefined";
978    })(),
979    // contains true if JSZip can read/generate Blob, false otherwise.
980    blob : (function(){
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") {
989          return false;
990       }
991       var buffer = new ArrayBuffer(0);
992       try {
993          return new Blob([buffer], { type: "application/zip" }).size === 0;
994       }
995       catch(e) {}
996
997       try {
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;
1002       }
1003       catch(e) {}
1004
1005       return false;
1006    })()
1007 };
1008
1009 (function () {
1010    JSZip.utils = {
1011       /**
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.
1015        */
1016       string2binary : function (str) {
1017          var result = "";
1018          for (var i = 0; i < str.length; i++) {
1019             result += String.fromCharCode(str.charCodeAt(i) & 0xff);
1020          }
1021          return result;
1022       },
1023       /**
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.
1029        */
1030       string2Uint8Array : function (str) {
1031          return JSZip.utils.transformTo("uint8array", str);
1032       },
1033
1034       /**
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.
1040        */
1041       uint8Array2String : function (array) {
1042          return JSZip.utils.transformTo("string", array);
1043       },
1044       /**
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.
1049        */
1050       arrayBuffer2Blob : function (buffer) {
1051          JSZip.utils.checkSupport("blob");
1052
1053          try {
1054             // Blob constructor
1055             return new Blob([buffer], { type: "application/zip" });
1056          }
1057          catch(e) {}
1058
1059          try {
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');
1065          }
1066          catch(e) {}
1067
1068          // well, fuck ?!
1069          throw new Error("Bug : can't construct the Blob.");
1070       },
1071       /**
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.
1076        */
1077       string2Blob : function (str) {
1078          var buffer = JSZip.utils.transformTo("arraybuffer", str);
1079          return JSZip.utils.arrayBuffer2Blob(buffer);
1080       }
1081    };
1082
1083    /**
1084     * The identity function.
1085     * @param {Object} input the input.
1086     * @return {Object} the same input.
1087     */
1088    function identity(input) {
1089       return input;
1090    };
1091
1092    /**
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.
1097     */
1098    function stringToArrayLike(str, array) {
1099       for (var i = 0; i < str.length; ++i) {
1100          array[i] = str.charCodeAt(i) & 0xFF;
1101       }
1102       return array;
1103    };
1104
1105    /**
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.
1109     */
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 !).
1116       //
1117       // result += String.fromCharCode(array[i]); generate too many strings !
1118       //
1119       // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
1120       var chunk = 65536;
1121       var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
1122
1123       while (k < len && chunk > 1) {
1124          try {
1125             if (type === "array" || type === "nodebuffer") {
1126                result.push(String.fromCharCode.apply(null, array.slice(k, Math.max(k + chunk, len))));
1127             } else {
1128                result.push(String.fromCharCode.apply(null, array.subarray(k, k + chunk)));
1129             }
1130             k += chunk;
1131          } catch (e) {
1132             chunk = Math.floor(chunk / 2);
1133          }
1134       }
1135       return result.join("");
1136    };
1137
1138    /**
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.
1143     */
1144    function arrayLikeToArrayLike(arrayFrom, arrayTo) {
1145       for(var i = 0; i < arrayFrom.length; i++) {
1146          arrayTo[i] = arrayFrom[i];
1147       }
1148       return arrayTo;
1149    };
1150
1151    // a matrix containing functions to transform everything into everything.
1152    var transform = {};
1153
1154    // string to ?
1155    transform["string"] = {
1156       "string" : identity,
1157       "array" : function (input) {
1158          return stringToArrayLike(input, new Array(input.length));
1159       },
1160       "arraybuffer" : function (input) {
1161          return transform["string"]["uint8array"](input).buffer;
1162       },
1163       "uint8array" : function (input) {
1164          return stringToArrayLike(input, new Uint8Array(input.length));
1165       },
1166       "nodebuffer" : function (input) {
1167          return stringToArrayLike(input, new Buffer(input.length));
1168       }
1169    };
1170
1171    // array to ?
1172    transform["array"] = {
1173       "string" : arrayLikeToString,
1174       "array" : identity,
1175       "arraybuffer" : function (input) {
1176          return (new Uint8Array(input)).buffer;
1177       },
1178       "uint8array" : function (input) {
1179          return new Uint8Array(input);
1180       },
1181       "nodebuffer" : function (input) {
1182          return new Buffer(input);
1183       }
1184    };
1185
1186    // arraybuffer to ?
1187    transform["arraybuffer"] = {
1188       "string" : function (input) {
1189          return arrayLikeToString(new Uint8Array(input));
1190       },
1191       "array" : function (input) {
1192          return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
1193       },
1194       "arraybuffer" : identity,
1195       "uint8array" : function (input) {
1196          return new Uint8Array(input);
1197       },
1198       "nodebuffer" : function (input) {
1199          return new Buffer(new Uint8Array(input));
1200       }
1201    };
1202
1203    // uint8array to ?
1204    transform["uint8array"] = {
1205       "string" : arrayLikeToString,
1206       "array" : function (input) {
1207          return arrayLikeToArrayLike(input, new Array(input.length));
1208       },
1209       "arraybuffer" : function (input) {
1210          return input.buffer;
1211       },
1212       "uint8array" : identity,
1213       "nodebuffer" : function(input) {
1214          return new Buffer(input);
1215       }
1216    };
1217
1218    // nodebuffer to ?
1219    transform["nodebuffer"] = {
1220       "string" : arrayLikeToString,
1221       "array" : function (input) {
1222          return arrayLikeToArrayLike(input, new Array(input.length));
1223       },
1224       "arraybuffer" : function (input) {
1225          return transform["nodebuffer"]["uint8array"](input).buffer;
1226       },
1227       "uint8array" : function (input) {
1228          return arrayLikeToArrayLike(input, new Uint8Array(input.length));
1229       },
1230       "nodebuffer" : identity
1231    };
1232
1233    /**
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.
1240     */
1241    JSZip.utils.transformTo = function (outputType, input) {
1242       if (!input) {
1243          // undefined, null, etc
1244          // an empty string won't harm.
1245          input = "";
1246       }
1247       if (!outputType) {
1248          return input;
1249       }
1250       JSZip.utils.checkSupport(outputType);
1251       var inputType = JSZip.utils.getTypeOf(input);
1252       var result = transform[inputType][outputType](input);
1253       return result;
1254    };
1255
1256    /**
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.
1261     */
1262    JSZip.utils.getTypeOf = function (input) {
1263       if (typeof input === "string") {
1264          return "string";
1265       }
1266       if (input instanceof Array) {
1267          return "array";
1268       }
1269       if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
1270          return "nodebuffer";
1271       }
1272       if (JSZip.support.uint8array && input instanceof Uint8Array) {
1273          return "uint8array";
1274       }
1275       if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
1276          return "arraybuffer";
1277       }
1278    };
1279
1280    /**
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.
1284     */
1285    JSZip.utils.checkSupport = function (type) {
1286       var supported = true;
1287       switch (type.toLowerCase()) {
1288          case "uint8array":
1289             supported = JSZip.support.uint8array;
1290          break;
1291          case "arraybuffer":
1292             supported = JSZip.support.arraybuffer;
1293          break;
1294          case "nodebuffer":
1295             supported = JSZip.support.nodebuffer;
1296          break;
1297          case "blob":
1298             supported = JSZip.support.blob;
1299          break;
1300       }
1301       if (!supported) {
1302          throw new Error(type + " is not supported by this browser");
1303       }
1304    };
1305
1306
1307 })();
1308
1309 (function (){
1310    /**
1311     * Represents an entry in the zip.
1312     * The content may or may not be compressed.
1313     * @constructor
1314     */
1315    JSZip.CompressedObject = function () {
1316          this.compressedSize = 0;
1317          this.uncompressedSize = 0;
1318          this.crc32 = 0;
1319          this.compressionMethod = null;
1320          this.compressedContent = null;
1321    };
1322
1323    JSZip.CompressedObject.prototype = {
1324       /**
1325        * Return the decompressed content in an unspecified format.
1326        * The format will depend on the decompressor.
1327        * @return {Object} the decompressed content.
1328        */
1329       getContent : function () {
1330          return null; // see implementation
1331       },
1332       /**
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.
1336        */
1337       getCompressedContent : function () {
1338          return null; // see implementation
1339       }
1340    };
1341 })();
1342
1343 /**
1344  *
1345  *  Base64 encode / decode
1346  *  http://www.webtoolkit.info/
1347  *
1348  *  Hacked so that it doesn't utf8 en/decode everything
1349  **/
1350 JSZip.base64 = (function() {
1351    // private property
1352    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1353
1354    return {
1355       // public method for encoding
1356       encode : function(input, utf8) {
1357          var output = "";
1358          var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
1359          var i = 0;
1360
1361          while (i < input.length) {
1362
1363             chr1 = input.charCodeAt(i++);
1364             chr2 = input.charCodeAt(i++);
1365             chr3 = input.charCodeAt(i++);
1366
1367             enc1 = chr1 >> 2;
1368             enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
1369             enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
1370             enc4 = chr3 & 63;
1371
1372             if (isNaN(chr2)) {
1373                enc3 = enc4 = 64;
1374             } else if (isNaN(chr3)) {
1375                enc4 = 64;
1376             }
1377
1378             output = output +
1379                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
1380                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
1381
1382          }
1383
1384          return output;
1385       },
1386
1387       // public method for decoding
1388       decode : function(input, utf8) {
1389          var output = "";
1390          var chr1, chr2, chr3;
1391          var enc1, enc2, enc3, enc4;
1392          var i = 0;
1393
1394          input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
1395
1396          while (i < input.length) {
1397
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++));
1402
1403             chr1 = (enc1 << 2) | (enc2 >> 4);
1404             chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
1405             chr3 = ((enc3 & 3) << 6) | enc4;
1406
1407             output = output + String.fromCharCode(chr1);
1408
1409             if (enc3 != 64) {
1410                output = output + String.fromCharCode(chr2);
1411             }
1412             if (enc4 != 64) {
1413                output = output + String.fromCharCode(chr3);
1414             }
1415
1416          }
1417
1418          return output;
1419
1420       }
1421    };
1422 }());
1423
1424 // enforcing Stuk's coding style
1425 // vim: set shiftwidth=3 softtabstop=3: