1 /* 2 * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.zip; 27 28 import java.io.Closeable; 29 import java.io.InputStream; 30 import java.io.IOException; 31 import java.io.EOFException; 32 import java.io.File; 33 import java.io.RandomAccessFile; 34 import java.nio.charset.Charset; 35 import java.nio.charset.StandardCharsets; 36 import java.nio.file.attribute.BasicFileAttributes; 37 import java.nio.file.Path; 38 import java.nio.file.Files; 39 40 import java.util.ArrayDeque; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Deque; 44 import java.util.Enumeration; 45 import java.util.HashMap; 46 import java.util.Iterator; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.NoSuchElementException; 50 import java.util.Spliterator; 51 import java.util.Spliterators; 52 import java.util.WeakHashMap; 53 import java.util.stream.Stream; 54 import java.util.stream.StreamSupport; 55 import jdk.internal.misc.JavaUtilZipFileAccess; 56 import jdk.internal.misc.SharedSecrets; 57 58 import static java.util.zip.ZipConstants.*; 59 import static java.util.zip.ZipConstants64.*; 60 import static java.util.zip.ZipUtils.*; 61 62 /** 63 * This class is used to read entries from a zip file. 64 * 65 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor 66 * or method in this class will cause a {@link NullPointerException} to be 67 * thrown. 68 * 69 * @author David Connelly 70 */ 71 public 72 class ZipFile implements ZipConstants, Closeable { 73 74 private final String name; // zip file name 75 private volatile boolean closeRequested = false; 76 private Source zsrc; 77 private ZipCoder zc; 78 79 private static final int STORED = ZipEntry.STORED; 80 private static final int DEFLATED = ZipEntry.DEFLATED; 81 82 /** 83 * Mode flag to open a zip file for reading. 84 */ 85 public static final int OPEN_READ = 0x1; 86 87 /** 88 * Mode flag to open a zip file and mark it for deletion. The file will be 89 * deleted some time between the moment that it is opened and the moment 90 * that it is closed, but its contents will remain accessible via the 91 * {@code ZipFile} object until either the close method is invoked or the 92 * virtual machine exits. 93 */ 94 public static final int OPEN_DELETE = 0x4; 95 96 /** 97 * Opens a zip file for reading. 98 * 99 * <p>First, if there is a security manager, its {@code checkRead} 100 * method is called with the {@code name} argument as its argument 101 * to ensure the read is allowed. 102 * 103 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 104 * decode the entry names and comments. 105 * 106 * @param name the name of the zip file 107 * @throws ZipException if a ZIP format error has occurred 108 * @throws IOException if an I/O error has occurred 109 * @throws SecurityException if a security manager exists and its 110 * {@code checkRead} method doesn't allow read access to the file. 111 * 112 * @see SecurityManager#checkRead(java.lang.String) 113 */ 114 public ZipFile(String name) throws IOException { 115 this(new File(name), OPEN_READ); 116 } 117 118 /** 119 * Opens a new {@code ZipFile} to read from the specified 120 * {@code File} object in the specified mode. The mode argument 121 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 122 * 123 * <p>First, if there is a security manager, its {@code checkRead} 124 * method is called with the {@code name} argument as its argument to 125 * ensure the read is allowed. 126 * 127 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 128 * decode the entry names and comments 129 * 130 * @param file the ZIP file to be opened for reading 131 * @param mode the mode in which the file is to be opened 132 * @throws ZipException if a ZIP format error has occurred 133 * @throws IOException if an I/O error has occurred 134 * @throws SecurityException if a security manager exists and 135 * its {@code checkRead} method 136 * doesn't allow read access to the file, 137 * or its {@code checkDelete} method doesn't allow deleting 138 * the file when the {@code OPEN_DELETE} flag is set. 139 * @throws IllegalArgumentException if the {@code mode} argument is invalid 140 * @see SecurityManager#checkRead(java.lang.String) 141 * @since 1.3 142 */ 143 public ZipFile(File file, int mode) throws IOException { 144 this(file, mode, StandardCharsets.UTF_8); 145 } 146 147 /** 148 * Opens a ZIP file for reading given the specified File object. 149 * 150 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 151 * decode the entry names and comments. 152 * 153 * @param file the ZIP file to be opened for reading 154 * @throws ZipException if a ZIP format error has occurred 155 * @throws IOException if an I/O error has occurred 156 */ 157 public ZipFile(File file) throws ZipException, IOException { 158 this(file, OPEN_READ); 159 } 160 161 /** 162 * Opens a new {@code ZipFile} to read from the specified 163 * {@code File} object in the specified mode. The mode argument 164 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 165 * 166 * <p>First, if there is a security manager, its {@code checkRead} 167 * method is called with the {@code name} argument as its argument to 168 * ensure the read is allowed. 169 * 170 * @param file the ZIP file to be opened for reading 171 * @param mode the mode in which the file is to be opened 172 * @param charset 173 * the {@linkplain java.nio.charset.Charset charset} to 174 * be used to decode the ZIP entry name and comment that are not 175 * encoded by using UTF-8 encoding (indicated by entry's general 176 * purpose flag). 177 * 178 * @throws ZipException if a ZIP format error has occurred 179 * @throws IOException if an I/O error has occurred 180 * 181 * @throws SecurityException 182 * if a security manager exists and its {@code checkRead} 183 * method doesn't allow read access to the file,or its 184 * {@code checkDelete} method doesn't allow deleting the 185 * file when the {@code OPEN_DELETE} flag is set 186 * 187 * @throws IllegalArgumentException if the {@code mode} argument is invalid 188 * 189 * @see SecurityManager#checkRead(java.lang.String) 190 * 191 * @since 1.7 192 */ 193 public ZipFile(File file, int mode, Charset charset) throws IOException 194 { 195 if (((mode & OPEN_READ) == 0) || 196 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { 197 throw new IllegalArgumentException("Illegal mode: 0x"+ 198 Integer.toHexString(mode)); 199 } 200 String name = file.getPath(); 201 SecurityManager sm = System.getSecurityManager(); 202 if (sm != null) { 203 sm.checkRead(name); 204 if ((mode & OPEN_DELETE) != 0) { 205 sm.checkDelete(name); 206 } 207 } 208 Objects.requireNonNull(charset, "charset"); 209 this.zc = ZipCoder.get(charset); 210 this.name = name; 211 long t0 = System.nanoTime(); 212 this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0); 213 sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); 214 sun.misc.PerfCounter.getZipFileCount().increment(); 215 } 216 217 /** 218 * Opens a zip file for reading. 219 * 220 * <p>First, if there is a security manager, its {@code checkRead} 221 * method is called with the {@code name} argument as its argument 222 * to ensure the read is allowed. 223 * 224 * @param name the name of the zip file 225 * @param charset 226 * the {@linkplain java.nio.charset.Charset charset} to 227 * be used to decode the ZIP entry name and comment that are not 228 * encoded by using UTF-8 encoding (indicated by entry's general 229 * purpose flag). 230 * 231 * @throws ZipException if a ZIP format error has occurred 232 * @throws IOException if an I/O error has occurred 233 * @throws SecurityException 234 * if a security manager exists and its {@code checkRead} 235 * method doesn't allow read access to the file 236 * 237 * @see SecurityManager#checkRead(java.lang.String) 238 * 239 * @since 1.7 240 */ 241 public ZipFile(String name, Charset charset) throws IOException 242 { 243 this(new File(name), OPEN_READ, charset); 244 } 245 246 /** 247 * Opens a ZIP file for reading given the specified File object. 248 * 249 * @param file the ZIP file to be opened for reading 250 * @param charset 251 * The {@linkplain java.nio.charset.Charset charset} to be 252 * used to decode the ZIP entry name and comment (ignored if 253 * the <a href="package-summary.html#lang_encoding"> language 254 * encoding bit</a> of the ZIP entry's general purpose bit 255 * flag is set). 256 * 257 * @throws ZipException if a ZIP format error has occurred 258 * @throws IOException if an I/O error has occurred 259 * 260 * @since 1.7 261 */ 262 public ZipFile(File file, Charset charset) throws IOException 263 { 264 this(file, OPEN_READ, charset); 265 } 266 267 /** 268 * Returns the zip file comment, or null if none. 269 * 270 * @return the comment string for the zip file, or null if none 271 * 272 * @throws IllegalStateException if the zip file has been closed 273 * 274 * Since 1.7 275 */ 276 public String getComment() { 277 synchronized (this) { 278 ensureOpen(); 279 if (zsrc.comment == null) { 280 return null; 281 } 282 return zc.toString(zsrc.comment); 283 } 284 } 285 286 /** 287 * Returns the zip file entry for the specified name, or null 288 * if not found. 289 * 290 * @param name the name of the entry 291 * @return the zip file entry, or null if not found 292 * @throws IllegalStateException if the zip file has been closed 293 */ 294 public ZipEntry getEntry(String name) { 295 296 Objects.requireNonNull(name, "name"); 297 synchronized (this) { 298 ensureOpen(); 299 int pos = zsrc.getEntryPos(zc.getBytes(name), true); 300 if (pos != -1) { 301 return getZipEntry(name, pos); 302 } 303 } 304 return null; 305 } 306 307 // The outstanding inputstreams that need to be closed, 308 // mapped to the inflater objects they use. 309 private final Map<InputStream, Inflater> streams = new WeakHashMap<>(); 310 311 /** 312 * Returns an input stream for reading the contents of the specified 313 * zip file entry. 314 * <p> 315 * Closing this ZIP file will, in turn, close all input streams that 316 * have been returned by invocations of this method. 317 * 318 * @param entry the zip file entry 319 * @return the input stream for reading the contents of the specified 320 * zip file entry. 321 * @throws ZipException if a ZIP format error has occurred 322 * @throws IOException if an I/O error has occurred 323 * @throws IllegalStateException if the zip file has been closed 324 */ 325 public InputStream getInputStream(ZipEntry entry) throws IOException { 326 return getInputStream(entry, null); 327 } 328 329 /** 330 * Returns an input stream for reading the contents of the specified 331 * zip file entry. 332 * <p> 333 * Closing this ZIP file will, in turn, close all input streams that 334 * have been returned by invocations of this method. 335 * 336 * @param entry the zip file entry 337 * @param zipCryption ZIP encrypt/decrypt engine. zip decryption will not 338 * work if this value set to null. 339 * @return the input stream for reading the contents of the specified 340 * zip file entry. 341 * @throws ZipException if a ZIP format error has occurred 342 * @throws IOException if an I/O error has occurred 343 * @throws IllegalStateException if the zip file has been closed 344 * @since 1.9 345 */ 346 public InputStream getInputStream(ZipEntry entry, ZipCryption zipCryption) 347 throws IOException { 348 Objects.requireNonNull(entry, "entry"); 349 350 if ((entry.flag & 1) == 1) { 351 Objects.requireNonNull(entry, "Passphrase is required"); 352 zipCryption.reset(); 353 } 354 355 int pos = -1; 356 ZipFileInputStream in = null; 357 synchronized (this) { 358 ensureOpen(); 359 if (!zc.isUTF8() && (entry.flag & EFS) != 0) { 360 pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false); 361 } else { 362 pos = zsrc.getEntryPos(zc.getBytes(entry.name), false); 363 } 364 if (pos == -1) { 365 return null; 366 } 367 in = new ZipFileInputStream(zsrc.cen, pos, zipCryption); 368 switch (CENHOW(zsrc.cen, pos)) { 369 case STORED: 370 if((entry.flag & 1) == 1) { 371 byte[] encryptionHeader = 372 new byte[zipCryption.getEncryptionHeaderSize()]; 373 in.readRaw(encryptionHeader, 0, encryptionHeader.length); 374 zipCryption.decryptBytes(encryptionHeader); 375 376 if (!zipCryption.isValid(entry, encryptionHeader)) { 377 throw new ZipException("possibly incorrect passphrase"); 378 } 379 } 380 synchronized (streams) { 381 streams.put(in, null); 382 } 383 return in; 384 case DEFLATED: 385 // Inflater likes a bit of slack 386 // MORE: Compute good size for inflater stream: 387 long size = CENLEN(zsrc.cen, pos) + 2; 388 if (size > 65536) { 389 size = 8192; 390 } 391 if (size <= 0) { 392 size = 4096; 393 } 394 Inflater inf = getInflater(); 395 396 if((entry.flag & 1) == 1) { 397 byte[] encryptionHeader = 398 new byte[zipCryption.getEncryptionHeaderSize()]; 399 in.readRaw(encryptionHeader, 0, encryptionHeader.length); 400 zipCryption.decryptBytes(encryptionHeader); 401 402 if (!zipCryption.isValid(entry, encryptionHeader)) { 403 throw new ZipException("possibly incorrect passphrase"); 404 } 405 } 406 407 InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size); 408 synchronized (streams) { 409 streams.put(is, inf); 410 } 411 return is; 412 default: 413 throw new ZipException("invalid compression method"); 414 } 415 } 416 } 417 418 private class ZipFileInflaterInputStream extends InflaterInputStream { 419 private volatile boolean closeRequested = false; 420 private boolean eof = false; 421 private final ZipFileInputStream zfin; 422 423 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, 424 int size) { 425 super(zfin, inf, size); 426 this.zfin = zfin; 427 } 428 429 public void close() throws IOException { 430 if (closeRequested) 431 return; 432 closeRequested = true; 433 434 super.close(); 435 Inflater inf; 436 synchronized (streams) { 437 inf = streams.remove(this); 438 } 439 if (inf != null) { 440 releaseInflater(inf); 441 } 442 } 443 444 // Override fill() method to provide an extra "dummy" byte 445 // at the end of the input stream. This is required when 446 // using the "nowrap" Inflater option. 447 protected void fill() throws IOException { 448 if (eof) { 449 throw new EOFException("Unexpected end of ZLIB input stream"); 450 } 451 len = in.read(buf, 0, buf.length); 452 if (len == -1) { 453 buf[0] = 0; 454 len = 1; 455 eof = true; 456 } 457 inf.setInput(buf, 0, len); 458 } 459 460 public int available() throws IOException { 461 if (closeRequested) 462 return 0; 463 long avail = zfin.size() - inf.getBytesWritten(); 464 return (avail > (long) Integer.MAX_VALUE ? 465 Integer.MAX_VALUE : (int) avail); 466 } 467 468 protected void finalize() throws Throwable { 469 close(); 470 } 471 } 472 473 /* 474 * Gets an inflater from the list of available inflaters or allocates 475 * a new one. 476 */ 477 private Inflater getInflater() { 478 Inflater inf; 479 synchronized (inflaterCache) { 480 while ((inf = inflaterCache.poll()) != null) { 481 if (!inf.ended()) { 482 return inf; 483 } 484 } 485 } 486 return new Inflater(true); 487 } 488 489 /* 490 * Releases the specified inflater to the list of available inflaters. 491 */ 492 private void releaseInflater(Inflater inf) { 493 if (!inf.ended()) { 494 inf.reset(); 495 synchronized (inflaterCache) { 496 inflaterCache.add(inf); 497 } 498 } 499 } 500 501 // List of available Inflater objects for decompression 502 private final Deque<Inflater> inflaterCache = new ArrayDeque<>(); 503 504 /** 505 * Returns the path name of the ZIP file. 506 * @return the path name of the ZIP file 507 */ 508 public String getName() { 509 return name; 510 } 511 512 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { 513 private int i = 0; 514 515 public ZipEntryIterator() { 516 ensureOpen(); 517 } 518 519 public boolean hasMoreElements() { 520 return hasNext(); 521 } 522 523 public boolean hasNext() { 524 synchronized (ZipFile.this) { 525 ensureOpen(); 526 return i < zsrc.total; 527 } 528 } 529 530 public ZipEntry nextElement() { 531 return next(); 532 } 533 534 public ZipEntry next() { 535 synchronized (ZipFile.this) { 536 ensureOpen(); 537 if (i >= zsrc.total) { 538 throw new NoSuchElementException(); 539 } 540 // each "entry" has 3 ints in table entries 541 return getZipEntry(null, zsrc.getEntryPos(i++ * 3)); 542 } 543 } 544 545 public Iterator<ZipEntry> asIterator() { 546 return this; 547 } 548 } 549 550 /** 551 * Returns an enumeration of the ZIP file entries. 552 * @return an enumeration of the ZIP file entries 553 * @throws IllegalStateException if the zip file has been closed 554 */ 555 public Enumeration<? extends ZipEntry> entries() { 556 return new ZipEntryIterator(); 557 } 558 559 /** 560 * Returns an ordered {@code Stream} over the ZIP file entries. 561 * Entries appear in the {@code Stream} in the order they appear in 562 * the central directory of the ZIP file. 563 * 564 * @return an ordered {@code Stream} of entries in this ZIP file 565 * @throws IllegalStateException if the zip file has been closed 566 * @since 1.8 567 */ 568 public Stream<? extends ZipEntry> stream() { 569 return StreamSupport.stream(Spliterators.spliterator( 570 new ZipEntryIterator(), size(), 571 Spliterator.ORDERED | Spliterator.DISTINCT | 572 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 573 } 574 575 /* Checks ensureOpen() before invoke this method */ 576 private ZipEntry getZipEntry(String name, int pos) { 577 byte[] cen = zsrc.cen; 578 ZipEntry e = new ZipEntry(); 579 int nlen = CENNAM(cen, pos); 580 int elen = CENEXT(cen, pos); 581 int clen = CENCOM(cen, pos); 582 e.flag = CENFLG(cen, pos); // get the flag first 583 if (name != null) { 584 e.name = name; 585 } else { 586 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 587 e.name = zc.toStringUTF8(cen, pos + CENHDR, nlen); 588 } else { 589 e.name = zc.toString(cen, pos + CENHDR, nlen); 590 } 591 } 592 e.xdostime = CENTIM(cen, pos); 593 e.crc = CENCRC(cen, pos); 594 e.size = CENLEN(cen, pos); 595 e.csize = CENSIZ(cen, pos); 596 e.method = CENHOW(cen, pos); 597 if (elen != 0) { 598 e.setExtra0(Arrays.copyOfRange(cen, pos + CENHDR + nlen, 599 pos + CENHDR + nlen + elen), true); 600 } 601 if (clen != 0) { 602 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 603 e.comment = zc.toStringUTF8(cen, pos + CENHDR + nlen + elen, clen); 604 } else { 605 e.comment = zc.toString(cen, pos + CENHDR + nlen + elen, clen); 606 } 607 } 608 return e; 609 } 610 611 /** 612 * Returns the number of entries in the ZIP file. 613 * 614 * @return the number of entries in the ZIP file 615 * @throws IllegalStateException if the zip file has been closed 616 */ 617 public int size() { 618 synchronized (this) { 619 ensureOpen(); 620 return zsrc.total; 621 } 622 } 623 624 /** 625 * Closes the ZIP file. 626 * <p> Closing this ZIP file will close all of the input streams 627 * previously returned by invocations of the {@link #getInputStream 628 * getInputStream} method. 629 * 630 * @throws IOException if an I/O error has occurred 631 */ 632 public void close() throws IOException { 633 if (closeRequested) { 634 return; 635 } 636 closeRequested = true; 637 638 synchronized (this) { 639 // Close streams, release their inflaters 640 synchronized (streams) { 641 if (!streams.isEmpty()) { 642 Map<InputStream, Inflater> copy = new HashMap<>(streams); 643 streams.clear(); 644 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { 645 e.getKey().close(); 646 Inflater inf = e.getValue(); 647 if (inf != null) { 648 inf.end(); 649 } 650 } 651 } 652 } 653 // Release cached inflaters 654 synchronized (inflaterCache) { 655 Inflater inf; 656 while ((inf = inflaterCache.poll()) != null) { 657 inf.end(); 658 } 659 } 660 // Release zip src 661 if (zsrc != null) { 662 Source.close(zsrc); 663 zsrc = null; 664 } 665 } 666 } 667 668 /** 669 * Ensures that the system resources held by this ZipFile object are 670 * released when there are no more references to it. 671 * 672 * <p> 673 * Since the time when GC would invoke this method is undetermined, 674 * it is strongly recommended that applications invoke the {@code close} 675 * method as soon they have finished accessing this {@code ZipFile}. 676 * This will prevent holding up system resources for an undetermined 677 * length of time. 678 * 679 * @throws IOException if an I/O error has occurred 680 * @see java.util.zip.ZipFile#close() 681 */ 682 protected void finalize() throws IOException { 683 close(); 684 } 685 686 private void ensureOpen() { 687 if (closeRequested) { 688 throw new IllegalStateException("zip file closed"); 689 } 690 if (zsrc == null) { 691 throw new IllegalStateException("The object is not initialized."); 692 } 693 } 694 695 private void ensureOpenOrZipException() throws IOException { 696 if (closeRequested) { 697 throw new ZipException("ZipFile closed"); 698 } 699 } 700 701 /* 702 * Inner class implementing the input stream used to read a 703 * (possibly compressed) zip file entry. 704 */ 705 private class ZipFileInputStream extends InputStream { 706 private volatile boolean closeRequested = false; 707 private long pos; // current position within entry data 708 protected long rem; // number of remaining bytes within entry 709 protected long size; // uncompressed size of this entry 710 private ZipCryption zipCryption; // ZIP encrypt/decrypt engine 711 712 ZipFileInputStream(byte[] cen, int cenpos, ZipCryption zipCryption) 713 throws IOException { 714 rem = CENSIZ(cen, cenpos); 715 size = CENLEN(cen, cenpos); 716 pos = CENOFF(cen, cenpos); 717 // zip64 718 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || 719 pos == ZIP64_MAGICVAL) { 720 checkZIP64(cen, cenpos); 721 } 722 // negative for lazy initialization, see getDataOffset(); 723 pos = - (pos + ZipFile.this.zsrc.locpos); 724 this.zipCryption = zipCryption; 725 } 726 727 private void checkZIP64(byte[] cen, int cenpos) throws IOException { 728 int off = cenpos + CENHDR + CENNAM(cen, cenpos); 729 int end = off + CENEXT(cen, cenpos); 730 while (off + 4 < end) { 731 int tag = get16(cen, off); 732 int sz = get16(cen, off + 2); 733 off += 4; 734 if (off + sz > end) // invalid data 735 break; 736 if (tag == EXTID_ZIP64) { 737 if (size == ZIP64_MAGICVAL) { 738 if (sz < 8 || (off + 8) > end) 739 break; 740 size = get64(cen, off); 741 sz -= 8; 742 off += 8; 743 } 744 if (rem == ZIP64_MAGICVAL) { 745 if (sz < 8 || (off + 8) > end) 746 break; 747 rem = get64(cen, off); 748 sz -= 8; 749 off += 8; 750 } 751 if (pos == ZIP64_MAGICVAL) { 752 if (sz < 8 || (off + 8) > end) 753 break; 754 pos = get64(cen, off); 755 sz -= 8; 756 off += 8; 757 } 758 break; 759 } 760 off += sz; 761 } 762 } 763 764 /* The Zip file spec explicitly allows the LOC extra data size to 765 * be different from the CEN extra data size. Since we cannot trust 766 * the CEN extra data size, we need to read the LOC to determine 767 * the entry data offset. 768 */ 769 private long initDataOffset() throws IOException { 770 if (pos <= 0) { 771 byte[] loc = new byte[LOCHDR]; 772 pos = -pos; 773 int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos); 774 if (len != LOCHDR) { 775 throw new ZipException("ZipFile error reading zip file"); 776 } 777 if (LOCSIG(loc) != LOCSIG) { 778 throw new ZipException("ZipFile invalid LOC header (bad signature)"); 779 } 780 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc); 781 } 782 return pos; 783 } 784 785 public int read(byte b[], int off, int len) throws IOException { 786 len = readRaw(b, off, len); 787 788 if (zipCryption != null) { 789 zipCryption.decryptBytes(b, off, len); 790 } 791 792 return len; 793 } 794 795 public int readRaw(byte b[], int off, int len) throws IOException { 796 synchronized (ZipFile.this) { 797 ensureOpenOrZipException(); 798 initDataOffset(); 799 if (rem == 0) { 800 return -1; 801 } 802 if (len > rem) { 803 len = (int) rem; 804 } 805 if (len <= 0) { 806 return 0; 807 } 808 len = ZipFile.this.zsrc.readAt(b, off, len, pos); 809 if (len > 0) { 810 pos += len; 811 rem -= len; 812 } 813 } 814 if (rem == 0) { 815 close(); 816 } 817 return len; 818 } 819 820 public int read() throws IOException { 821 byte[] b = new byte[1]; 822 if (read(b, 0, 1) == 1) { 823 return b[0] & 0xff; 824 } else { 825 return -1; 826 } 827 } 828 829 public long skip(long n) throws IOException { 830 synchronized (ZipFile.this) { 831 ensureOpenOrZipException(); 832 initDataOffset(); 833 if (n > rem) { 834 n = rem; 835 } 836 pos += n; 837 rem -= n; 838 } 839 if (rem == 0) { 840 close(); 841 } 842 return n; 843 } 844 845 public int available() { 846 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 847 } 848 849 public long size() { 850 return size; 851 } 852 853 public void close() { 854 if (closeRequested) { 855 return; 856 } 857 closeRequested = true; 858 rem = 0; 859 synchronized (streams) { 860 streams.remove(this); 861 } 862 } 863 864 protected void finalize() { 865 close(); 866 } 867 } 868 869 static { 870 SharedSecrets.setJavaUtilZipFileAccess( 871 new JavaUtilZipFileAccess() { 872 public boolean startsWithLocHeader(ZipFile zip) { 873 return zip.zsrc.startsWithLoc; 874 } 875 public String[] getMetaInfEntryNames(ZipFile zip) { 876 return zip.getMetaInfEntryNames(); 877 } 878 } 879 ); 880 } 881 882 /* 883 * Returns an array of strings representing the names of all entries 884 * that begin with "META-INF/" (case ignored). This method is used 885 * in JarFile, via SharedSecrets, as an optimization when looking up 886 * manifest and signature file entries. Returns null if no entries 887 * were found. 888 */ 889 private String[] getMetaInfEntryNames() { 890 synchronized (this) { 891 ensureOpen(); 892 if (zsrc.metanames.size() == 0) { 893 return null; 894 } 895 String[] names = new String[zsrc.metanames.size()]; 896 byte[] cen = zsrc.cen; 897 for (int i = 0; i < names.length; i++) { 898 int pos = zsrc.metanames.get(i); 899 names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos), 900 StandardCharsets.UTF_8); 901 } 902 return names; 903 } 904 } 905 906 private static class Source { 907 private final Key key; // the key in files 908 private int refs = 1; 909 910 private RandomAccessFile zfile; // zfile of the underlying zip file 911 private byte[] cen; // CEN & ENDHDR 912 private long locpos; // position of first LOC header (usually 0) 913 private byte[] comment; // zip file comment 914 // list of meta entries in META-INF dir 915 private ArrayList<Integer> metanames = new ArrayList<>(); 916 private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) 917 918 // A Hashmap for all entries. 919 // 920 // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR, 921 // We might have a lot of these in a typical system. In order to save space we don't 922 // keep the name in memory, but merely remember a 32 bit {@code hash} value of the 923 // entry name and its offset {@code pos} in the central directory hdeader. 924 // 925 // private static class Entry { 926 // int hash; // 32 bit hashcode on name 927 // int next; // hash chain: index into entries 928 // int pos; // Offset of central directory file header 929 // } 930 // private Entry[] entries; // array of hashed cen entry 931 // 932 // To reduce the total size of entries further, we use a int[] here to store 3 "int" 933 // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be 934 // referred by their index of their positions in the {@code entries}. 935 // 936 private int[] entries; // array of hashed cen entry 937 private int addEntry(int index, int hash, int next, int pos) { 938 entries[index++] = hash; 939 entries[index++] = next; 940 entries[index++] = pos; 941 return index; 942 } 943 private int getEntryHash(int index) { return entries[index]; } 944 private int getEntryNext(int index) { return entries[index + 1]; } 945 private int getEntryPos(int index) { return entries[index + 2]; } 946 private static final int ZIP_ENDCHAIN = -1; 947 private int total; // total number of entries 948 private int[] table; // Hash chain heads: indexes into entries 949 private int tablelen; // number of hash heads 950 951 private static class Key { 952 BasicFileAttributes attrs; 953 File file; 954 955 public Key(File file, BasicFileAttributes attrs) { 956 this.attrs = attrs; 957 this.file = file; 958 } 959 960 public int hashCode() { 961 long t = attrs.lastModifiedTime().toMillis(); 962 return ((int)(t ^ (t >>> 32))) + file.hashCode(); 963 } 964 965 public boolean equals(Object obj) { 966 if (obj instanceof Key) { 967 Key key = (Key)obj; 968 if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { 969 return false; 970 } 971 Object fk = attrs.fileKey(); 972 if (fk != null) { 973 return fk.equals(key.attrs.fileKey()); 974 } else { 975 return file.equals(key.file); 976 } 977 } 978 return false; 979 } 980 } 981 private static final HashMap<Key, Source> files = new HashMap<>(); 982 983 984 public static Source get(File file, boolean toDelete) throws IOException { 985 Key key = new Key(file, 986 Files.readAttributes(file.toPath(), BasicFileAttributes.class)); 987 Source src = null; 988 synchronized (files) { 989 src = files.get(key); 990 if (src != null) { 991 src.refs++; 992 return src; 993 } 994 } 995 src = new Source(key, toDelete); 996 997 synchronized (files) { 998 if (files.containsKey(key)) { // someone else put in first 999 src.close(); // close the newly created one 1000 src = files.get(key); 1001 src.refs++; 1002 return src; 1003 } 1004 files.put(key, src); 1005 return src; 1006 } 1007 } 1008 1009 private static void close(Source src) throws IOException { 1010 synchronized (files) { 1011 if (--src.refs == 0) { 1012 files.remove(src.key); 1013 src.close(); 1014 } 1015 } 1016 } 1017 1018 private Source(Key key, boolean toDelete) throws IOException { 1019 this.key = key; 1020 this.zfile = new RandomAccessFile(key.file, "r"); 1021 if (toDelete) { 1022 key.file.delete(); 1023 } 1024 try { 1025 initCEN(-1); 1026 byte[] buf = new byte[4]; 1027 readFullyAt(buf, 0, 4, 0); 1028 this.startsWithLoc = (LOCSIG(buf) == LOCSIG); 1029 } catch (IOException x) { 1030 try { 1031 this.zfile.close(); 1032 } catch (IOException xx) {} 1033 throw x; 1034 } 1035 } 1036 1037 private void close() throws IOException { 1038 zfile.close(); 1039 zfile = null; 1040 cen = null; 1041 entries = null; 1042 table = null; 1043 metanames = null; 1044 } 1045 1046 private static final int BUF_SIZE = 8192; 1047 private final int readFullyAt(byte[] buf, int off, int len, long pos) 1048 throws IOException 1049 { 1050 synchronized(zfile) { 1051 zfile.seek(pos); 1052 int N = len; 1053 while (N > 0) { 1054 int n = Math.min(BUF_SIZE, N); 1055 zfile.readFully(buf, off, n); 1056 off += n; 1057 N -= n; 1058 } 1059 return len; 1060 } 1061 } 1062 1063 private final int readAt(byte[] buf, int off, int len, long pos) 1064 throws IOException 1065 { 1066 synchronized(zfile) { 1067 zfile.seek(pos); 1068 return zfile.read(buf, off, len); 1069 } 1070 } 1071 1072 private static final int hashN(byte[] a, int off, int len) { 1073 int h = 1; 1074 while (len-- > 0) { 1075 h = 31 * h + a[off++]; 1076 } 1077 return h; 1078 } 1079 1080 private static final int hash_append(int hash, byte b) { 1081 return hash * 31 + b; 1082 } 1083 1084 private static class End { 1085 int centot; // 4 bytes 1086 long cenlen; // 4 bytes 1087 long cenoff; // 4 bytes 1088 long endpos; // 4 bytes 1089 } 1090 1091 /* 1092 * Searches for end of central directory (END) header. The contents of 1093 * the END header will be read and placed in endbuf. Returns the file 1094 * position of the END header, otherwise returns -1 if the END header 1095 * was not found or an error occurred. 1096 */ 1097 private End findEND() throws IOException { 1098 long ziplen = zfile.length(); 1099 if (ziplen <= 0) 1100 zerror("zip file is empty"); 1101 End end = new End(); 1102 byte[] buf = new byte[READBLOCKSZ]; 1103 long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0; 1104 long minPos = minHDR - (buf.length - ENDHDR); 1105 for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) { 1106 int off = 0; 1107 if (pos < 0) { 1108 // Pretend there are some NUL bytes before start of file 1109 off = (int)-pos; 1110 Arrays.fill(buf, 0, off, (byte)0); 1111 } 1112 int len = buf.length - off; 1113 if (readFullyAt(buf, off, len, pos + off) != len ) { 1114 zerror("zip END header not found"); 1115 } 1116 // Now scan the block backwards for END header signature 1117 for (int i = buf.length - ENDHDR; i >= 0; i--) { 1118 if (buf[i+0] == (byte)'P' && 1119 buf[i+1] == (byte)'K' && 1120 buf[i+2] == (byte)'\005' && 1121 buf[i+3] == (byte)'\006') { 1122 // Found ENDSIG header 1123 byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR); 1124 end.centot = ENDTOT(endbuf); 1125 end.cenlen = ENDSIZ(endbuf); 1126 end.cenoff = ENDOFF(endbuf); 1127 end.endpos = pos + i; 1128 int comlen = ENDCOM(endbuf); 1129 if (end.endpos + ENDHDR + comlen != ziplen) { 1130 // ENDSIG matched, however the size of file comment in it does 1131 // not match the real size. One "common" cause for this problem 1132 // is some "extra" bytes are padded at the end of the zipfile. 1133 // Let's do some extra verification, we don't care about the 1134 // performance in this situation. 1135 byte[] sbuf = new byte[4]; 1136 long cenpos = end.endpos - end.cenlen; 1137 long locpos = cenpos - end.cenoff; 1138 if (cenpos < 0 || 1139 locpos < 0 || 1140 readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 || 1141 GETSIG(sbuf) != CENSIG || 1142 readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 || 1143 GETSIG(sbuf) != LOCSIG) { 1144 continue; 1145 } 1146 } 1147 if (comlen > 0) { // this zip file has comlen 1148 comment = new byte[comlen]; 1149 if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) { 1150 zerror("zip comment read failed"); 1151 } 1152 } 1153 if (end.cenlen == ZIP64_MAGICVAL || 1154 end.cenoff == ZIP64_MAGICVAL || 1155 end.centot == ZIP64_MAGICCOUNT) 1156 { 1157 // need to find the zip64 end; 1158 try { 1159 byte[] loc64 = new byte[ZIP64_LOCHDR]; 1160 if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) 1161 != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { 1162 return end; 1163 } 1164 long end64pos = ZIP64_LOCOFF(loc64); 1165 byte[] end64buf = new byte[ZIP64_ENDHDR]; 1166 if (readFullyAt(end64buf, 0, end64buf.length, end64pos) 1167 != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { 1168 return end; 1169 } 1170 // end64 found, re-calcualte everything. 1171 end.cenlen = ZIP64_ENDSIZ(end64buf); 1172 end.cenoff = ZIP64_ENDOFF(end64buf); 1173 end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g 1174 end.endpos = end64pos; 1175 } catch (IOException x) {} // no zip64 loc/end 1176 } 1177 return end; 1178 } 1179 } 1180 } 1181 zerror("zip END header not found"); 1182 return null; //make compiler happy 1183 } 1184 1185 // Reads zip file central directory. 1186 private void initCEN(int knownTotal) throws IOException { 1187 if (knownTotal == -1) { 1188 End end = findEND(); 1189 if (end.endpos == 0) { 1190 locpos = 0; 1191 total = 0; 1192 entries = new int[0]; 1193 cen = null; 1194 return; // only END header present 1195 } 1196 if (end.cenlen > end.endpos) 1197 zerror("invalid END header (bad central directory size)"); 1198 long cenpos = end.endpos - end.cenlen; // position of CEN table 1199 // Get position of first local file (LOC) header, taking into 1200 // account that there may be a stub prefixed to the zip file. 1201 locpos = cenpos - end.cenoff; 1202 if (locpos < 0) { 1203 zerror("invalid END header (bad central directory offset)"); 1204 } 1205 // read in the CEN and END 1206 cen = new byte[(int)(end.cenlen + ENDHDR)]; 1207 if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { 1208 zerror("read CEN tables failed"); 1209 } 1210 total = end.centot; 1211 } else { 1212 total = knownTotal; 1213 } 1214 // hash table for entries 1215 entries = new int[total * 3]; 1216 tablelen = ((total/2) | 1); // Odd -> fewer collisions 1217 table = new int[tablelen]; 1218 Arrays.fill(table, ZIP_ENDCHAIN); 1219 int idx = 0; 1220 int hash = 0; 1221 int next = -1; 1222 1223 // list for all meta entries 1224 metanames = new ArrayList<>(); 1225 1226 // Iterate through the entries in the central directory 1227 int i = 0; 1228 int hsh = 0; 1229 int pos = 0; 1230 int limit = cen.length - ENDHDR; 1231 while (pos + CENHDR <= limit) { 1232 if (i >= total) { 1233 // This will only happen if the zip file has an incorrect 1234 // ENDTOT field, which usually means it contains more than 1235 // 65535 entries. 1236 initCEN(countCENHeaders(cen, limit)); 1237 return; 1238 } 1239 if (CENSIG(cen, pos) != CENSIG) 1240 zerror("invalid CEN header (bad signature)"); 1241 int method = CENHOW(cen, pos); 1242 int nlen = CENNAM(cen, pos); 1243 int elen = CENEXT(cen, pos); 1244 int clen = CENCOM(cen, pos); 1245 if (method != STORED && method != DEFLATED) 1246 zerror("invalid CEN header (bad compression method: " + method + ")"); 1247 if (pos + CENHDR + nlen > limit) 1248 zerror("invalid CEN header (bad header size)"); 1249 // Record the CEN offset and the name hash in our hash cell. 1250 hash = hashN(cen, pos + CENHDR, nlen); 1251 hsh = (hash & 0x7fffffff) % tablelen; 1252 next = table[hsh]; 1253 table[hsh] = idx; 1254 idx = addEntry(idx, hash, next, pos); 1255 // Adds name to metanames. 1256 if (isMetaName(cen, pos + CENHDR, nlen)) { 1257 metanames.add(pos); 1258 } 1259 // skip ext and comment 1260 pos += (CENHDR + nlen + elen + clen); 1261 i++; 1262 } 1263 total = i; 1264 if (pos + ENDHDR != cen.length) { 1265 zerror("invalid CEN header (bad header size)"); 1266 } 1267 } 1268 1269 private static void zerror(String msg) throws ZipException { 1270 throw new ZipException(msg); 1271 } 1272 1273 /* 1274 * Returns the {@code pos} of the zip cen entry corresponding to the 1275 * specified entry name, or -1 if not found. 1276 */ 1277 private int getEntryPos(byte[] name, boolean addSlash) { 1278 if (total == 0) { 1279 return -1; 1280 } 1281 int hsh = hashN(name, 0, name.length); 1282 int idx = table[(hsh & 0x7fffffff) % tablelen]; 1283 /* 1284 * This while loop is an optimization where a double lookup 1285 * for name and name+/ is being performed. The name char 1286 * array has enough room at the end to try again with a 1287 * slash appended if the first table lookup does not succeed. 1288 */ 1289 while(true) { 1290 /* 1291 * Search down the target hash chain for a entry whose 1292 * 32 bit hash matches the hashed name. 1293 */ 1294 while (idx != ZIP_ENDCHAIN) { 1295 if (getEntryHash(idx) == hsh) { 1296 // The CEN name must match the specfied one 1297 int pos = getEntryPos(idx); 1298 if (name.length == CENNAM(cen, pos)) { 1299 boolean matched = true; 1300 int nameoff = pos + CENHDR; 1301 for (int i = 0; i < name.length; i++) { 1302 if (name[i] != cen[nameoff++]) { 1303 matched = false; 1304 break; 1305 } 1306 } 1307 if (matched) { 1308 return pos; 1309 } 1310 } 1311 } 1312 idx = getEntryNext(idx); 1313 } 1314 /* If not addSlash, or slash is already there, we are done */ 1315 if (!addSlash || name[name.length - 1] == '/') { 1316 return -1; 1317 } 1318 /* Add slash and try once more */ 1319 name = Arrays.copyOf(name, name.length + 1); 1320 name[name.length - 1] = '/'; 1321 hsh = hash_append(hsh, (byte)'/'); 1322 //idx = table[hsh % tablelen]; 1323 idx = table[(hsh & 0x7fffffff) % tablelen]; 1324 addSlash = false; 1325 } 1326 } 1327 1328 private static byte[] metainf = new byte[] { 1329 'M', 'E', 'T', 'A', '-', 'I' , 'N', 'F', '/', 1330 }; 1331 1332 /* 1333 * Returns true if the specified entry's name begins with the string 1334 * "META-INF/" irrespective of case. 1335 */ 1336 private static boolean isMetaName(byte[] name, int off, int len) { 1337 if (len < 9 || (name[off] != 'M' && name[off] != 'm')) { // sizeof("META-INF/") - 1 1338 return false; 1339 } 1340 off++; 1341 for (int i = 1; i < metainf.length; i++) { 1342 byte c = name[off++]; 1343 // Avoid toupper; it's locale-dependent 1344 if (c >= 'a' && c <= 'z') { 1345 c += 'A' - 'a'; 1346 } 1347 if (metainf[i] != c) { 1348 return false; 1349 } 1350 } 1351 return true; 1352 } 1353 1354 /* 1355 * Counts the number of CEN headers in a central directory extending 1356 * from BEG to END. Might return a bogus answer if the zip file is 1357 * corrupt, but will not crash. 1358 */ 1359 static int countCENHeaders(byte[] cen, int end) { 1360 int count = 0; 1361 int pos = 0; 1362 while (pos + CENHDR <= end) { 1363 count++; 1364 pos += (CENHDR + CENNAM(cen, pos) + CENEXT(cen, pos) + CENCOM(cen, pos)); 1365 } 1366 return count; 1367 } 1368 } 1369 }