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