comparison geotemco/lib/jszip/jszip-load.js @ 0:57bde4830927

first commit
author Zoe Hong <zhong@mpiwg-berlin.mpg.de>
date Tue, 24 Mar 2015 11:37:17 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:57bde4830927
1 /**
2
3 JSZip - A Javascript class for generating and reading zip files
4 <http://stuartk.com/jszip>
5
6 (c) 2011 David Duponchel <d.duponchel@gmail.com>
7 Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
8
9 **/
10 /*global JSZip,JSZipBase64 */
11 (function () {
12
13 var MAX_VALUE_16BITS = 65535;
14 var MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1
15
16 /**
17 * Prettify a string read as binary.
18 * @param {string} str the string to prettify.
19 * @return {string} a pretty string.
20 */
21 var pretty = function (str) {
22 var res = '', code, i;
23 for (i = 0; i < (str||"").length; i++) {
24 code = str.charCodeAt(i);
25 res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
26 }
27 return res;
28 };
29
30 /**
31 * Find a compression registered in JSZip.
32 * @param {string} compressionMethod the method magic to find.
33 * @return {Object|null} the JSZip compression object, null if none found.
34 */
35 var findCompression = function (compressionMethod) {
36 for (var method in JSZip.compressions) {
37 if( !JSZip.compressions.hasOwnProperty(method) ) { continue; }
38 if (JSZip.compressions[method].magic === compressionMethod) {
39 return JSZip.compressions[method];
40 }
41 }
42 return null;
43 };
44
45 // class StreamReader {{{
46 /**
47 * Read bytes from a stream.
48 * Developer tip : when debugging, a watch on pretty(this.reader.stream.slice(this.reader.index))
49 * is very useful :)
50 * @constructor
51 * @param {String|ArrayBuffer|Uint8Array} stream the stream to read.
52 */
53 function StreamReader(stream) {
54 this.stream = "";
55 if (JSZip.support.uint8array && stream instanceof Uint8Array) {
56 this.stream = JSZip.utils.uint8Array2String(stream);
57 } else if (JSZip.support.arraybuffer && stream instanceof ArrayBuffer) {
58 var bufferView = new Uint8Array(stream);
59 this.stream = JSZip.utils.uint8Array2String(bufferView);
60 } else {
61 this.stream = JSZip.utils.string2binary(stream);
62 }
63 this.index = 0;
64 }
65 StreamReader.prototype = {
66 /**
67 * Check that the offset will not go too far.
68 * @param {string} offset the additional offset to check.
69 * @throws {Error} an Error if the offset is out of bounds.
70 */
71 checkOffset : function (offset) {
72 this.checkIndex(this.index + offset);
73 },
74 /**
75 * Check that the specifed index will not be too far.
76 * @param {string} newIndex the index to check.
77 * @throws {Error} an Error if the index is out of bounds.
78 */
79 checkIndex : function (newIndex) {
80 if (this.stream.length < newIndex || newIndex < 0) {
81 throw new Error("End of stream reached (stream length = " +
82 this.stream.length + ", asked index = " +
83 (newIndex) + "). Corrupted zip ?");
84 }
85 },
86 /**
87 * Change the index.
88 * @param {number} newIndex The new index.
89 * @throws {Error} if the new index is out of the stream.
90 */
91 setIndex : function (newIndex) {
92 this.checkIndex(newIndex);
93 this.index = newIndex;
94 },
95 /**
96 * Skip the next n bytes.
97 * @param {number} n the number of bytes to skip.
98 * @throws {Error} if the new index is out of the stream.
99 */
100 skip : function (n) {
101 this.setIndex(this.index + n);
102 },
103 /**
104 * Get the byte at the specified index.
105 * @param {number} i the index to use.
106 * @return {number} a byte.
107 */
108 byteAt : function(i) {
109 return this.stream.charCodeAt(i);
110 },
111 /**
112 * Get the next number with a given byte size.
113 * @param {number} size the number of bytes to read.
114 * @return {number} the corresponding number.
115 */
116 readInt : function (size) {
117 var result = 0, i;
118 this.checkOffset(size);
119 for(i = this.index + size - 1; i >= this.index; i--) {
120 result = (result << 8) + this.byteAt(i);
121 }
122 this.index += size;
123 return result;
124 },
125 /**
126 * Get the next string with a given byte size.
127 * @param {number} size the number of bytes to read.
128 * @return {string} the corresponding string.
129 */
130 readString : function (size) {
131 this.checkOffset(size);
132 // this will work because the constructor applied the "& 0xff" mask.
133 var result = this.stream.slice(this.index, this.index + size);
134 this.index += size;
135 return result;
136 },
137 /**
138 * Get the next date.
139 * @return {Date} the date.
140 */
141 readDate : function () {
142 var dostime = this.readInt(4);
143 return new Date(
144 ((dostime >> 25) & 0x7f) + 1980, // year
145 ((dostime >> 21) & 0x0f) - 1, // month
146 (dostime >> 16) & 0x1f, // day
147 (dostime >> 11) & 0x1f, // hour
148 (dostime >> 5) & 0x3f, // minute
149 (dostime & 0x1f) << 1); // second
150 }
151 };
152 // }}} end of StreamReader
153
154 // class ZipEntry {{{
155 /**
156 * An entry in the zip file.
157 * @constructor
158 * @param {Object} options Options of the current file.
159 * @param {Object} loadOptions Options for loading the stream.
160 */
161 function ZipEntry(options, loadOptions) {
162 this.options = options;
163 this.loadOptions = loadOptions;
164 }
165 ZipEntry.prototype = {
166 /**
167 * say if the file is encrypted.
168 * @return {boolean} true if the file is encrypted, false otherwise.
169 */
170 isEncrypted : function () {
171 // bit 1 is set
172 return (this.bitFlag & 0x0001) === 0x0001;
173 },
174 /**
175 * say if the file has utf-8 filename/comment.
176 * @return {boolean} true if the filename/comment is in utf-8, false otherwise.
177 */
178 useUTF8 : function () {
179 // bit 11 is set
180 return (this.bitFlag & 0x0800) === 0x0800;
181 },
182 /**
183 * Read the local part of a zip file and add the info in this object.
184 * @param {StreamReader} reader the reader to use.
185 */
186 readLocalPart : function(reader) {
187 var compression, localExtraFieldsLength;
188
189 // we already know everything from the central dir !
190 // If the central dir data are false, we are doomed.
191 // On the bright side, the local part is scary : zip64, data descriptors, both, etc.
192 // The less data we get here, the more reliable this should be.
193 // Let's skip the whole header and dash to the data !
194 reader.skip(22);
195 // in some zip created on windows, the filename stored in the central dir contains \ instead of /.
196 // Strangely, the filename here is OK.
197 // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
198 // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
199 // Search "unzip mismatching "local" filename continuing with "central" filename version" on
200 // the internet.
201 //
202 // I think I see the logic here : the central directory is used to display
203 // content and the local directory is used to extract the files. Mixing / and \
204 // may be used to display \ to windows users and use / when extracting the files.
205 // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394
206 this.fileNameLength = reader.readInt(2);
207 localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
208 this.fileName = reader.readString(this.fileNameLength);
209 reader.skip(localExtraFieldsLength);
210
211 if (this.compressedSize == -1 || this.uncompressedSize == -1) {
212 throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " +
213 "(compressedSize == -1 || uncompressedSize == -1)");
214 }
215 this.compressedFileData = reader.readString(this.compressedSize);
216
217 compression = findCompression(this.compressionMethod);
218 if (compression === null) { // no compression found
219 throw new Error("Corrupted zip : compression " + pretty(this.compressionMethod) +
220 " unknown (inner file : " + this.fileName + ")");
221 }
222 this.uncompressedFileData = compression.uncompress(this.compressedFileData);
223
224 if (this.uncompressedFileData.length !== this.uncompressedSize) {
225 throw new Error("Bug : uncompressed data size mismatch");
226 }
227
228 if (this.loadOptions.checkCRC32 && JSZip.prototype.crc32(this.uncompressedFileData) !== this.crc32) {
229 throw new Error("Corrupted zip : CRC32 mismatch");
230 }
231 },
232
233 /**
234 * Read the central part of a zip file and add the info in this object.
235 * @param {StreamReader} reader the reader to use.
236 */
237 readCentralPart : function(reader) {
238 this.versionMadeBy = reader.readString(2);
239 this.versionNeeded = reader.readInt(2);
240 this.bitFlag = reader.readInt(2);
241 this.compressionMethod = reader.readString(2);
242 this.date = reader.readDate();
243 this.crc32 = reader.readInt(4);
244 this.compressedSize = reader.readInt(4);
245 this.uncompressedSize = reader.readInt(4);
246 this.fileNameLength = reader.readInt(2);
247 this.extraFieldsLength = reader.readInt(2);
248 this.fileCommentLength = reader.readInt(2);
249 this.diskNumberStart = reader.readInt(2);
250 this.internalFileAttributes = reader.readInt(2);
251 this.externalFileAttributes = reader.readInt(4);
252 this.localHeaderOffset = reader.readInt(4);
253
254 if (this.isEncrypted()) {
255 throw new Error("Encrypted zip are not supported");
256 }
257
258 this.fileName = reader.readString(this.fileNameLength);
259 this.readExtraFields(reader);
260 this.parseZIP64ExtraField(reader);
261 this.fileComment = reader.readString(this.fileCommentLength);
262
263 // warning, this is true only for zip with madeBy == DOS (plateform dependent feature)
264 this.dir = this.externalFileAttributes & 0x00000010 ? true : false;
265 },
266 /**
267 * Parse the ZIP64 extra field and merge the info in the current ZipEntry.
268 * @param {StreamReader} reader the reader to use.
269 */
270 parseZIP64ExtraField : function(reader) {
271
272 if(!this.extraFields[0x0001]) {
273 return;
274 }
275
276 // should be something, preparing the extra reader
277 var extraReader = new StreamReader(this.extraFields[0x0001].value);
278
279 // I really hope that these 64bits integer can fit in 32 bits integer, because js
280 // won't let us have more.
281 if(this.uncompressedSize === MAX_VALUE_32BITS) {
282 this.uncompressedSize = extraReader.readInt(8);
283 }
284 if(this.compressedSize === MAX_VALUE_32BITS) {
285 this.compressedSize = extraReader.readInt(8);
286 }
287 if(this.localHeaderOffset === MAX_VALUE_32BITS) {
288 this.localHeaderOffset = extraReader.readInt(8);
289 }
290 if(this.diskNumberStart === MAX_VALUE_32BITS) {
291 this.diskNumberStart = extraReader.readInt(4);
292 }
293 },
294 /**
295 * Read the central part of a zip file and add the info in this object.
296 * @param {StreamReader} reader the reader to use.
297 */
298 readExtraFields : function(reader) {
299 var start = reader.index,
300 extraFieldId,
301 extraFieldLength,
302 extraFieldValue;
303
304 this.extraFields = this.extraFields || {};
305
306 while (reader.index < start + this.extraFieldsLength) {
307 extraFieldId = reader.readInt(2);
308 extraFieldLength = reader.readInt(2);
309 extraFieldValue = reader.readString(extraFieldLength);
310
311 this.extraFields[extraFieldId] = {
312 id: extraFieldId,
313 length: extraFieldLength,
314 value: extraFieldValue
315 };
316 }
317 },
318 /**
319 * Apply an UTF8 transformation if needed.
320 */
321 handleUTF8 : function() {
322 if (this.useUTF8()) {
323 this.fileName = JSZip.prototype.utf8decode(this.fileName);
324 this.fileComment = JSZip.prototype.utf8decode(this.fileComment);
325 }
326 }
327 };
328 // }}} end of ZipEntry
329
330 // class ZipEntries {{{
331 /**
332 * All the entries in the zip file.
333 * @constructor
334 * @param {String|ArrayBuffer|Uint8Array} data the binary stream to load.
335 * @param {Object} loadOptions Options for loading the stream.
336 */
337 function ZipEntries(data, loadOptions) {
338 this.files = [];
339 this.loadOptions = loadOptions;
340 if (data) {
341 this.load(data);
342 }
343 }
344 ZipEntries.prototype = {
345 /**
346 * Check that the reader is on the speficied signature.
347 * @param {string} expectedSignature the expected signature.
348 * @throws {Error} if it is an other signature.
349 */
350 checkSignature : function(expectedSignature) {
351 var signature = this.reader.readString(4);
352 if (signature !== expectedSignature) {
353 throw new Error("Corrupted zip or bug : unexpected signature " +
354 "(" + pretty(signature) + ", expected " + pretty(expectedSignature) + ")");
355 }
356 },
357 /**
358 * Read the end of the central directory.
359 */
360 readBlockEndOfCentral : function () {
361 this.diskNumber = this.reader.readInt(2);
362 this.diskWithCentralDirStart = this.reader.readInt(2);
363 this.centralDirRecordsOnThisDisk = this.reader.readInt(2);
364 this.centralDirRecords = this.reader.readInt(2);
365 this.centralDirSize = this.reader.readInt(4);
366 this.centralDirOffset = this.reader.readInt(4);
367
368 this.zipCommentLength = this.reader.readInt(2);
369 this.zipComment = this.reader.readString(this.zipCommentLength);
370 },
371 /**
372 * Read the end of the Zip 64 central directory.
373 * Not merged with the method readEndOfCentral :
374 * The end of central can coexist with its Zip64 brother,
375 * I don't want to read the wrong number of bytes !
376 */
377 readBlockZip64EndOfCentral : function () {
378 this.zip64EndOfCentralSize = this.reader.readInt(8);
379 this.versionMadeBy = this.reader.readString(2);
380 this.versionNeeded = this.reader.readInt(2);
381 this.diskNumber = this.reader.readInt(4);
382 this.diskWithCentralDirStart = this.reader.readInt(4);
383 this.centralDirRecordsOnThisDisk = this.reader.readInt(8);
384 this.centralDirRecords = this.reader.readInt(8);
385 this.centralDirSize = this.reader.readInt(8);
386 this.centralDirOffset = this.reader.readInt(8);
387
388 this.zip64ExtensibleData = {};
389 var extraDataSize = this.zip64EndOfCentralSize - 44,
390 index = 0,
391 extraFieldId,
392 extraFieldLength,
393 extraFieldValue;
394 while(index < extraDataSize) {
395 extraFieldId = this.reader.readInt(2);
396 extraFieldLength = this.reader.readInt(4);
397 extraFieldValue = this.reader.readString(extraFieldLength);
398 this.zip64ExtensibleData[extraFieldId] = {
399 id: extraFieldId,
400 length: extraFieldLength,
401 value: extraFieldValue
402 };
403 }
404 },
405 /**
406 * Read the end of the Zip 64 central directory locator.
407 */
408 readBlockZip64EndOfCentralLocator : function () {
409 this.diskWithZip64CentralDirStart = this.reader.readInt(4);
410 this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8);
411 this.disksCount = this.reader.readInt(4);
412 if (this.disksCount > 1) {
413 throw new Error("Multi-volumes zip are not supported");
414 }
415 },
416 /**
417 * Read the local files, based on the offset read in the central part.
418 */
419 readLocalFiles : function() {
420 var i, file;
421 for(i = 0; i < this.files.length; i++) {
422 file = this.files[i];
423 this.reader.setIndex(file.localHeaderOffset);
424 this.checkSignature(JSZip.signature.LOCAL_FILE_HEADER);
425 file.readLocalPart(this.reader);
426 file.handleUTF8();
427 }
428 },
429 /**
430 * Read the central directory.
431 */
432 readCentralDir : function() {
433 var file;
434
435 this.reader.setIndex(this.centralDirOffset);
436 while(this.reader.readString(4) === JSZip.signature.CENTRAL_FILE_HEADER) {
437 file = new ZipEntry({
438 zip64: this.zip64
439 }, this.loadOptions);
440 file.readCentralPart(this.reader);
441 this.files.push(file);
442 }
443 },
444 /**
445 * Read the end of central directory.
446 */
447 readEndOfCentral : function() {
448 var offset = this.reader.stream.lastIndexOf(JSZip.signature.CENTRAL_DIRECTORY_END);
449 if (offset === -1) {
450 throw new Error("Corrupted zip : can't find end of central directory");
451 }
452 this.reader.setIndex(offset);
453 this.checkSignature(JSZip.signature.CENTRAL_DIRECTORY_END);
454 this.readBlockEndOfCentral();
455
456
457 /* extract from the zip spec :
458 4) If one of the fields in the end of central directory
459 record is too small to hold required data, the field
460 should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
461 ZIP64 format record should be created.
462 5) The end of central directory record and the
463 Zip64 end of central directory locator record must
464 reside on the same disk when splitting or spanning
465 an archive.
466 */
467 if ( this.diskNumber === MAX_VALUE_16BITS
468 || this.diskWithCentralDirStart === MAX_VALUE_16BITS
469 || this.centralDirRecordsOnThisDisk === MAX_VALUE_16BITS
470 || this.centralDirRecords === MAX_VALUE_16BITS
471 || this.centralDirSize === MAX_VALUE_32BITS
472 || this.centralDirOffset === MAX_VALUE_32BITS
473 ) {
474 this.zip64 = true;
475
476 /*
477 Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from
478 the zip file can fit into a 32bits integer. This cannot be solved : Javascript represents
479 all numbers as 64-bit double precision IEEE 754 floating point numbers.
480 So, we have 53bits for integers and bitwise operations treat everything as 32bits.
481 see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
482 and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5
483 */
484
485 // should look for a zip64 EOCD locator
486 offset = this.reader.stream.lastIndexOf(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
487 if (offset === -1) {
488 throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");
489 }
490 this.reader.setIndex(offset);
491 this.checkSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
492 this.readBlockZip64EndOfCentralLocator();
493
494 // now the zip64 EOCD record
495 this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
496 this.checkSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_END);
497 this.readBlockZip64EndOfCentral();
498 }
499 },
500 /**
501 * Read a zip file and create ZipEntries.
502 * @param {String|ArrayBuffer|Uint8Array} data the binary string representing a zip file.
503 */
504 load : function(data) {
505 this.reader = new StreamReader(data);
506
507 this.readEndOfCentral();
508 this.readCentralDir();
509 this.readLocalFiles();
510 }
511 };
512 // }}} end of ZipEntries
513
514 /**
515 * Implementation of the load method of JSZip.
516 * It uses the above classes to decode a zip file, and load every files.
517 * @param {String|ArrayBuffer|Uint8Array} data the data to load.
518 * @param {Object} options Options for loading the stream.
519 * options.base64 : is the stream in base64 ? default : false
520 */
521 JSZip.prototype.load = function(data, options) {
522 var files, zipEntries, i, input;
523 options = options || {};
524 if(options.base64) {
525 data = JSZipBase64.decode(data);
526 }
527
528 zipEntries = new ZipEntries(data, options);
529 files = zipEntries.files;
530 for (i = 0; i < files.length; i++) {
531 input = files[i];
532 this.file(input.fileName, input.uncompressedFileData, {
533 binary:true,
534 optimizedBinaryString:true,
535 date:input.date,
536 dir:input.dir
537 });
538 }
539
540 return this;
541 };
542
543 }());
544 // enforcing Stuk's coding style
545 // vim: set shiftwidth=3 softtabstop=3 foldmethod=marker: