1 /** 2 * Copyright (C) 2012 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global runtime, core, DOMParser*/ 26 /*jslint bitwise: true*/ 27 28 /** 29 * @constructor 30 * @param {!string} url path to zip file, should be readable by the runtime 31 * @param {?function(?string, !core.Zip):undefined} entriesReadCallback callback 32 * indicating the zip 33 * has loaded this list of entries, the arguments are a string that 34 * indicates error if present and the created object 35 */ 36 core.Zip = function Zip(url, entriesReadCallback) { 37 "use strict"; 38 var /**@type{Array.<!ZipEntry>}*/ 39 entries, 40 /**@type{number}*/ 41 filesize, 42 /**@type{number}*/ 43 nEntries, 44 /**@type{!function(!Uint8Array,number):!Uint8Array}*/ 45 inflate = core.RawInflate.inflate, 46 /**@type{!core.Zip}*/ 47 zip = this, 48 base64 = new core.Base64(); 49 50 /** 51 * @param {!Uint8Array} data 52 * @return {!number} 53 */ 54 function crc32(data) { 55 // Calculate the crc32 polynomial of a string 56 // 57 // version: 1009.2513 58 // discuss at: http:\/\/phpjs.org\/functions\/crc32 59 // + original by: Webtoolkit.info (http:\/\/www.webtoolkit.info\/) 60 // + improved by: T0bsn 61 // - depends on: utf8_encode 62 // * example 1: crc32('Kevin van Zonneveld'); 63 // * returns 1: 1249991249 64 var /**@const 65 @type{!Array.<!number>}*/ 66 table = [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D], 67 /**@type{!number}*/ 68 crc = 0, 69 /**@type{!number}*/ 70 i, 71 /**@const@type{!number}*/ 72 iTop = data.length, 73 /**@type{!number}*/ 74 x = 0, 75 /**@type{!number}*/ 76 y = 0; 77 crc = crc ^ (-1); 78 for (i = 0; i < iTop; i += 1) { 79 y = (crc ^ data[i]) & 0xFF; 80 x = table[y]; 81 crc = (crc >>> 8) ^ x; 82 } 83 return crc ^ (-1); 84 } 85 86 /** 87 * @param {!number} dostime 88 * @return {!Date} 89 */ 90 function dosTime2Date(dostime) { 91 var /**@const@type{!number}*/ 92 year = ((dostime >> 25) & 0x7f) + 1980, 93 /**@const@type{!number}*/ 94 month = ((dostime >> 21) & 0x0f) - 1, 95 /**@const@type{!number}*/ 96 mday = (dostime >> 16) & 0x1f, 97 /**@const@type{!number}*/ 98 hour = (dostime >> 11) & 0x0f, 99 /**@const@type{!number}*/ 100 min = (dostime >> 5) & 0x3f, 101 /**@const@type{!number}*/ 102 sec = (dostime & 0x1f) << 1, 103 /**@const@type{!Date}*/ 104 d = new Date(year, month, mday, hour, min, sec); 105 return d; 106 } 107 /** 108 * @param {!Date} date 109 * @return {!number} 110 */ 111 function date2DosTime(date) { 112 var /**@const@type{!number}*/ y = date.getFullYear(); 113 return y < 1980 ? 0 : 114 ((y - 1980) << 25) | ((date.getMonth() + 1) << 21) | 115 (date.getDate() << 16) | (date.getHours() << 11) | 116 (date.getMinutes() << 5) | (date.getSeconds() >> 1); 117 } 118 /** 119 * Create a new ZipEntry. 120 * If the stream is not provided, the object should be initialized 121 * with ZipEntry.set() 122 * @constructor 123 * @param {!string} url 124 * @param {!core.ByteArray=} stream 125 */ 126 function ZipEntry(url, stream) { 127 var /**@const 128 @type{!number}*/ 129 sig, 130 /**@const 131 @type{!number}*/ 132 namelen, 133 /**@const 134 @type{!number}*/ 135 extralen, 136 /**@const 137 @type{!number}*/ 138 commentlen, 139 /**@const 140 @type{!number}*/ 141 compressionMethod, 142 /**@const 143 @type{!number}*/ 144 compressedSize, 145 /**@const 146 @type{!number}*/ 147 uncompressedSize, 148 /**@const 149 @type{!number}*/ 150 offset, 151 /**@const 152 @type{!ZipEntry}*/ 153 entry = this; 154 155 /** 156 * @param {!Uint8Array} data 157 * @param {!function(?string, ?Uint8Array)} callback 158 * @return {undefined} 159 */ 160 function handleEntryData(data, callback) { 161 var /**@const 162 @type{!core.ByteArray}*/ 163 estream = new core.ByteArray(data), 164 /**@const 165 @type{!number}*/ 166 esig = estream.readUInt32LE(), 167 /**@const 168 @type{!number}*/ 169 filenamelen, 170 /**@const 171 @type{!number}*/ 172 eextralen; 173 if (esig !== 0x04034b50) { 174 callback('File entry signature is wrong.' + esig.toString() + 175 ' ' + data.length.toString(), null); 176 return; 177 } 178 estream.pos += 22; 179 filenamelen = estream.readUInt16LE(); 180 eextralen = estream.readUInt16LE(); 181 estream.pos += filenamelen + eextralen; 182 if (compressionMethod) { 183 data = data.subarray(estream.pos, estream.pos + compressedSize); 184 if (compressedSize !== data.length) { 185 callback("The amount of compressed bytes read was " + 186 data.length.toString() + " instead of " + 187 compressedSize.toString() + " for " + entry.filename + 188 " in " + url + ".", null); 189 return; 190 } 191 data = inflate(data, uncompressedSize); 192 } else { 193 data = data.subarray(estream.pos, estream.pos + uncompressedSize); 194 } 195 if (uncompressedSize !== data.length) { 196 callback("The amount of bytes read was " + 197 data.length.toString() + 198 " instead of " + uncompressedSize.toString() + " for " + 199 entry.filename + " in " + url + ".", null); 200 return; 201 } 202 /* 203 * This check is disabled for performance reasons 204 if (crc !== crc32(data)) { 205 runtime.log("Warning: CRC32 for " + entry.filename + 206 " is wrong."); 207 } 208 */ 209 entry.data = data; 210 callback(null, data); 211 } 212 /** 213 * @param {!function(?string, ?Uint8Array)} callback 214 * @return {undefined} 215 */ 216 function load(callback) { 217 // if data has already been downloaded, use that 218 if (entry.data !== null) { 219 callback(null, entry.data); 220 return; 221 } 222 // the 256 at the end is security for when local extra field is 223 // larger 224 var /**@type{!number}*/ size 225 = compressedSize + 34 + namelen + extralen + 256; 226 if (size + offset > filesize) { 227 size = filesize - offset; 228 } 229 runtime.read(url, offset, size, function (err, data) { 230 if (err || data === null) { 231 callback(err, data); 232 } else { 233 handleEntryData(data, callback); 234 } 235 }); 236 } 237 this.load = load; 238 /** 239 * @param {!string} filename 240 * @param {!Uint8Array} data 241 * @param {!boolean} compressed 242 * @param {!Date} date 243 * @return {undefined} 244 */ 245 function set(filename, data, compressed, date) { 246 entry.filename = filename; 247 entry.data = data; 248 entry.compressed = compressed; 249 entry.date = date; 250 } 251 this.set = set; 252 /** 253 * @type {?string} 254 */ 255 this.error = null; 256 257 if (!stream) { 258 return; 259 } 260 sig = stream.readUInt32LE(); 261 262 if (sig !== 0x02014b50) { 263 this.error = 264 "Central directory entry has wrong signature at position " + 265 (stream.pos - 4).toString() + ' for file "' + url + '": ' + 266 stream.data.length.toString(); 267 return; 268 } 269 // stream should be positioned at the start of the CDS entry for the 270 // file 271 stream.pos += 6; 272 compressionMethod = stream.readUInt16LE(); 273 this.date = dosTime2Date(stream.readUInt32LE()); 274 stream.readUInt32LE(); 275 compressedSize = stream.readUInt32LE(); 276 uncompressedSize = stream.readUInt32LE(); 277 namelen = stream.readUInt16LE(); 278 extralen = stream.readUInt16LE(); 279 commentlen = stream.readUInt16LE(); 280 stream.pos += 8; 281 offset = stream.readUInt32LE(); 282 this.filename = runtime.byteArrayToString( 283 stream.data.subarray(stream.pos, stream.pos + namelen), 284 "utf8" 285 ); 286 /**@type{?Uint8Array}*/ 287 this.data = null; 288 stream.pos += namelen + extralen + commentlen; 289 } 290 /** 291 * @param {!Uint8Array} data 292 * @param {!function(?string, !core.Zip)} callback 293 * @return {undefined} 294 */ 295 function handleCentralDirectory(data, callback) { 296 // parse the central directory 297 var stream = new core.ByteArray(data), i, e; 298 entries = []; 299 for (i = 0; i < nEntries; i += 1) { 300 e = new ZipEntry(url, stream); 301 if (e.error) { 302 callback(e.error, zip); 303 return; 304 } 305 entries[entries.length] = e; 306 } 307 // report that entries are listed and no error occured 308 callback(null, zip); 309 } 310 /** 311 * @param {!Uint8Array} data 312 * @param {!function(?string, !core.Zip)} callback 313 * @return {undefined} 314 */ 315 function handleCentralDirectoryEnd(data, callback) { 316 if (data.length !== 22) { 317 callback("Central directory length should be 22.", zip); 318 return; 319 } 320 var stream = new core.ByteArray(data), sig, disk, cddisk, diskNEntries, 321 cdsSize, cdsOffset; 322 sig = stream.readUInt32LE(); 323 if (sig !== 0x06054b50) { 324 callback('Central directory signature is wrong: ' + sig.toString(), 325 zip); 326 return; 327 } 328 disk = stream.readUInt16LE(); 329 if (disk !== 0) { 330 callback('Zip files with non-zero disk numbers are not supported.', 331 zip); 332 return; 333 } 334 cddisk = stream.readUInt16LE(); 335 if (cddisk !== 0) { 336 callback('Zip files with non-zero disk numbers are not supported.', 337 zip); 338 return; 339 } 340 diskNEntries = stream.readUInt16LE(); 341 nEntries = stream.readUInt16LE(); 342 if (diskNEntries !== nEntries) { 343 callback('Number of entries is inconsistent.', zip); 344 return; 345 } 346 cdsSize = stream.readUInt32LE(); 347 cdsOffset = stream.readUInt16LE(); 348 cdsOffset = filesize - 22 - cdsSize; 349 350 // for some reason cdsOffset is not always equal to offset calculated 351 // from the central directory size. The latter is reliable. 352 runtime.read(url, cdsOffset, filesize - cdsOffset, 353 function (err, data) { 354 if (err || data === null) { 355 callback(err, zip); 356 } else { 357 handleCentralDirectory(data, callback); 358 } 359 }); 360 } 361 /** 362 * @param {!string} filename 363 * @param {!function(?string, ?Uint8Array)} callback receiving err and data 364 * @return {undefined} 365 */ 366 function load(filename, callback) { 367 var entry = null, 368 e, 369 i; 370 for (i = 0; i < entries.length; i += 1) { 371 e = entries[i]; 372 if (e.filename === filename) { 373 entry = e; 374 break; 375 } 376 } 377 if (entry) { 378 if (entry.data) { 379 callback(null, entry.data); 380 } else { 381 entry.load(callback); 382 } 383 } else { 384 callback(filename + " not found.", null); 385 } 386 } 387 /** 388 * @param {!string} filename 389 * @param {!function(?string, ?string):undefined} callback receiving err and data 390 * @return {undefined} 391 */ 392 function loadAsString(filename, callback) { 393 // the javascript implementation simply reads the file and converts to 394 // string 395 load(filename, function (err, data) { 396 if (err || data === null) { 397 return callback(err, null); 398 } 399 var d = runtime.byteArrayToString(data, "utf8"); 400 callback(null, d); 401 }); 402 } 403 /** 404 * @param {!string} filename 405 * @param {!{rootElementReady: function(?string, ?string=, boolean=):undefined}} handler 406 * @return {undefined} 407 */ 408 function loadContentXmlAsFragments(filename, handler) { 409 // the javascript implementation simply reads the file 410 zip.loadAsString(filename, function (err, data) { 411 if (err) { 412 return handler.rootElementReady(err); 413 } 414 handler.rootElementReady(null, data, true); 415 }); 416 } 417 /** 418 * @param {!string} filename 419 * @param {!string} mimetype 420 * @param {!function(?string,?string):undefined} callback 421 */ 422 function loadAsDataURL(filename, mimetype, callback) { 423 load(filename, function (err, data) { 424 if (err || !data) { 425 return callback(err, null); 426 } 427 var /**@const@type{!Uint8Array}*/p = data, 428 chunksize = 45000, // must be multiple of 3 and less than 50000 429 i = 0, 430 dataurl; 431 if (!mimetype) { 432 if (p[1] === 0x50 && p[2] === 0x4E && p[3] === 0x47) { 433 mimetype = "image/png"; 434 } else if (p[0] === 0xFF && p[1] === 0xD8 && p[2] === 0xFF) { 435 mimetype = "image/jpeg"; 436 } else if (p[0] === 0x47 && p[1] === 0x49 && p[2] === 0x46) { 437 mimetype = "image/gif"; 438 } else { 439 mimetype = ""; 440 } 441 } 442 dataurl = 'data:' + mimetype + ';base64,'; 443 // to avoid exceptions, base64 encoding is done in chunks 444 // it would make sense to move this to base64.toBase64 445 while (i < data.length) { 446 dataurl += base64.convertUTF8ArrayToBase64( 447 p.subarray(i, Math.min(i + chunksize, p.length)) 448 ); 449 i += chunksize; 450 } 451 callback(null, dataurl); 452 }); 453 } 454 /** 455 * @param {!string} filename 456 * @param {function(?string,?Document):undefined} callback 457 * @return {undefined} 458 */ 459 function loadAsDOM(filename, callback) { 460 zip.loadAsString(filename, function (err, xmldata) { 461 if (err || xmldata === null) { 462 callback(err, null); 463 return; 464 } 465 var parser = new DOMParser(), 466 dom = parser.parseFromString(xmldata, "text/xml"); 467 callback(null, dom); 468 }); 469 } 470 /** 471 * Add or replace an entry to the zip file. 472 * This data is not stored to disk yet, and therefore, no callback is 473 * necessary. 474 * @param {!string} filename 475 * @param {!Uint8Array} data 476 * @param {!boolean} compressed 477 * @param {!Date} date 478 * @return {undefined} 479 */ 480 function save(filename, data, compressed, date) { 481 var i, 482 entry; 483 for (i = 0; i < entries.length; i += 1) { 484 entry = entries[i]; 485 if (entry.filename === filename) { 486 entry.set(filename, data, compressed, date); 487 return; 488 } 489 } 490 entry = new ZipEntry(url); 491 entry.set(filename, data, compressed, date); 492 entries.push(entry); 493 } 494 /** 495 * Removes entry from the zip. 496 * @param {!string} filename 497 * @return {!boolean} return false if entry is not found; otherwise true. 498 */ 499 function remove(filename) { 500 var i, entry; 501 for (i = 0; i < entries.length; i += 1) { 502 entry = entries[i]; 503 if (entry.filename === filename) { 504 entries.splice(i, 1); 505 return true; 506 } 507 } 508 return false; 509 } 510 /** 511 * @param {!ZipEntry} entry 512 * @return {!core.ByteArrayWriter} 513 */ 514 function writeEntry(entry) { 515 // each entry is currently stored uncompressed 516 var data = new core.ByteArrayWriter("utf8"), 517 length = 0; 518 data.appendArray([0x50, 0x4B, 0x03, 0x04, 0x14, 0, 0, 0, 0, 0]); 519 if (entry.data) { 520 length = entry.data.length; 521 } 522 data.appendUInt32LE(date2DosTime(entry.date)); 523 data.appendUInt32LE(entry.data ? crc32(entry.data) : 0); 524 data.appendUInt32LE(length); // compressedSize 525 data.appendUInt32LE(length); // uncompressedSize 526 data.appendUInt16LE(entry.filename.length); // namelen 527 data.appendUInt16LE(0); // extralen 528 data.appendString(entry.filename); 529 if (entry.data) { 530 data.appendByteArray(entry.data); 531 } 532 return data; 533 } 534 /** 535 * @param {!ZipEntry} entry 536 * @param {!number} offset 537 * @return {!core.ByteArrayWriter} 538 */ 539 function writeCODEntry(entry, offset) { 540 // each entry is currently stored uncompressed 541 var data = new core.ByteArrayWriter("utf8"), 542 length = 0; 543 data.appendArray([0x50, 0x4B, 0x01, 0x02, 0x14, 0x00, 0x14, 0x00, 544 0, 0, 0, 0]); 545 if (entry.data) { 546 length = entry.data.length; 547 } 548 data.appendUInt32LE(date2DosTime(entry.date)); 549 data.appendUInt32LE(entry.data ? crc32(entry.data) : 0); 550 data.appendUInt32LE(length); // compressedSize 551 data.appendUInt32LE(length); // uncompressedSize 552 data.appendUInt16LE(entry.filename.length); // namelen 553 // extralen, commalen, diskno, file attributes 554 data.appendArray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); 555 data.appendUInt32LE(offset); 556 data.appendString(entry.filename); 557 return data; 558 } 559 /** 560 * @param {!number} position 561 * @param {!function(?string):undefined} callback 562 * @return {undefined} 563 */ 564 function loadAllEntries(position, callback) { 565 if (position === entries.length) { 566 callback(null); 567 return; 568 } 569 var entry = entries[position]; 570 if (entry.data !== null) { 571 loadAllEntries(position + 1, callback); 572 return; 573 } 574 entry.load(function (err) { 575 if (err) { 576 callback(err); 577 return; 578 } 579 loadAllEntries(position + 1, callback); 580 }); 581 } 582 /** 583 * Create a bytearray from the zipfile. 584 * @param {!function(!Uint8Array):undefined} successCallback receiving zip as bytearray 585 * @param {!function(?string):undefined} errorCallback receiving possible err 586 * @return {undefined} 587 */ 588 function createByteArray(successCallback, errorCallback) { 589 // make sure all data is in memory, for each entry that has data 590 // undefined, try to load the entry 591 loadAllEntries(0, function (err) { 592 if (err) { 593 errorCallback(err); 594 return; 595 } 596 var i, e, codoffset, codsize, 597 data = new core.ByteArrayWriter("utf8"), 598 offsets = [0]; 599 // write entries 600 for (i = 0; i < entries.length; i += 1) { 601 data.appendByteArrayWriter(writeEntry(entries[i])); 602 offsets.push(data.getLength()); 603 } 604 // write central directory 605 codoffset = data.getLength(); 606 for (i = 0; i < entries.length; i += 1) { 607 e = entries[i]; 608 data.appendByteArrayWriter(writeCODEntry(e, offsets[i])); 609 } 610 codsize = data.getLength() - codoffset; 611 data.appendArray([0x50, 0x4B, 0x05, 0x06, 0, 0, 0, 0]); 612 data.appendUInt16LE(entries.length); 613 data.appendUInt16LE(entries.length); 614 data.appendUInt32LE(codsize); 615 data.appendUInt32LE(codoffset); 616 data.appendArray([0, 0]); 617 618 successCallback(data.getByteArray()); 619 }); 620 } 621 /** 622 * Write the zipfile to the given path. 623 * @param {!string} newurl 624 * @param {!function(?string):undefined} callback receiving possible err 625 * @return {undefined} 626 */ 627 function writeAs(newurl, callback) { 628 // make sure all data is in memory, for each entry that has data 629 // undefined, try to load the entry 630 createByteArray(function (data) { 631 runtime.writeFile(newurl, data, callback); 632 }, callback); 633 } 634 /** 635 * Write the zipfile to the given path. 636 * @param {!function(?string):undefined} callback receiving possible err 637 * @return {undefined} 638 */ 639 function write(callback) { 640 writeAs(url, callback); 641 } 642 this.load = load; 643 this.save = save; 644 this.remove = remove; 645 this.write = write; 646 this.writeAs = writeAs; 647 this.createByteArray = createByteArray; 648 // a special function that makes faster odf loading possible 649 this.loadContentXmlAsFragments = loadContentXmlAsFragments; 650 this.loadAsString = loadAsString; 651 this.loadAsDOM = loadAsDOM; 652 this.loadAsDataURL = loadAsDataURL; 653 this.getEntries = function () { 654 return entries.slice(); 655 }; 656 657 // determine the file size 658 filesize = -1; 659 // if no callback is defined, this is a new file 660 if (entriesReadCallback === null) { 661 entries = []; 662 return; 663 } 664 runtime.getFileSize(url, function (size) { 665 filesize = size; 666 if (filesize < 0) { 667 entriesReadCallback("File '" + url + "' cannot be read.", zip); 668 } else { 669 runtime.read(url, filesize - 22, 22, function (err, data) { 670 // todo: refactor entire zip class 671 if (err || entriesReadCallback === null || data === null) { 672 entriesReadCallback(err, zip); 673 } else { 674 handleCentralDirectoryEnd(data, entriesReadCallback); 675 } 676 }); 677 } 678 }); 679 }; 680