1 /* 2 Copyright 2008-2022 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph and JSXCompressor. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 JSXCompressor is free software dual licensed under the GNU LGPL or Apache License. 14 15 You can redistribute it and/or modify it under the terms of the 16 17 * GNU Lesser General Public License as published by 18 the Free Software Foundation, either version 3 of the License, or 19 (at your option) any later version 20 OR 21 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 22 OR 23 * Apache License Version 2.0 24 25 JSXGraph is distributed in the hope that it will be useful, 26 but WITHOUT ANY WARRANTY; without even the implied warranty of 27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 GNU Lesser General Public License for more details. 29 30 You should have received a copy of the GNU Lesser General Public License, Apache 31 License, and the MIT License along with JSXGraph. If not, see 32 <http://www.gnu.org/licenses/>, <https://www.apache.org/licenses/LICENSE-2.0.html>, 33 and <http://opensource.org/licenses/MIT/>. 34 */ 35 36 37 /*global JXG: true, define: true*/ 38 /*jslint nomen: true, plusplus: true, bitwise: true*/ 39 40 /* depends: 41 jxg 42 */ 43 44 /** 45 * @fileoverview Utilities for uncompressing and base64 decoding 46 */ 47 48 define(['jxg'], function (JXG) { 49 50 "use strict"; 51 52 // Zip routine constants 53 54 var bitReverse = [ 55 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 56 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 57 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 58 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 59 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 60 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 61 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 62 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 63 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 64 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 65 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 66 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 67 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 68 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 69 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 70 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 71 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 72 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 73 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 74 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 75 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 76 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 77 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 78 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 79 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 80 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 81 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 82 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 83 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 84 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 85 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 86 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff 87 ], 88 cplens = [ 89 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 90 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 91 ], 92 93 cplext = [ 94 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 95 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99 96 ], /* 99==invalid */ 97 98 cpdist = [ 99 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d, 100 0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1, 101 0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01, 102 0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001 103 ], 104 105 cpdext = [ 106 0, 0, 0, 0, 1, 1, 2, 2, 107 3, 3, 4, 4, 5, 5, 6, 6, 108 7, 7, 8, 8, 9, 9, 10, 10, 109 11, 11, 12, 12, 13, 13 110 ], 111 112 border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], 113 114 NAMEMAX = 256; 115 116 117 // Util namespace 118 JXG.Util = JXG.Util || {}; 119 120 /** 121 * @class Unzip class 122 * Class for gunzipping, unzipping and base64 decoding of files. 123 * It is used for reading GEONExT, Geogebra and Intergeo files. 124 * 125 * Only Huffman codes are decoded in gunzip. 126 * The code is based on the source code for gunzip.c by Pasi Ojala 127 * @see http://www.cs.tut.fi/~albert/Dev/gunzip/gunzip.c 128 * @see http://www.cs.tut.fi/~albert 129 */ 130 JXG.Util.Unzip = function (barray) { 131 var gpflags, crc, SIZE, fileout, flens, fmax, 132 outputArr = [], 133 output = '', 134 debug = false, 135 files = 0, 136 unzipped = [], 137 buf32k = new Array(32768), 138 bIdx = 0, 139 modeZIP = false, 140 barraylen = barray.length, 141 bytepos = 0, 142 bitpos = 0, 143 bb = 1, 144 bits = 0, 145 literalTree = new Array(288), 146 distanceTree = new Array(32), 147 treepos = 0, 148 Places = null, 149 Places2 = null, 150 impDistanceTree = new Array(64), 151 impLengthTree = new Array(64), 152 len = 0, 153 fpos = new Array(17), 154 nameBuf = []; 155 156 fpos[0] = 0; 157 158 function readByte() { 159 bits += 8; 160 161 if (bytepos < barraylen) { 162 return barray[bytepos++]; 163 } 164 165 return -1; 166 } 167 168 function byteAlign() { 169 bb = 1; 170 } 171 172 function readBit() { 173 var carry; 174 175 try { // Prevent problems on iOS7 with >> 176 bits++; 177 carry = (bb & 1); 178 bb >>= 1; 179 180 if (bb === 0) { 181 bb = readByte(); 182 carry = (bb & 1); 183 bb = (bb >> 1) | 0x80; 184 } 185 186 return carry; 187 } catch (e) { 188 throw e; 189 } 190 } 191 192 function readBits(a) { 193 var res = 0, 194 i = a; 195 196 // Prevent problems on iOS7 with >> 197 try { 198 while (i--) { 199 res = (res << 1) | readBit(); 200 } 201 202 if (a) { 203 res = bitReverse[res] >> (8 - a); 204 } 205 } catch (e) { 206 throw e; 207 } 208 209 return res; 210 } 211 212 function flushBuffer() { 213 bIdx = 0; 214 } 215 216 function addBuffer(a) { 217 SIZE++; 218 buf32k[bIdx++] = a; 219 outputArr.push(String.fromCharCode(a)); 220 221 if (bIdx === 0x8000) { 222 bIdx = 0; 223 } 224 } 225 226 function HufNode() { 227 this.b0 = 0; 228 this.b1 = 0; 229 this.jump = null; 230 this.jumppos = -1; 231 } 232 233 function isPat() { 234 while (true) { 235 if (fpos[len] >= fmax) { 236 return -1; 237 } 238 239 if (flens[fpos[len]] === len) { 240 return fpos[len]++; 241 } 242 243 fpos[len]++; 244 } 245 } 246 247 function rec() { 248 var curplace = Places[treepos], 249 tmp; 250 251 if (len === 17) { 252 return -1; 253 } 254 treepos++; 255 len++; 256 257 tmp = isPat(); 258 259 if (tmp >= 0) { 260 /* leaf cell for 0-bit */ 261 curplace.b0 = tmp; 262 } else { 263 /* Not a Leaf cell */ 264 curplace.b0 = 0x8000; 265 266 if (rec()) { 267 return -1; 268 } 269 } 270 271 tmp = isPat(); 272 273 if (tmp >= 0) { 274 /* leaf cell for 1-bit */ 275 curplace.b1 = tmp; 276 /* Just for the display routine */ 277 curplace.jump = null; 278 } else { 279 /* Not a Leaf cell */ 280 curplace.b1 = 0x8000; 281 curplace.jump = Places[treepos]; 282 curplace.jumppos = treepos; 283 if (rec()) { 284 return -1; 285 } 286 } 287 len--; 288 289 return 0; 290 } 291 292 function createTree(currentTree, numval, lengths, show) { 293 var i; 294 295 Places = currentTree; 296 treepos = 0; 297 flens = lengths; 298 fmax = numval; 299 300 for (i = 0; i < 17; i++) { 301 fpos[i] = 0; 302 } 303 len = 0; 304 305 if (rec()) { 306 return -1; 307 } 308 309 return 0; 310 } 311 312 function decodeValue(currentTree) { 313 var len, i, b, 314 xtreepos = 0, 315 X = currentTree[xtreepos]; 316 317 /* decode one symbol of the data */ 318 while (true) { 319 b = readBit(); 320 321 if (b) { 322 if (!(X.b1 & 0x8000)) { 323 /* If leaf node, return data */ 324 return X.b1; 325 } 326 327 X = X.jump; 328 len = currentTree.length; 329 330 for (i = 0; i < len; i++) { 331 if (currentTree[i] === X) { 332 xtreepos = i; 333 break; 334 } 335 } 336 } else { 337 if (!(X.b0 & 0x8000)) { 338 /* If leaf node, return data */ 339 return X.b0; 340 } 341 xtreepos++; 342 X = currentTree[xtreepos]; 343 } 344 } 345 } 346 347 function deflateLoop() { 348 var last, c, type, i, j, l, ll, ll2, len, blockLen, dist, cSum, 349 n, literalCodes, distCodes, lenCodes, z; 350 351 do { 352 last = readBit(); 353 type = readBits(2); 354 355 if (type === 0) { 356 // Stored 357 byteAlign(); 358 blockLen = readByte(); 359 blockLen |= (readByte() << 8); 360 361 cSum = readByte(); 362 cSum |= (readByte() << 8); 363 364 if (((blockLen ^ ~cSum) & 0xffff)) { 365 JXG.debug('BlockLen checksum mismatch\n'); 366 } 367 368 while (blockLen--) { 369 c = readByte(); 370 addBuffer(c); 371 } 372 } else if (type === 1) { 373 /* Fixed Huffman tables -- fixed decode routine */ 374 while (true) { 375 /* 376 256 0000000 0 377 : : : 378 279 0010111 23 379 0 00110000 48 380 : : : 381 143 10111111 191 382 280 11000000 192 383 : : : 384 287 11000111 199 385 144 110010000 400 386 : : : 387 255 111111111 511 388 389 Note the bit order! 390 */ 391 392 j = (bitReverse[readBits(7)] >> 1); 393 394 if (j > 23) { 395 j = (j << 1) | readBit(); /* 48..255 */ 396 397 if (j > 199) { /* 200..255 */ 398 j -= 128; /* 72..127 */ 399 j = (j << 1) | readBit(); /* 144..255 << */ 400 } else { /* 48..199 */ 401 j -= 48; /* 0..151 */ 402 if (j > 143) { 403 j = j + 136; /* 280..287 << */ 404 /* 0..143 << */ 405 } 406 } 407 } else { /* 0..23 */ 408 j += 256; /* 256..279 << */ 409 } 410 411 if (j < 256) { 412 addBuffer(j); 413 } else if (j === 256) { 414 /* EOF */ 415 break; 416 } else { 417 j -= 256 + 1; /* bytes + EOF */ 418 len = readBits(cplext[j]) + cplens[j]; 419 j = bitReverse[readBits(5)] >> 3; 420 421 if (cpdext[j] > 8) { 422 dist = readBits(8); 423 dist |= (readBits(cpdext[j] - 8) << 8); 424 } else { 425 dist = readBits(cpdext[j]); 426 } 427 428 dist += cpdist[j]; 429 430 for (j = 0; j < len; j++) { 431 c = buf32k[(bIdx - dist) & 0x7fff]; 432 addBuffer(c); 433 } 434 } 435 } // while 436 } else if (type === 2) { 437 // "static" just to preserve stack 438 ll = new Array(288 + 32); 439 440 // Dynamic Huffman tables 441 literalCodes = 257 + readBits(5); 442 distCodes = 1 + readBits(5); 443 lenCodes = 4 + readBits(4); 444 445 for (j = 0; j < 19; j++) { 446 ll[j] = 0; 447 } 448 449 // Get the decode tree code lengths 450 451 for (j = 0; j < lenCodes; j++) { 452 ll[border[j]] = readBits(3); 453 } 454 len = distanceTree.length; 455 456 for (i = 0; i < len; i++) { 457 distanceTree[i] = new HufNode(); 458 } 459 460 if (createTree(distanceTree, 19, ll, 0)) { 461 flushBuffer(); 462 return 1; 463 } 464 465 //read in literal and distance code lengths 466 n = literalCodes + distCodes; 467 i = 0; 468 z = -1; 469 470 while (i < n) { 471 z++; 472 j = decodeValue(distanceTree); 473 474 // length of code in bits (0..15) 475 if (j < 16) { 476 ll[i++] = j; 477 // repeat last length 3 to 6 times 478 } else if (j === 16) { 479 j = 3 + readBits(2); 480 481 if (i + j > n) { 482 flushBuffer(); 483 return 1; 484 } 485 l = i ? ll[i - 1] : 0; 486 487 while (j--) { 488 ll[i++] = l; 489 } 490 } else { 491 // 3 to 10 zero length codes 492 if (j === 17) { 493 j = 3 + readBits(3); 494 // j == 18: 11 to 138 zero length codes 495 } else { 496 j = 11 + readBits(7); 497 } 498 499 if (i + j > n) { 500 flushBuffer(); 501 return 1; 502 } 503 504 while (j--) { 505 ll[i++] = 0; 506 } 507 } 508 } 509 510 // Can overwrite tree decode tree as it is not used anymore 511 len = literalTree.length; 512 for (i = 0; i < len; i++) { 513 literalTree[i] = new HufNode(); 514 } 515 516 if (createTree(literalTree, literalCodes, ll, 0)) { 517 flushBuffer(); 518 return 1; 519 } 520 521 len = literalTree.length; 522 523 for (i = 0; i < len; i++) { 524 distanceTree[i] = new HufNode(); 525 } 526 527 ll2 = []; 528 529 for (i = literalCodes; i < ll.length; i++) { 530 ll2[i - literalCodes] = ll[i]; 531 } 532 533 if (createTree(distanceTree, distCodes, ll2, 0)) { 534 flushBuffer(); 535 return 1; 536 } 537 538 while (true) { 539 j = decodeValue(literalTree); 540 541 // In C64: if carry set 542 if (j >= 256) { 543 j -= 256; 544 if (j === 0) { 545 // EOF 546 break; 547 } 548 549 j -= 1; 550 len = readBits(cplext[j]) + cplens[j]; 551 j = decodeValue(distanceTree); 552 553 if (cpdext[j] > 8) { 554 dist = readBits(8); 555 dist |= (readBits(cpdext[j] - 8) << 8); 556 } else { 557 dist = readBits(cpdext[j]); 558 } 559 560 dist += cpdist[j]; 561 562 while (len--) { 563 c = buf32k[(bIdx - dist) & 0x7fff]; 564 addBuffer(c); 565 } 566 } else { 567 addBuffer(j); 568 } 569 } 570 } 571 } while (!last); 572 573 flushBuffer(); 574 byteAlign(); 575 576 return 0; 577 } 578 579 580 /** 581 * nextFile: 582 * Extract the next file from the compressed archive. 583 * Calls skipdir() to proceed recursively. 584 * 585 * @return {Boolean} false if the end of files' data section has baseElement 586 * reached. Then, then all recursive functions are stopped immediately. 587 * 588 */ 589 function nextFile() { 590 var i, c, extralen, filelen, size, compSize, crc, method, 591 tmp = []; 592 593 // Prevent problems on iOS7 with >> 594 try { 595 outputArr = []; 596 modeZIP = false; 597 tmp[0] = readByte(); 598 tmp[1] = readByte(); 599 600 //GZIP 601 if (tmp[0] === 0x78 && tmp[1] === 0xda) { 602 deflateLoop(); 603 unzipped[files] = [outputArr.join(''), 'geonext.gxt']; 604 files++; 605 } 606 607 //GZIP 608 if (tmp[0] === 0x1f && tmp[1] === 0x8b) { 609 skipdir(); 610 unzipped[files] = [outputArr.join(''), 'file']; 611 files++; 612 } 613 614 //ZIP 615 if (tmp[0] === 0x50 && tmp[1] === 0x4b) { 616 modeZIP = true; 617 tmp[2] = readByte(); 618 tmp[3] = readByte(); 619 620 if (tmp[2] === 0x03 && tmp[3] === 0x04) { 621 //MODE_ZIP 622 tmp[0] = readByte(); 623 tmp[1] = readByte(); 624 625 gpflags = readByte(); 626 gpflags |= (readByte() << 8); 627 628 method = readByte(); 629 method |= (readByte() << 8); 630 631 readByte(); 632 readByte(); 633 readByte(); 634 readByte(); 635 636 crc = readByte(); 637 crc |= (readByte() << 8); 638 crc |= (readByte() << 16); 639 crc |= (readByte() << 24); 640 641 compSize = readByte(); 642 compSize |= (readByte() << 8); 643 compSize |= (readByte() << 16); 644 compSize |= (readByte() << 24); 645 646 size = readByte(); 647 size |= (readByte() << 8); 648 size |= (readByte() << 16); 649 size |= (readByte() << 24); 650 651 filelen = readByte(); 652 filelen |= (readByte() << 8); 653 654 extralen = readByte(); 655 extralen |= (readByte() << 8); 656 657 i = 0; 658 nameBuf = []; 659 660 while (filelen--) { 661 c = readByte(); 662 if (c === '/' | c === ':') { 663 i = 0; 664 } else if (i < NAMEMAX - 1) { 665 nameBuf[i++] = String.fromCharCode(c); 666 } 667 } 668 669 if (!fileout) { 670 fileout = nameBuf; 671 } 672 673 i = 0; 674 while (i < extralen) { 675 c = readByte(); 676 i++; 677 } 678 679 SIZE = 0; 680 if (method === 8) { 681 deflateLoop(); 682 unzipped[files] = new Array(2); 683 unzipped[files][0] = outputArr.join(''); 684 unzipped[files][1] = nameBuf.join(''); 685 files++; 686 } 687 688 if (skipdir()) { 689 // We are beyond the files' data in the zip archive. 690 // Let's get out immediately... 691 return false; 692 } 693 } 694 return true; 695 } 696 } catch (e) { 697 throw e; 698 } 699 return false; 700 } 701 702 703 /** 704 * Test if the end of the files' data part of the archive has baseElement 705 * reached. If not, uncompressing is resumed. 706 * 707 * @return {Boolean} true if the end of the files' data sections have 708 * been reached. 709 * 710 * @private 711 */ 712 function skipdir() { 713 var crc, compSize, size, os, i, c, 714 tmp = []; 715 716 if ((gpflags & 8)) { 717 tmp[0] = readByte(); 718 tmp[1] = readByte(); 719 tmp[2] = readByte(); 720 tmp[3] = readByte(); 721 722 // signature for data descriptor record: 0x08074b50 723 // 12 bytes: 724 // crc 4 bytes 725 // compressed size 4 bytes 726 // uncompressed size 4 bytes 727 if (tmp[0] === 0x50 && 728 tmp[1] === 0x4b && 729 tmp[2] === 0x07 && 730 tmp[3] === 0x08) { 731 crc = readByte(); 732 crc |= (readByte() << 8); 733 crc |= (readByte() << 16); 734 crc |= (readByte() << 24); 735 } else { 736 crc = tmp[0] | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24); 737 } 738 739 compSize = readByte(); 740 compSize |= (readByte() << 8); 741 compSize |= (readByte() << 16); 742 compSize |= (readByte() << 24); 743 744 size = readByte(); 745 size |= (readByte() << 8); 746 size |= (readByte() << 16); 747 size |= (readByte() << 24); 748 } 749 750 if (modeZIP) { 751 if (nextFile()) { 752 // A file has been decompressed, we have to proceed 753 return false; 754 } 755 } 756 757 tmp[0] = readByte(); 758 if (tmp[0] !== 8) { 759 // It seems, we are beyond the files' data in the zip archive. 760 // We'll skip the rest.. 761 return true; 762 } 763 764 // There is another file in the zip file. We proceed... 765 gpflags = readByte(); 766 767 readByte(); 768 readByte(); 769 readByte(); 770 readByte(); 771 772 readByte(); 773 os = readByte(); 774 775 if ((gpflags & 4)) { 776 tmp[0] = readByte(); 777 tmp[2] = readByte(); 778 len = tmp[0] + 256 * tmp[1]; 779 for (i = 0; i < len; i++) { 780 readByte(); 781 } 782 } 783 784 if ((gpflags & 8)) { 785 i = 0; 786 nameBuf = []; 787 788 c = readByte(); 789 while (c) { 790 if (c === '7' || c === ':') { 791 i = 0; 792 } 793 794 if (i < NAMEMAX - 1) { 795 nameBuf[i++] = c; 796 } 797 798 c = readByte(); 799 } 800 } 801 802 if ((gpflags & 16)) { 803 c = readByte(); 804 while (c) { 805 c = readByte(); 806 } 807 } 808 809 if ((gpflags & 2)) { 810 readByte(); 811 readByte(); 812 } 813 814 deflateLoop(); 815 816 crc = readByte(); 817 crc |= (readByte() << 8); 818 crc |= (readByte() << 16); 819 crc |= (readByte() << 24); 820 821 size = readByte(); 822 size |= (readByte() << 8); 823 size |= (readByte() << 16); 824 size |= (readByte() << 24); 825 826 if (modeZIP) { 827 if (nextFile()) { 828 // A file has been decompressed, we have to proceed 829 return false; 830 } 831 } 832 833 // We are here in non-ZIP-files only, 834 // In that case the eturn value doesn't matter 835 return false; 836 } 837 838 JXG.Util.Unzip.prototype.unzipFile = function (name) { 839 var i; 840 841 this.unzip(); 842 843 for (i = 0; i < unzipped.length; i++) { 844 if (unzipped[i][1] === name) { 845 return unzipped[i][0]; 846 } 847 } 848 849 return ''; 850 }; 851 852 JXG.Util.Unzip.prototype.unzip = function () { 853 nextFile(); 854 return unzipped; 855 }; 856 }; 857 858 return JXG.Util; 859 }); 860