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