zip-js 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,966 @@
1
+ /*
2
+ Copyright (c) 2013 Gildas Lormeau. All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in
12
+ the documentation and/or other materials provided with the distribution.
13
+
14
+ 3. The names of the authors may not be used to endorse or promote products
15
+ derived from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
18
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
19
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
20
+ INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
21
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
23
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
29
+ (function(obj) {
30
+ "use strict";
31
+
32
+ var ERR_BAD_FORMAT = "File format is not recognized.";
33
+ var ERR_CRC = "CRC failed.";
34
+ var ERR_ENCRYPTED = "File contains encrypted entry.";
35
+ var ERR_ZIP64 = "File is using Zip64 (4gb+ file size).";
36
+ var ERR_READ = "Error while reading zip file.";
37
+ var ERR_WRITE = "Error while writing zip file.";
38
+ var ERR_WRITE_DATA = "Error while writing file data.";
39
+ var ERR_READ_DATA = "Error while reading file data.";
40
+ var ERR_DUPLICATED_NAME = "File already exists.";
41
+ var CHUNK_SIZE = 512 * 1024;
42
+
43
+ var TEXT_PLAIN = "text/plain";
44
+
45
+ var appendABViewSupported;
46
+ try {
47
+ appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0;
48
+ } catch (e) {
49
+ }
50
+
51
+ function Crc32() {
52
+ this.crc = -1;
53
+ }
54
+ Crc32.prototype.append = function append(data) {
55
+ var crc = this.crc | 0, table = this.table;
56
+ for (var offset = 0, len = data.length | 0; offset < len; offset++)
57
+ crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
58
+ this.crc = crc;
59
+ };
60
+ Crc32.prototype.get = function get() {
61
+ return ~this.crc;
62
+ };
63
+ Crc32.prototype.table = (function() {
64
+ var i, j, t, table = []; // Uint32Array is actually slower than []
65
+ for (i = 0; i < 256; i++) {
66
+ t = i;
67
+ for (j = 0; j < 8; j++)
68
+ if (t & 1)
69
+ t = (t >>> 1) ^ 0xEDB88320;
70
+ else
71
+ t = t >>> 1;
72
+ table[i] = t;
73
+ }
74
+ return table;
75
+ })();
76
+
77
+ // "no-op" codec
78
+ function NOOP() {}
79
+ NOOP.prototype.append = function append(bytes, onprogress) {
80
+ return bytes;
81
+ };
82
+ NOOP.prototype.flush = function flush() {};
83
+
84
+ function blobSlice(blob, index, length) {
85
+ if (index < 0 || length < 0 || index + length > blob.size)
86
+ throw new RangeError('offset:' + index + ', length:' + length + ', size:' + blob.size);
87
+ if (blob.slice)
88
+ return blob.slice(index, index + length);
89
+ else if (blob.webkitSlice)
90
+ return blob.webkitSlice(index, index + length);
91
+ else if (blob.mozSlice)
92
+ return blob.mozSlice(index, index + length);
93
+ else if (blob.msSlice)
94
+ return blob.msSlice(index, index + length);
95
+ }
96
+
97
+ function getDataHelper(byteLength, bytes) {
98
+ var dataBuffer, dataArray;
99
+ dataBuffer = new ArrayBuffer(byteLength);
100
+ dataArray = new Uint8Array(dataBuffer);
101
+ if (bytes)
102
+ dataArray.set(bytes, 0);
103
+ return {
104
+ buffer : dataBuffer,
105
+ array : dataArray,
106
+ view : new DataView(dataBuffer)
107
+ };
108
+ }
109
+
110
+ // Readers
111
+ function Reader() {
112
+ }
113
+
114
+ function TextReader(text) {
115
+ var that = this, blobReader;
116
+
117
+ function init(callback, onerror) {
118
+ var blob = new Blob([ text ], {
119
+ type : TEXT_PLAIN
120
+ });
121
+ blobReader = new BlobReader(blob);
122
+ blobReader.init(function() {
123
+ that.size = blobReader.size;
124
+ callback();
125
+ }, onerror);
126
+ }
127
+
128
+ function readUint8Array(index, length, callback, onerror) {
129
+ blobReader.readUint8Array(index, length, callback, onerror);
130
+ }
131
+
132
+ that.size = 0;
133
+ that.init = init;
134
+ that.readUint8Array = readUint8Array;
135
+ }
136
+ TextReader.prototype = new Reader();
137
+ TextReader.prototype.constructor = TextReader;
138
+
139
+ function Data64URIReader(dataURI) {
140
+ var that = this, dataStart;
141
+
142
+ function init(callback) {
143
+ var dataEnd = dataURI.length;
144
+ while (dataURI.charAt(dataEnd - 1) == "=")
145
+ dataEnd--;
146
+ dataStart = dataURI.indexOf(",") + 1;
147
+ that.size = Math.floor((dataEnd - dataStart) * 0.75);
148
+ callback();
149
+ }
150
+
151
+ function readUint8Array(index, length, callback) {
152
+ var i, data = getDataHelper(length);
153
+ var start = Math.floor(index / 3) * 4;
154
+ var end = Math.ceil((index + length) / 3) * 4;
155
+ var bytes = obj.atob(dataURI.substring(start + dataStart, end + dataStart));
156
+ var delta = index - Math.floor(start / 4) * 3;
157
+ for (i = delta; i < delta + length; i++)
158
+ data.array[i - delta] = bytes.charCodeAt(i);
159
+ callback(data.array);
160
+ }
161
+
162
+ that.size = 0;
163
+ that.init = init;
164
+ that.readUint8Array = readUint8Array;
165
+ }
166
+ Data64URIReader.prototype = new Reader();
167
+ Data64URIReader.prototype.constructor = Data64URIReader;
168
+
169
+ function BlobReader(blob) {
170
+ var that = this;
171
+
172
+ function init(callback) {
173
+ that.size = blob.size;
174
+ callback();
175
+ }
176
+
177
+ function readUint8Array(index, length, callback, onerror) {
178
+ var reader = new FileReader();
179
+ reader.onload = function(e) {
180
+ callback(new Uint8Array(e.target.result));
181
+ };
182
+ reader.onerror = onerror;
183
+ try {
184
+ reader.readAsArrayBuffer(blobSlice(blob, index, length));
185
+ } catch (e) {
186
+ onerror(e);
187
+ }
188
+ }
189
+
190
+ that.size = 0;
191
+ that.init = init;
192
+ that.readUint8Array = readUint8Array;
193
+ }
194
+ BlobReader.prototype = new Reader();
195
+ BlobReader.prototype.constructor = BlobReader;
196
+
197
+ // Writers
198
+
199
+ function Writer() {
200
+ }
201
+ Writer.prototype.getData = function(callback) {
202
+ callback(this.data);
203
+ };
204
+
205
+ function TextWriter(encoding) {
206
+ var that = this, blob;
207
+
208
+ function init(callback) {
209
+ blob = new Blob([], {
210
+ type : TEXT_PLAIN
211
+ });
212
+ callback();
213
+ }
214
+
215
+ function writeUint8Array(array, callback) {
216
+ blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
217
+ type : TEXT_PLAIN
218
+ });
219
+ callback();
220
+ }
221
+
222
+ function getData(callback, onerror) {
223
+ var reader = new FileReader();
224
+ reader.onload = function(e) {
225
+ callback(e.target.result);
226
+ };
227
+ reader.onerror = onerror;
228
+ reader.readAsText(blob, encoding);
229
+ }
230
+
231
+ that.init = init;
232
+ that.writeUint8Array = writeUint8Array;
233
+ that.getData = getData;
234
+ }
235
+ TextWriter.prototype = new Writer();
236
+ TextWriter.prototype.constructor = TextWriter;
237
+
238
+ function Data64URIWriter(contentType) {
239
+ var that = this, data = "", pending = "";
240
+
241
+ function init(callback) {
242
+ data += "data:" + (contentType || "") + ";base64,";
243
+ callback();
244
+ }
245
+
246
+ function writeUint8Array(array, callback) {
247
+ var i, delta = pending.length, dataString = pending;
248
+ pending = "";
249
+ for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++)
250
+ dataString += String.fromCharCode(array[i]);
251
+ for (; i < array.length; i++)
252
+ pending += String.fromCharCode(array[i]);
253
+ if (dataString.length > 2)
254
+ data += obj.btoa(dataString);
255
+ else
256
+ pending = dataString;
257
+ callback();
258
+ }
259
+
260
+ function getData(callback) {
261
+ callback(data + obj.btoa(pending));
262
+ }
263
+
264
+ that.init = init;
265
+ that.writeUint8Array = writeUint8Array;
266
+ that.getData = getData;
267
+ }
268
+ Data64URIWriter.prototype = new Writer();
269
+ Data64URIWriter.prototype.constructor = Data64URIWriter;
270
+
271
+ function BlobWriter(contentType) {
272
+ var blob, that = this;
273
+
274
+ function init(callback) {
275
+ blob = new Blob([], {
276
+ type : contentType
277
+ });
278
+ callback();
279
+ }
280
+
281
+ function writeUint8Array(array, callback) {
282
+ blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
283
+ type : contentType
284
+ });
285
+ callback();
286
+ }
287
+
288
+ function getData(callback) {
289
+ callback(blob);
290
+ }
291
+
292
+ that.init = init;
293
+ that.writeUint8Array = writeUint8Array;
294
+ that.getData = getData;
295
+ }
296
+ BlobWriter.prototype = new Writer();
297
+ BlobWriter.prototype.constructor = BlobWriter;
298
+
299
+ /**
300
+ * inflate/deflate core functions
301
+ * @param worker {Worker} web worker for the task.
302
+ * @param initialMessage {Object} initial message to be sent to the worker. should contain
303
+ * sn(serial number for distinguishing multiple tasks sent to the worker), and codecClass.
304
+ * This function may add more properties before sending.
305
+ */
306
+ function launchWorkerProcess(worker, initialMessage, reader, writer, offset, size, onprogress, onend, onreaderror, onwriteerror) {
307
+ var chunkIndex = 0, index, outputSize, sn = initialMessage.sn, crc;
308
+
309
+ function onflush() {
310
+ worker.removeEventListener('message', onmessage, false);
311
+ onend(outputSize, crc);
312
+ }
313
+
314
+ function onmessage(event) {
315
+ var message = event.data, data = message.data, err = message.error;
316
+ if (err) {
317
+ err.toString = function () { return 'Error: ' + this.message; };
318
+ onreaderror(err);
319
+ return;
320
+ }
321
+ if (message.sn !== sn)
322
+ return;
323
+ if (typeof message.codecTime === 'number')
324
+ worker.codecTime += message.codecTime; // should be before onflush()
325
+ if (typeof message.crcTime === 'number')
326
+ worker.crcTime += message.crcTime;
327
+
328
+ switch (message.type) {
329
+ case 'append':
330
+ if (data) {
331
+ outputSize += data.length;
332
+ writer.writeUint8Array(data, function() {
333
+ step();
334
+ }, onwriteerror);
335
+ } else
336
+ step();
337
+ break;
338
+ case 'flush':
339
+ crc = message.crc;
340
+ if (data) {
341
+ outputSize += data.length;
342
+ writer.writeUint8Array(data, function() {
343
+ onflush();
344
+ }, onwriteerror);
345
+ } else
346
+ onflush();
347
+ break;
348
+ case 'progress':
349
+ if (onprogress)
350
+ onprogress(index + message.loaded, size);
351
+ break;
352
+ case 'importScripts': //no need to handle here
353
+ case 'newTask':
354
+ case 'echo':
355
+ break;
356
+ default:
357
+ console.warn('zip.js:launchWorkerProcess: unknown message: ', message);
358
+ }
359
+ }
360
+
361
+ function step() {
362
+ index = chunkIndex * CHUNK_SIZE;
363
+ // use `<=` instead of `<`, because `size` may be 0.
364
+ if (index <= size) {
365
+ reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
366
+ if (onprogress)
367
+ onprogress(index, size);
368
+ var msg = index === 0 ? initialMessage : {sn : sn};
369
+ msg.type = 'append';
370
+ msg.data = array;
371
+
372
+ // posting a message with transferables will fail on IE10
373
+ try {
374
+ worker.postMessage(msg, [array.buffer]);
375
+ } catch(ex) {
376
+ worker.postMessage(msg); // retry without transferables
377
+ }
378
+ chunkIndex++;
379
+ }, onreaderror);
380
+ } else {
381
+ worker.postMessage({
382
+ sn: sn,
383
+ type: 'flush'
384
+ });
385
+ }
386
+ }
387
+
388
+ outputSize = 0;
389
+ worker.addEventListener('message', onmessage, false);
390
+ step();
391
+ }
392
+
393
+ function launchProcess(process, reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror) {
394
+ var chunkIndex = 0, index, outputSize = 0,
395
+ crcInput = crcType === 'input',
396
+ crcOutput = crcType === 'output',
397
+ crc = new Crc32();
398
+ function step() {
399
+ var outputData;
400
+ index = chunkIndex * CHUNK_SIZE;
401
+ if (index < size)
402
+ reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) {
403
+ var outputData;
404
+ try {
405
+ outputData = process.append(inputData, function(loaded) {
406
+ if (onprogress)
407
+ onprogress(index + loaded, size);
408
+ });
409
+ } catch (e) {
410
+ onreaderror(e);
411
+ return;
412
+ }
413
+ if (outputData) {
414
+ outputSize += outputData.length;
415
+ writer.writeUint8Array(outputData, function() {
416
+ chunkIndex++;
417
+ setTimeout(step, 1);
418
+ }, onwriteerror);
419
+ if (crcOutput)
420
+ crc.append(outputData);
421
+ } else {
422
+ chunkIndex++;
423
+ setTimeout(step, 1);
424
+ }
425
+ if (crcInput)
426
+ crc.append(inputData);
427
+ if (onprogress)
428
+ onprogress(index, size);
429
+ }, onreaderror);
430
+ else {
431
+ try {
432
+ outputData = process.flush();
433
+ } catch (e) {
434
+ onreaderror(e);
435
+ return;
436
+ }
437
+ if (outputData) {
438
+ if (crcOutput)
439
+ crc.append(outputData);
440
+ outputSize += outputData.length;
441
+ writer.writeUint8Array(outputData, function() {
442
+ onend(outputSize, crc.get());
443
+ }, onwriteerror);
444
+ } else
445
+ onend(outputSize, crc.get());
446
+ }
447
+ }
448
+
449
+ step();
450
+ }
451
+
452
+ function inflate(worker, sn, reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
453
+ var crcType = computeCrc32 ? 'output' : 'none';
454
+ if (obj.zip.useWebWorkers) {
455
+ var initialMessage = {
456
+ sn: sn,
457
+ codecClass: 'Inflater',
458
+ crcType: crcType,
459
+ };
460
+ launchWorkerProcess(worker, initialMessage, reader, writer, offset, size, onprogress, onend, onreaderror, onwriteerror);
461
+ } else
462
+ launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror);
463
+ }
464
+
465
+ function deflate(worker, sn, reader, writer, level, onend, onprogress, onreaderror, onwriteerror) {
466
+ var crcType = 'input';
467
+ if (obj.zip.useWebWorkers) {
468
+ var initialMessage = {
469
+ sn: sn,
470
+ options: {level: level},
471
+ codecClass: 'Deflater',
472
+ crcType: crcType,
473
+ };
474
+ launchWorkerProcess(worker, initialMessage, reader, writer, 0, reader.size, onprogress, onend, onreaderror, onwriteerror);
475
+ } else
476
+ launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, crcType, onprogress, onend, onreaderror, onwriteerror);
477
+ }
478
+
479
+ function copy(worker, sn, reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
480
+ var crcType = 'input';
481
+ if (obj.zip.useWebWorkers && computeCrc32) {
482
+ var initialMessage = {
483
+ sn: sn,
484
+ codecClass: 'NOOP',
485
+ crcType: crcType,
486
+ };
487
+ launchWorkerProcess(worker, initialMessage, reader, writer, offset, size, onprogress, onend, onreaderror, onwriteerror);
488
+ } else
489
+ launchProcess(new NOOP(), reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror);
490
+ }
491
+
492
+ // ZipReader
493
+
494
+ function decodeASCII(str) {
495
+ var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB',
496
+ '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9',
497
+ '\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1',
498
+ '\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6',
499
+ '\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3',
500
+ '\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE',
501
+ '\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE',
502
+ '\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7',
503
+ '\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ];
504
+ for (i = 0; i < str.length; i++) {
505
+ charCode = str.charCodeAt(i) & 0xFF;
506
+ if (charCode > 127)
507
+ out += extendedASCII[charCode - 128];
508
+ else
509
+ out += String.fromCharCode(charCode);
510
+ }
511
+ return out;
512
+ }
513
+
514
+ function decodeUTF8(string) {
515
+ return decodeURIComponent(escape(string));
516
+ }
517
+
518
+ function getString(bytes) {
519
+ var i, str = "";
520
+ for (i = 0; i < bytes.length; i++)
521
+ str += String.fromCharCode(bytes[i]);
522
+ return str;
523
+ }
524
+
525
+ function getDate(timeRaw) {
526
+ var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
527
+ try {
528
+ return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5,
529
+ (time & 0x001F) * 2, 0);
530
+ } catch (e) {
531
+ }
532
+ }
533
+
534
+ function readCommonHeader(entry, data, index, centralDirectory, onerror) {
535
+ entry.version = data.view.getUint16(index, true);
536
+ entry.bitFlag = data.view.getUint16(index + 2, true);
537
+ entry.compressionMethod = data.view.getUint16(index + 4, true);
538
+ entry.lastModDateRaw = data.view.getUint32(index + 6, true);
539
+ entry.lastModDate = getDate(entry.lastModDateRaw);
540
+ if ((entry.bitFlag & 0x01) === 0x01) {
541
+ onerror(ERR_ENCRYPTED);
542
+ return;
543
+ }
544
+ if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) {
545
+ entry.crc32 = data.view.getUint32(index + 10, true);
546
+ entry.compressedSize = data.view.getUint32(index + 14, true);
547
+ entry.uncompressedSize = data.view.getUint32(index + 18, true);
548
+ }
549
+ if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) {
550
+ onerror(ERR_ZIP64);
551
+ return;
552
+ }
553
+ entry.filenameLength = data.view.getUint16(index + 22, true);
554
+ entry.extraFieldLength = data.view.getUint16(index + 24, true);
555
+ }
556
+
557
+ function createZipReader(reader, callback, onerror) {
558
+ var inflateSN = 0;
559
+
560
+ function Entry() {
561
+ }
562
+
563
+ Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) {
564
+ var that = this;
565
+
566
+ function testCrc32(crc32) {
567
+ var dataCrc32 = getDataHelper(4);
568
+ dataCrc32.view.setUint32(0, crc32);
569
+ return that.crc32 == dataCrc32.view.getUint32(0);
570
+ }
571
+
572
+ function getWriterData(uncompressedSize, crc32) {
573
+ if (checkCrc32 && !testCrc32(crc32))
574
+ onerror(ERR_CRC);
575
+ else
576
+ writer.getData(function(data) {
577
+ onend(data);
578
+ });
579
+ }
580
+
581
+ function onreaderror(err) {
582
+ onerror(err || ERR_READ_DATA);
583
+ }
584
+
585
+ function onwriteerror(err) {
586
+ onerror(err || ERR_WRITE_DATA);
587
+ }
588
+
589
+ reader.readUint8Array(that.offset, 30, function(bytes) {
590
+ var data = getDataHelper(bytes.length, bytes), dataOffset;
591
+ if (data.view.getUint32(0) != 0x504b0304) {
592
+ onerror(ERR_BAD_FORMAT);
593
+ return;
594
+ }
595
+ readCommonHeader(that, data, 4, false, onerror);
596
+ dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength;
597
+ writer.init(function() {
598
+ if (that.compressionMethod === 0)
599
+ copy(that._worker, inflateSN++, reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
600
+ else
601
+ inflate(that._worker, inflateSN++, reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
602
+ }, onwriteerror);
603
+ }, onreaderror);
604
+ };
605
+
606
+ function seekEOCDR(eocdrCallback) {
607
+ // "End of central directory record" is the last part of a zip archive, and is at least 22 bytes long.
608
+ // Zip file comment is the last part of EOCDR and has max length of 64KB,
609
+ // so we only have to search the last 64K + 22 bytes of a archive for EOCDR signature (0x06054b50).
610
+ var EOCDR_MIN = 22;
611
+ if (reader.size < EOCDR_MIN) {
612
+ onerror(ERR_BAD_FORMAT);
613
+ return;
614
+ }
615
+ var ZIP_COMMENT_MAX = 256 * 256, EOCDR_MAX = EOCDR_MIN + ZIP_COMMENT_MAX;
616
+
617
+ // In most cases, the EOCDR is EOCDR_MIN bytes long
618
+ doSeek(EOCDR_MIN, function() {
619
+ // If not found, try within EOCDR_MAX bytes
620
+ doSeek(Math.min(EOCDR_MAX, reader.size), function() {
621
+ onerror(ERR_BAD_FORMAT);
622
+ });
623
+ });
624
+
625
+ // seek last length bytes of file for EOCDR
626
+ function doSeek(length, eocdrNotFoundCallback) {
627
+ reader.readUint8Array(reader.size - length, length, function(bytes) {
628
+ for (var i = bytes.length - EOCDR_MIN; i >= 0; i--) {
629
+ if (bytes[i] === 0x50 && bytes[i + 1] === 0x4b && bytes[i + 2] === 0x05 && bytes[i + 3] === 0x06) {
630
+ eocdrCallback(new DataView(bytes.buffer, i, EOCDR_MIN));
631
+ return;
632
+ }
633
+ }
634
+ eocdrNotFoundCallback();
635
+ }, function() {
636
+ onerror(ERR_READ);
637
+ });
638
+ }
639
+ }
640
+
641
+ var zipReader = {
642
+ getEntries : function(callback) {
643
+ var worker = this._worker;
644
+ // look for End of central directory record
645
+ seekEOCDR(function(dataView) {
646
+ var datalength, fileslength;
647
+ datalength = dataView.getUint32(16, true);
648
+ fileslength = dataView.getUint16(8, true);
649
+ if (datalength < 0 || datalength >= reader.size) {
650
+ onerror(ERR_BAD_FORMAT);
651
+ return;
652
+ }
653
+ reader.readUint8Array(datalength, reader.size - datalength, function(bytes) {
654
+ var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes);
655
+ for (i = 0; i < fileslength; i++) {
656
+ entry = new Entry();
657
+ entry._worker = worker;
658
+ if (data.view.getUint32(index) != 0x504b0102) {
659
+ onerror(ERR_BAD_FORMAT);
660
+ return;
661
+ }
662
+ readCommonHeader(entry, data, index + 6, true, onerror);
663
+ entry.commentLength = data.view.getUint16(index + 32, true);
664
+ entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10);
665
+ entry.offset = data.view.getUint32(index + 42, true);
666
+ filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength));
667
+ entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename);
668
+ if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/")
669
+ entry.directory = true;
670
+ comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46
671
+ + entry.filenameLength + entry.extraFieldLength + entry.commentLength));
672
+ entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment);
673
+ entries.push(entry);
674
+ index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength;
675
+ }
676
+ callback(entries);
677
+ }, function() {
678
+ onerror(ERR_READ);
679
+ });
680
+ });
681
+ },
682
+ close : function(callback) {
683
+ if (this._worker) {
684
+ this._worker.terminate();
685
+ this._worker = null;
686
+ }
687
+ if (callback)
688
+ callback();
689
+ },
690
+ _worker: null
691
+ };
692
+
693
+ if (!obj.zip.useWebWorkers)
694
+ callback(zipReader);
695
+ else {
696
+ createWorker('inflater',
697
+ function(worker) {
698
+ zipReader._worker = worker;
699
+ callback(zipReader);
700
+ },
701
+ function(err) {
702
+ onerror(err);
703
+ }
704
+ );
705
+ }
706
+ }
707
+
708
+ // ZipWriter
709
+
710
+ function encodeUTF8(string) {
711
+ return unescape(encodeURIComponent(string));
712
+ }
713
+
714
+ function getBytes(str) {
715
+ var i, array = [];
716
+ for (i = 0; i < str.length; i++)
717
+ array.push(str.charCodeAt(i));
718
+ return array;
719
+ }
720
+
721
+ function createZipWriter(writer, callback, onerror, dontDeflate) {
722
+ var files = {}, filenames = [], datalength = 0;
723
+ var deflateSN = 0;
724
+
725
+ function onwriteerror(err) {
726
+ onerror(err || ERR_WRITE);
727
+ }
728
+
729
+ function onreaderror(err) {
730
+ onerror(err || ERR_READ_DATA);
731
+ }
732
+
733
+ var zipWriter = {
734
+ add : function(name, reader, onend, onprogress, options) {
735
+ var header, filename, date;
736
+ var worker = this._worker;
737
+
738
+ function writeHeader(callback) {
739
+ var data;
740
+ date = options.lastModDate || new Date();
741
+ header = getDataHelper(26);
742
+ files[name] = {
743
+ headerArray : header.array,
744
+ directory : options.directory,
745
+ filename : filename,
746
+ offset : datalength,
747
+ comment : getBytes(encodeUTF8(options.comment || ""))
748
+ };
749
+ header.view.setUint32(0, 0x14000808);
750
+ if (options.version)
751
+ header.view.setUint8(0, options.version);
752
+ if (!dontDeflate && options.level !== 0 && !options.directory)
753
+ header.view.setUint16(4, 0x0800);
754
+ header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
755
+ header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
756
+ header.view.setUint16(22, filename.length, true);
757
+ data = getDataHelper(30 + filename.length);
758
+ data.view.setUint32(0, 0x504b0304);
759
+ data.array.set(header.array, 4);
760
+ data.array.set(filename, 30);
761
+ datalength += data.array.length;
762
+ writer.writeUint8Array(data.array, callback, onwriteerror);
763
+ }
764
+
765
+ function writeFooter(compressedLength, crc32) {
766
+ var footer = getDataHelper(16);
767
+ datalength += compressedLength || 0;
768
+ footer.view.setUint32(0, 0x504b0708);
769
+ if (typeof crc32 != "undefined") {
770
+ header.view.setUint32(10, crc32, true);
771
+ footer.view.setUint32(4, crc32, true);
772
+ }
773
+ if (reader) {
774
+ footer.view.setUint32(8, compressedLength, true);
775
+ header.view.setUint32(14, compressedLength, true);
776
+ footer.view.setUint32(12, reader.size, true);
777
+ header.view.setUint32(18, reader.size, true);
778
+ }
779
+ writer.writeUint8Array(footer.array, function() {
780
+ datalength += 16;
781
+ onend();
782
+ }, onwriteerror);
783
+ }
784
+
785
+ function writeFile() {
786
+ options = options || {};
787
+ name = name.trim();
788
+ if (options.directory && name.charAt(name.length - 1) != "/")
789
+ name += "/";
790
+ if (files.hasOwnProperty(name)) {
791
+ onerror(ERR_DUPLICATED_NAME);
792
+ return;
793
+ }
794
+ filename = getBytes(encodeUTF8(name));
795
+ filenames.push(name);
796
+ writeHeader(function() {
797
+ if (reader)
798
+ if (dontDeflate || options.level === 0)
799
+ copy(worker, deflateSN++, reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
800
+ else
801
+ deflate(worker, deflateSN++, reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror);
802
+ else
803
+ writeFooter();
804
+ }, onwriteerror);
805
+ }
806
+
807
+ if (reader)
808
+ reader.init(writeFile, onreaderror);
809
+ else
810
+ writeFile();
811
+ },
812
+ close : function(callback) {
813
+ if (this._worker) {
814
+ this._worker.terminate();
815
+ this._worker = null;
816
+ }
817
+
818
+ var data, length = 0, index = 0, indexFilename, file;
819
+ for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
820
+ file = files[filenames[indexFilename]];
821
+ length += 46 + file.filename.length + file.comment.length;
822
+ }
823
+ data = getDataHelper(length + 22);
824
+ for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
825
+ file = files[filenames[indexFilename]];
826
+ data.view.setUint32(index, 0x504b0102);
827
+ data.view.setUint16(index + 4, 0x1400);
828
+ data.array.set(file.headerArray, index + 6);
829
+ data.view.setUint16(index + 32, file.comment.length, true);
830
+ if (file.directory)
831
+ data.view.setUint8(index + 38, 0x10);
832
+ data.view.setUint32(index + 42, file.offset, true);
833
+ data.array.set(file.filename, index + 46);
834
+ data.array.set(file.comment, index + 46 + file.filename.length);
835
+ index += 46 + file.filename.length + file.comment.length;
836
+ }
837
+ data.view.setUint32(index, 0x504b0506);
838
+ data.view.setUint16(index + 8, filenames.length, true);
839
+ data.view.setUint16(index + 10, filenames.length, true);
840
+ data.view.setUint32(index + 12, length, true);
841
+ data.view.setUint32(index + 16, datalength, true);
842
+ writer.writeUint8Array(data.array, function() {
843
+ writer.getData(callback);
844
+ }, onwriteerror);
845
+ },
846
+ _worker: null
847
+ };
848
+
849
+ if (!obj.zip.useWebWorkers)
850
+ callback(zipWriter);
851
+ else {
852
+ createWorker('deflater',
853
+ function(worker) {
854
+ zipWriter._worker = worker;
855
+ callback(zipWriter);
856
+ },
857
+ function(err) {
858
+ onerror(err);
859
+ }
860
+ );
861
+ }
862
+ }
863
+
864
+ function resolveURLs(urls) {
865
+ var a = document.createElement('a');
866
+ return urls.map(function(url) {
867
+ a.href = url;
868
+ return a.href;
869
+ });
870
+ }
871
+
872
+ var DEFAULT_WORKER_SCRIPTS = {
873
+ deflater: ['z-worker.js', 'deflate.js'],
874
+ inflater: ['z-worker.js', 'inflate.js']
875
+ };
876
+ function createWorker(type, callback, onerror) {
877
+ if (obj.zip.workerScripts !== null && obj.zip.workerScriptsPath !== null) {
878
+ onerror(new Error('Either zip.workerScripts or zip.workerScriptsPath may be set, not both.'));
879
+ return;
880
+ }
881
+ var scripts;
882
+ if (obj.zip.workerScripts) {
883
+ scripts = obj.zip.workerScripts[type];
884
+ if (!Array.isArray(scripts)) {
885
+ onerror(new Error('zip.workerScripts.' + type + ' is not an array!'));
886
+ return;
887
+ }
888
+ scripts = resolveURLs(scripts);
889
+ } else {
890
+ scripts = DEFAULT_WORKER_SCRIPTS[type].slice(0);
891
+ scripts[0] = (obj.zip.workerScriptsPath || '') + scripts[0];
892
+ }
893
+ var worker = new Worker(scripts[0]);
894
+ // record total consumed time by inflater/deflater/crc32 in this worker
895
+ worker.codecTime = worker.crcTime = 0;
896
+ worker.postMessage({ type: 'importScripts', scripts: scripts.slice(1) });
897
+ worker.addEventListener('message', onmessage);
898
+ function onmessage(ev) {
899
+ var msg = ev.data;
900
+ if (msg.error) {
901
+ worker.terminate(); // should before onerror(), because onerror() may throw.
902
+ onerror(msg.error);
903
+ return;
904
+ }
905
+ if (msg.type === 'importScripts') {
906
+ worker.removeEventListener('message', onmessage);
907
+ worker.removeEventListener('error', errorHandler);
908
+ callback(worker);
909
+ }
910
+ }
911
+ // catch entry script loading error and other unhandled errors
912
+ worker.addEventListener('error', errorHandler);
913
+ function errorHandler(err) {
914
+ worker.terminate();
915
+ onerror(err);
916
+ }
917
+ }
918
+
919
+ function onerror_default(error) {
920
+ console.error(error);
921
+ }
922
+ obj.zip = {
923
+ Reader : Reader,
924
+ Writer : Writer,
925
+ BlobReader : BlobReader,
926
+ Data64URIReader : Data64URIReader,
927
+ TextReader : TextReader,
928
+ BlobWriter : BlobWriter,
929
+ Data64URIWriter : Data64URIWriter,
930
+ TextWriter : TextWriter,
931
+ createReader : function(reader, callback, onerror) {
932
+ onerror = onerror || onerror_default;
933
+
934
+ reader.init(function() {
935
+ createZipReader(reader, callback, onerror);
936
+ }, onerror);
937
+ },
938
+ createWriter : function(writer, callback, onerror, dontDeflate) {
939
+ onerror = onerror || onerror_default;
940
+ dontDeflate = !!dontDeflate;
941
+
942
+ writer.init(function() {
943
+ createZipWriter(writer, callback, onerror, dontDeflate);
944
+ }, onerror);
945
+ },
946
+ useWebWorkers : true,
947
+ /**
948
+ * Directory containing the default worker scripts (z-worker.js, deflate.js, and inflate.js), relative to current base url.
949
+ * E.g.: zip.workerScripts = './';
950
+ */
951
+ workerScriptsPath : null,
952
+ /**
953
+ * Advanced option to control which scripts are loaded in the Web worker. If this option is specified, then workerScriptsPath must not be set.
954
+ * workerScripts.deflater/workerScripts.inflater should be arrays of urls to scripts for deflater/inflater, respectively.
955
+ * Scripts in the array are executed in order, and the first one should be z-worker.js, which is used to start the worker.
956
+ * All urls are relative to current base url.
957
+ * E.g.:
958
+ * zip.workerScripts = {
959
+ * deflater: ['z-worker.js', 'deflate.js'],
960
+ * inflater: ['z-worker.js', 'inflate.js']
961
+ * };
962
+ */
963
+ workerScripts : null,
964
+ };
965
+
966
+ })(this);