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 Objects.requireNonNull(entry, "entry"); 327 int pos = -1; 328 ZipFileInputStream in = null; 329 synchronized (this) { 330 ensureOpen(); 331 if (!zc.isUTF8() && (entry.flag & EFS) != 0) { 332 pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false); 333 } else { 334 pos = zsrc.getEntryPos(zc.getBytes(entry.name), false); 335 } 336 if (pos == -1) { 337 return null; 338 } 339 in = new ZipFileInputStream(zsrc.cen, pos); 340 switch (CENHOW(zsrc.cen, pos)) { 341 case STORED: 342 synchronized (streams) { 343 streams.put(in, null); 344 } 345 return in; 346 case DEFLATED: 347 // Inflater likes a bit of slack 348 // MORE: Compute good size for inflater stream: 349 long size = CENLEN(zsrc.cen, pos) + 2; 350 if (size > 65536) { 351 size = 8192; 352 } 353 if (size <= 0) { 354 size = 4096; 355 } 356 Inflater inf = getInflater(); 357 InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size); 358 synchronized (streams) { 359 streams.put(is, inf); 360 } 361 return is; 362 default: 363 throw new ZipException("invalid compression method"); 364 } 365 } 366 } 367 368 private class ZipFileInflaterInputStream extends InflaterInputStream { 369 private volatile boolean closeRequested = false; 370 private boolean eof = false; 371 private final ZipFileInputStream zfin; 372 373 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, 374 int size) { 375 super(zfin, inf, size); 376 this.zfin = zfin; 377 } 378 379 public void close() throws IOException { 380 if (closeRequested) 381 return; 382 closeRequested = true; 383 384 super.close(); 385 Inflater inf; 386 synchronized (streams) { 387 inf = streams.remove(this); 388 } 389 if (inf != null) { 390 releaseInflater(inf); 391 } 392 } 393 394 // Override fill() method to provide an extra "dummy" byte 395 // at the end of the input stream. This is required when 396 // using the "nowrap" Inflater option. 397 protected void fill() throws IOException { 398 if (eof) { 399 throw new EOFException("Unexpected end of ZLIB input stream"); 400 } 401 len = in.read(buf, 0, buf.length); 402 if (len == -1) { 403 buf[0] = 0; 404 len = 1; 405 eof = true; 406 } 407 inf.setInput(buf, 0, len); 408 } 409 410 public int available() throws IOException { 411 if (closeRequested) 412 return 0; 413 long avail = zfin.size() - inf.getBytesWritten(); 414 return (avail > (long) Integer.MAX_VALUE ? 415 Integer.MAX_VALUE : (int) avail); 416 } 417 418 protected void finalize() throws Throwable { 419 close(); 420 } 421 } 422 423 /* 424 * Gets an inflater from the list of available inflaters or allocates 425 * a new one. 426 */ 427 private Inflater getInflater() { 428 Inflater inf; 429 synchronized (inflaterCache) { 430 while ((inf = inflaterCache.poll()) != null) { 431 if (!inf.ended()) { 432 return inf; 433 } 434 } 435 } 436 return new Inflater(true); 437 } 438 439 /* 440 * Releases the specified inflater to the list of available inflaters. 441 */ 442 private void releaseInflater(Inflater inf) { 443 if (!inf.ended()) { 444 inf.reset(); 445 synchronized (inflaterCache) { 446 inflaterCache.add(inf); 447 } 448 } 449 } 450 451 // List of available Inflater objects for decompression 452 private final Deque<Inflater> inflaterCache = new ArrayDeque<>(); 453 454 /** 455 * Returns the path name of the ZIP file. 456 * @return the path name of the ZIP file 457 */ 458 public String getName() { 459 return name; 460 } 461 462 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { 463 private int i = 0; 464 465 public ZipEntryIterator() { 466 ensureOpen(); 467 } 468 469 public boolean hasMoreElements() { 470 return hasNext(); 471 } 472 473 public boolean hasNext() { 474 synchronized (ZipFile.this) { 475 ensureOpen(); 476 return i < zsrc.total; 477 } 478 } 479 480 public ZipEntry nextElement() { 481 return next(); 482 } 483 484 public ZipEntry next() { 485 synchronized (ZipFile.this) { 486 ensureOpen(); 487 if (i >= zsrc.total) { 488 throw new NoSuchElementException(); 489 } 490 // each "entry" has 3 ints in table entries 491 return getZipEntry(null, zsrc.getEntryPos(i++ * 3)); 492 } 493 } 494 495 public Iterator<ZipEntry> asIterator() { 496 return this; 497 } 498 } 499 500 /** 501 * Returns an enumeration of the ZIP file entries. 502 * @return an enumeration of the ZIP file entries 503 * @throws IllegalStateException if the zip file has been closed 504 */ 505 public Enumeration<? extends ZipEntry> entries() { 506 return new ZipEntryIterator(); 507 } 508 509 /** 510 * Returns an ordered {@code Stream} over the ZIP file entries. 511 * Entries appear in the {@code Stream} in the order they appear in 512 * the central directory of the ZIP file. 513 * 514 * @return an ordered {@code Stream} of entries in this ZIP file 515 * @throws IllegalStateException if the zip file has been closed 516 * @since 1.8 517 */ 518 public Stream<? extends ZipEntry> stream() { 519 return StreamSupport.stream(Spliterators.spliterator( 520 new ZipEntryIterator(), size(), 521 Spliterator.ORDERED | Spliterator.DISTINCT | 522 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 523 } 524 525 /* Checks ensureOpen() before invoke this method */ 526 private ZipEntry getZipEntry(String name, int pos) { 527 byte[] cen = zsrc.cen; 528 ZipEntry e = new ZipEntry(); 529 int nlen = CENNAM(cen, pos); 530 int elen = CENEXT(cen, pos); 531 int clen = CENCOM(cen, pos); 532 e.flag = CENFLG(cen, pos); // get the flag first 533 if (name != null) { 534 e.name = name; 535 } else { 536 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 537 e.name = zc.toStringUTF8(cen, pos + CENHDR, nlen); 538 } else { 539 e.name = zc.toString(cen, pos + CENHDR, nlen); 540 } 541 } 542 e.xdostime = CENTIM(cen, pos); 543 e.crc = CENCRC(cen, pos); 544 e.size = CENLEN(cen, pos); 545 e.csize = CENSIZ(cen, pos); 546 e.method = CENHOW(cen, pos); 547 if (elen != 0) { 548 e.setExtra0(Arrays.copyOfRange(cen, pos + CENHDR + nlen, 549 pos + CENHDR + nlen + elen), true); 550 } 551 if (clen != 0) { 552 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 553 e.comment = zc.toStringUTF8(cen, pos + CENHDR + nlen + elen, clen); 554 } else { 555 e.comment = zc.toString(cen, pos + CENHDR + nlen + elen, clen); 556 } 557 } 558 return e; 559 } 560 561 /** 562 * Returns the number of entries in the ZIP file. 563 * 564 * @return the number of entries in the ZIP file 565 * @throws IllegalStateException if the zip file has been closed 566 */ 567 public int size() { 568 synchronized (this) { 569 ensureOpen(); 570 return zsrc.total; 571 } 572 } 573 574 /** 575 * Closes the ZIP file. 576 * <p> Closing this ZIP file will close all of the input streams 577 * previously returned by invocations of the {@link #getInputStream 578 * getInputStream} method. 579 * 580 * @throws IOException if an I/O error has occurred 581 */ 582 public void close() throws IOException { 583 if (closeRequested) { 584 return; 585 } 586 closeRequested = true; 587 588 synchronized (this) { 589 // Close streams, release their inflaters 590 synchronized (streams) { 591 if (!streams.isEmpty()) { 592 Map<InputStream, Inflater> copy = new HashMap<>(streams); 593 streams.clear(); 594 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { 595 e.getKey().close(); 596 Inflater inf = e.getValue(); 597 if (inf != null) { 598 inf.end(); 599 } 600 } 601 } 602 } 603 // Release cached inflaters 604 synchronized (inflaterCache) { 605 Inflater inf; 606 while ((inf = inflaterCache.poll()) != null) { 607 inf.end(); 608 } 609 } 610 // Release zip src 611 if (zsrc != null) { 612 Source.close(zsrc); 613 zsrc = null; 614 } 615 } 616 } 617 618 /** 619 * Ensures that the system resources held by this ZipFile object are 620 * released when there are no more references to it. 621 * 622 * <p> 623 * Since the time when GC would invoke this method is undetermined, 624 * it is strongly recommended that applications invoke the {@code close} 625 * method as soon they have finished accessing this {@code ZipFile}. 626 * This will prevent holding up system resources for an undetermined 627 * length of time. 628 * 629 * @throws IOException if an I/O error has occurred 630 * @see java.util.zip.ZipFile#close() 631 */ 632 protected void finalize() throws IOException { 633 close(); 634 } 635 636 private void ensureOpen() { 637 if (closeRequested) { 638 throw new IllegalStateException("zip file closed"); 639 } 640 if (zsrc == null) { 641 throw new IllegalStateException("The object is not initialized."); 642 } 643 } 644 645 private void ensureOpenOrZipException() throws IOException { 646 if (closeRequested) { 647 throw new ZipException("ZipFile closed"); 648 } 649 } 650 651 /* 652 * Inner class implementing the input stream used to read a 653 * (possibly compressed) zip file entry. 654 */ 655 private class ZipFileInputStream extends InputStream { 656 private volatile boolean closeRequested = false; 657 private long pos; // current position within entry data 658 protected long rem; // number of remaining bytes within entry 659 protected long size; // uncompressed size of this entry 660 661 ZipFileInputStream(byte[] cen, int cenpos) throws IOException { 662 rem = CENSIZ(cen, cenpos); 663 size = CENLEN(cen, cenpos); 664 pos = CENOFF(cen, cenpos); 665 // zip64 666 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || 667 pos == ZIP64_MAGICVAL) { 668 checkZIP64(cen, cenpos); 669 } 670 // negative for lazy initialization, see getDataOffset(); 671 pos = - (pos + ZipFile.this.zsrc.locpos); 672 } 673 674 private void checkZIP64(byte[] cen, int cenpos) throws IOException { 675 int off = cenpos + CENHDR + CENNAM(cen, cenpos); 676 int end = off + CENEXT(cen, cenpos); 677 while (off + 4 < end) { 678 int tag = get16(cen, off); 679 int sz = get16(cen, off + 2); 680 off += 4; 681 if (off + sz > end) // invalid data 682 break; 683 if (tag == EXTID_ZIP64) { 684 if (size == ZIP64_MAGICVAL) { 685 if (sz < 8 || (off + 8) > end) 686 break; 687 size = get64(cen, off); 688 sz -= 8; 689 off += 8; 690 } 691 if (rem == ZIP64_MAGICVAL) { 692 if (sz < 8 || (off + 8) > end) 693 break; 694 rem = get64(cen, off); 695 sz -= 8; 696 off += 8; 697 } 698 if (pos == ZIP64_MAGICVAL) { 699 if (sz < 8 || (off + 8) > end) 700 break; 701 pos = get64(cen, off); 702 sz -= 8; 703 off += 8; 704 } 705 break; 706 } 707 off += sz; 708 } 709 } 710 711 /* The Zip file spec explicitly allows the LOC extra data size to 712 * be different from the CEN extra data size. Since we cannot trust 713 * the CEN extra data size, we need to read the LOC to determine 714 * the entry data offset. 715 */ 716 private long initDataOffset() throws IOException { 717 if (pos <= 0) { 718 byte[] loc = new byte[LOCHDR]; 719 pos = -pos; 720 int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos); 721 if (len != LOCHDR) { 722 throw new ZipException("ZipFile error reading zip file"); 723 } 724 if (LOCSIG(loc) != LOCSIG) { 725 throw new ZipException("ZipFile invalid LOC header (bad signature)"); 726 } 727 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc); 728 } 729 return pos; 730 } 731 732 public int read(byte b[], int off, int len) throws IOException { 733 synchronized (ZipFile.this) { 734 ensureOpenOrZipException(); 735 initDataOffset(); 736 if (rem == 0) { 737 return -1; 738 } 739 if (len > rem) { 740 len = (int) rem; 741 } 742 if (len <= 0) { 743 return 0; 744 } 745 len = ZipFile.this.zsrc.readAt(b, off, len, pos); 746 if (len > 0) { 747 pos += len; 748 rem -= len; 749 } 750 } 751 if (rem == 0) { 752 close(); 753 } 754 return len; 755 } 756 757 public int read() throws IOException { 758 byte[] b = new byte[1]; 759 if (read(b, 0, 1) == 1) { 760 return b[0] & 0xff; 761 } else { 762 return -1; 763 } 764 } 765 766 public long skip(long n) throws IOException { 767 synchronized (ZipFile.this) { 768 ensureOpenOrZipException(); 769 initDataOffset(); 770 if (n > rem) { 771 n = rem; 772 } 773 pos += n; 774 rem -= n; 775 } 776 if (rem == 0) { 777 close(); 778 } 779 return n; 780 } 781 782 public int available() { 783 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 784 } 785 786 public long size() { 787 return size; 788 } 789 790 public void close() { 791 if (closeRequested) { 792 return; 793 } 794 closeRequested = true; 795 rem = 0; 796 synchronized (streams) { 797 streams.remove(this); 798 } 799 } 800 801 protected void finalize() { 802 close(); 803 } 804 } 805 806 static { 807 SharedSecrets.setJavaUtilZipFileAccess( 808 new JavaUtilZipFileAccess() { 809 public boolean startsWithLocHeader(ZipFile zip) { 810 return zip.zsrc.startsWithLoc; 811 } 812 public String[] getMetaInfEntryNames(ZipFile zip) { 813 return zip.getMetaInfEntryNames(); 814 } 815 } 816 ); 817 } 818 819 /* 820 * Returns an array of strings representing the names of all entries 821 * that begin with "META-INF/" (case ignored). This method is used 822 * in JarFile, via SharedSecrets, as an optimization when looking up 823 * manifest and signature file entries. Returns null if no entries 824 * were found. 825 */ 826 private String[] getMetaInfEntryNames() { 827 synchronized (this) { 828 ensureOpen(); 829 if (zsrc.metanames.size() == 0) { 830 return null; 831 } 832 String[] names = new String[zsrc.metanames.size()]; 833 byte[] cen = zsrc.cen; 834 for (int i = 0; i < names.length; i++) { 835 int pos = zsrc.metanames.get(i); 836 names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos), 837 StandardCharsets.UTF_8); 838 } 839 return names; 840 } 841 } 842 843 private static class Source { 844 private final Key key; // the key in files 845 private int refs = 1; 846 847 private RandomAccessFile zfile; // zfile of the underlying zip file 848 private byte[] cen; // CEN & ENDHDR 849 private long locpos; // position of first LOC header (usually 0) 850 private byte[] comment; // zip file comment 851 // list of meta entries in META-INF dir 852 private ArrayList<Integer> metanames = new ArrayList<>(); 853 private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) 854 855 // A Hashmap for all entries. 856 // 857 // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR, 858 // We might have a lot of these in a typical system. In order to save space we don't 859 // keep the name in memory, but merely remember a 32 bit {@code hash} value of the 860 // entry name and its offset {@code pos} in the central directory hdeader. 861 // 862 // private static class Entry { 863 // int hash; // 32 bit hashcode on name 864 // int next; // hash chain: index into entries 865 // int pos; // Offset of central directory file header 866 // } 867 // private Entry[] entries; // array of hashed cen entry 868 // 869 // To reduce the total size of entries further, we use a int[] here to store 3 "int" 870 // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be 871 // referred by their index of their positions in the {@code entries}. 872 // 873 private int[] entries; // array of hashed cen entry 874 private int addEntry(int index, int hash, int next, int pos) { 875 entries[index++] = hash; 876 entries[index++] = next; 877 entries[index++] = pos; 878 return index; 879 } 880 private int getEntryHash(int index) { return entries[index]; } 881 private int getEntryNext(int index) { return entries[index + 1]; } 882 private int getEntryPos(int index) { return entries[index + 2]; } 883 private static final int ZIP_ENDCHAIN = -1; 884 private int total; // total number of entries 885 private int[] table; // Hash chain heads: indexes into entries 886 private int tablelen; // number of hash heads 887 888 private static class Key { 889 BasicFileAttributes attrs; 890 File file; 891 892 public Key(File file, BasicFileAttributes attrs) { 893 this.attrs = attrs; 894 this.file = file; 895 } 896 897 public int hashCode() { 898 long t = attrs.lastModifiedTime().toMillis(); 899 return ((int)(t ^ (t >>> 32))) + file.hashCode(); 900 } 901 902 public boolean equals(Object obj) { 903 if (obj instanceof Key) { 904 Key key = (Key)obj; 905 if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { 906 return false; 907 } 908 Object fk = attrs.fileKey(); 909 if (fk != null) { 910 return fk.equals(key.attrs.fileKey()); 911 } else { 912 return file.equals(key.file); 913 } 914 } 915 return false; 916 } 917 } 918 private static final HashMap<Key, Source> files = new HashMap<>(); 919 920 921 public static Source get(File file, boolean toDelete) throws IOException { 922 Key key = new Key(file, 923 Files.readAttributes(file.toPath(), BasicFileAttributes.class)); 924 Source src = null; 925 synchronized (files) { 926 src = files.get(key); 927 if (src != null) { 928 src.refs++; 929 return src; 930 } 931 } 932 src = new Source(key, toDelete); 933 934 synchronized (files) { 935 if (files.containsKey(key)) { // someone else put in first 936 src.close(); // close the newly created one 937 src = files.get(key); 938 src.refs++; 939 return src; 940 } 941 files.put(key, src); 942 return src; 943 } 944 } 945 946 private static void close(Source src) throws IOException { 947 synchronized (files) { 948 if (--src.refs == 0) { 949 files.remove(src.key); 950 src.close(); 951 } 952 } 953 } 954 955 private Source(Key key, boolean toDelete) throws IOException { 956 this.key = key; 957 this.zfile = new RandomAccessFile(key.file, "r"); 958 if (toDelete) { 959 key.file.delete(); 960 } 961 try { 962 initCEN(-1); 963 byte[] buf = new byte[4]; 964 readFullyAt(buf, 0, 4, 0); 965 this.startsWithLoc = (LOCSIG(buf) == LOCSIG); 966 } catch (IOException x) { 967 try { 968 this.zfile.close(); 969 } catch (IOException xx) {} 970 throw x; 971 } 972 } 973 974 private void close() throws IOException { 975 zfile.close(); 976 zfile = null; 977 cen = null; 978 entries = null; 979 table = null; 980 metanames = null; 981 } 982 983 private static final int BUF_SIZE = 8192; 984 private final int readFullyAt(byte[] buf, int off, int len, long pos) 985 throws IOException 986 { 987 synchronized(zfile) { 988 zfile.seek(pos); 989 int N = len; 990 while (N > 0) { 991 int n = Math.min(BUF_SIZE, N); 992 zfile.readFully(buf, off, n); 993 off += n; 994 N -= n; 995 } 996 return len; 997 } 998 } 999 1000 private final int readAt(byte[] buf, int off, int len, long pos) 1001 throws IOException 1002 { 1003 synchronized(zfile) { 1004 zfile.seek(pos); 1005 return zfile.read(buf, off, len); 1006 } 1007 } 1008 1009 private static final int hashN(byte[] a, int off, int len) { 1010 int h = 1; 1011 while (len-- > 0) { 1012 h = 31 * h + a[off++]; 1013 } 1014 return h; 1015 } 1016 1017 private static final int hash_append(int hash, byte b) { 1018 return hash * 31 + b; 1019 } 1020 1021 private static class End { 1022 int centot; // 4 bytes 1023 long cenlen; // 4 bytes 1024 long cenoff; // 4 bytes 1025 long endpos; // 4 bytes 1026 } 1027 1028 /* 1029 * Searches for end of central directory (END) header. The contents of 1030 * the END header will be read and placed in endbuf. Returns the file 1031 * position of the END header, otherwise returns -1 if the END header 1032 * was not found or an error occurred. 1033 */ 1034 private End findEND() throws IOException { 1035 long ziplen = zfile.length(); 1036 if (ziplen <= 0) 1037 zerror("zip file is empty"); 1038 End end = new End(); 1039 byte[] buf = new byte[READBLOCKSZ]; 1040 long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0; 1041 long minPos = minHDR - (buf.length - ENDHDR); 1042 for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) { 1043 int off = 0; 1044 if (pos < 0) { 1045 // Pretend there are some NUL bytes before start of file 1046 off = (int)-pos; 1047 Arrays.fill(buf, 0, off, (byte)0); 1048 } 1049 int len = buf.length - off; 1050 if (readFullyAt(buf, off, len, pos + off) != len ) { 1051 zerror("zip END header not found"); 1052 } 1053 // Now scan the block backwards for END header signature 1054 for (int i = buf.length - ENDHDR; i >= 0; i--) { 1055 if (buf[i+0] == (byte)'P' && 1056 buf[i+1] == (byte)'K' && 1057 buf[i+2] == (byte)'\005' && 1058 buf[i+3] == (byte)'\006') { 1059 // Found ENDSIG header 1060 byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR); 1061 end.centot = ENDTOT(endbuf); 1062 end.cenlen = ENDSIZ(endbuf); 1063 end.cenoff = ENDOFF(endbuf); 1064 end.endpos = pos + i; 1065 int comlen = ENDCOM(endbuf); 1066 if (end.endpos + ENDHDR + comlen != ziplen) { 1067 // ENDSIG matched, however the size of file comment in it does 1068 // not match the real size. One "common" cause for this problem 1069 // is some "extra" bytes are padded at the end of the zipfile. 1070 // Let's do some extra verification, we don't care about the 1071 // performance in this situation. 1072 byte[] sbuf = new byte[4]; 1073 long cenpos = end.endpos - end.cenlen; 1074 long locpos = cenpos - end.cenoff; 1075 if (cenpos < 0 || 1076 locpos < 0 || 1077 readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 || 1078 GETSIG(sbuf) != CENSIG || 1079 readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 || 1080 GETSIG(sbuf) != LOCSIG) { 1081 continue; 1082 } 1083 } 1084 if (comlen > 0) { // this zip file has comlen 1085 comment = new byte[comlen]; 1086 if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) { 1087 zerror("zip comment read failed"); 1088 } 1089 } 1090 if (end.cenlen == ZIP64_MAGICVAL || 1091 end.cenoff == ZIP64_MAGICVAL || 1092 end.centot == ZIP64_MAGICCOUNT) 1093 { 1094 // need to find the zip64 end; 1095 try { 1096 byte[] loc64 = new byte[ZIP64_LOCHDR]; 1097 if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) 1098 != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { 1099 return end; 1100 } 1101 long end64pos = ZIP64_LOCOFF(loc64); 1102 byte[] end64buf = new byte[ZIP64_ENDHDR]; 1103 if (readFullyAt(end64buf, 0, end64buf.length, end64pos) 1104 != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { 1105 return end; 1106 } 1107 // end64 found, re-calcualte everything. 1108 end.cenlen = ZIP64_ENDSIZ(end64buf); 1109 end.cenoff = ZIP64_ENDOFF(end64buf); 1110 end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g 1111 end.endpos = end64pos; 1112 } catch (IOException x) {} // no zip64 loc/end 1113 } 1114 return end; 1115 } 1116 } 1117 } 1118 zerror("zip END header not found"); 1119 return null; //make compiler happy 1120 } 1121 1122 // Reads zip file central directory. 1123 private void initCEN(int knownTotal) throws IOException { 1124 if (knownTotal == -1) { 1125 End end = findEND(); 1126 if (end.endpos == 0) { 1127 locpos = 0; 1128 total = 0; 1129 entries = new int[0]; 1130 cen = null; 1131 return; // only END header present 1132 } 1133 if (end.cenlen > end.endpos) 1134 zerror("invalid END header (bad central directory size)"); 1135 long cenpos = end.endpos - end.cenlen; // position of CEN table 1136 // Get position of first local file (LOC) header, taking into 1137 // account that there may be a stub prefixed to the zip file. 1138 locpos = cenpos - end.cenoff; 1139 if (locpos < 0) { 1140 zerror("invalid END header (bad central directory offset)"); 1141 } 1142 // read in the CEN and END 1143 cen = new byte[(int)(end.cenlen + ENDHDR)]; 1144 if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { 1145 zerror("read CEN tables failed"); 1146 } 1147 total = end.centot; 1148 } else { 1149 total = knownTotal; 1150 } 1151 // hash table for entries 1152 entries = new int[total * 3]; 1153 tablelen = ((total/2) | 1); // Odd -> fewer collisions 1154 table = new int[tablelen]; 1155 Arrays.fill(table, ZIP_ENDCHAIN); 1156 int idx = 0; 1157 int hash = 0; 1158 int next = -1; 1159 1160 // list for all meta entries 1161 metanames = new ArrayList<>(); 1162 1163 // Iterate through the entries in the central directory 1164 int i = 0; 1165 int hsh = 0; 1166 int pos = 0; 1167 int limit = cen.length - ENDHDR; 1168 while (pos + CENHDR <= limit) { 1169 if (i >= total) { 1170 // This will only happen if the zip file has an incorrect 1171 // ENDTOT field, which usually means it contains more than 1172 // 65535 entries. 1173 initCEN(countCENHeaders(cen, limit)); 1174 return; 1175 } 1176 if (CENSIG(cen, pos) != CENSIG) 1177 zerror("invalid CEN header (bad signature)"); 1178 int method = CENHOW(cen, pos); 1179 int nlen = CENNAM(cen, pos); 1180 int elen = CENEXT(cen, pos); 1181 int clen = CENCOM(cen, pos); 1182 if ((CENFLG(cen, pos) & 1) != 0) 1183 zerror("invalid CEN header (encrypted entry)"); 1184 if (method != STORED && method != DEFLATED) 1185 zerror("invalid CEN header (bad compression method: " + method + ")"); 1186 if (pos + CENHDR + nlen > limit) 1187 zerror("invalid CEN header (bad header size)"); 1188 // Record the CEN offset and the name hash in our hash cell. 1189 hash = hashN(cen, pos + CENHDR, nlen); 1190 hsh = (hash & 0x7fffffff) % tablelen; 1191 next = table[hsh]; 1192 table[hsh] = idx; 1193 idx = addEntry(idx, hash, next, pos); 1194 // Adds name to metanames. 1195 if (isMetaName(cen, pos + CENHDR, nlen)) { 1196 metanames.add(pos); 1197 } 1198 // skip ext and comment 1199 pos += (CENHDR + nlen + elen + clen); 1200 i++; 1201 } 1202 total = i; 1203 if (pos + ENDHDR != cen.length) { 1204 zerror("invalid CEN header (bad header size)"); 1205 } 1206 } 1207 1208 private static void zerror(String msg) throws ZipException { 1209 throw new ZipException(msg); 1210 } 1211 1212 /* 1213 * Returns the {@code pos} of the zip cen entry corresponding to the 1214 * specified entry name, or -1 if not found. 1215 */ 1216 private int getEntryPos(byte[] name, boolean addSlash) { 1217 if (total == 0) { 1218 return -1; 1219 } 1220 int hsh = hashN(name, 0, name.length); 1221 int idx = table[(hsh & 0x7fffffff) % tablelen]; 1222 /* 1223 * This while loop is an optimization where a double lookup 1224 * for name and name+/ is being performed. The name char 1225 * array has enough room at the end to try again with a 1226 * slash appended if the first table lookup does not succeed. 1227 */ 1228 while(true) { 1229 /* 1230 * Search down the target hash chain for a entry whose 1231 * 32 bit hash matches the hashed name. 1232 */ 1233 while (idx != ZIP_ENDCHAIN) { 1234 if (getEntryHash(idx) == hsh) { 1235 // The CEN name must match the specfied one 1236 int pos = getEntryPos(idx); 1237 if (name.length == CENNAM(cen, pos)) { 1238 boolean matched = true; 1239 int nameoff = pos + CENHDR; 1240 for (int i = 0; i < name.length; i++) { 1241 if (name[i] != cen[nameoff++]) { 1242 matched = false; 1243 break; 1244 } 1245 } 1246 if (matched) { 1247 return pos; 1248 } 1249 } 1250 } 1251 idx = getEntryNext(idx); 1252 } 1253 /* If not addSlash, or slash is already there, we are done */ 1254 if (!addSlash || name[name.length - 1] == '/') { 1255 return -1; 1256 } 1257 /* Add slash and try once more */ 1258 name = Arrays.copyOf(name, name.length + 1); 1259 name[name.length - 1] = '/'; 1260 hsh = hash_append(hsh, (byte)'/'); 1261 //idx = table[hsh % tablelen]; 1262 idx = table[(hsh & 0x7fffffff) % tablelen]; 1263 addSlash = false; 1264 } 1265 } 1266 1267 private static byte[] metainf = new byte[] { 1268 'M', 'E', 'T', 'A', '-', 'I' , 'N', 'F', '/', 1269 }; 1270 1271 /* 1272 * Returns true if the specified entry's name begins with the string 1273 * "META-INF/" irrespective of case. 1274 */ 1275 private static boolean isMetaName(byte[] name, int off, int len) { 1276 if (len < 9 || (name[off] != 'M' && name[off] != 'm')) { // sizeof("META-INF/") - 1 1277 return false; 1278 } 1279 off++; 1280 for (int i = 1; i < metainf.length; i++) { 1281 byte c = name[off++]; 1282 // Avoid toupper; it's locale-dependent 1283 if (c >= 'a' && c <= 'z') { 1284 c += 'A' - 'a'; 1285 } 1286 if (metainf[i] != c) { 1287 return false; 1288 } 1289 } 1290 return true; 1291 } 1292 1293 /* 1294 * Counts the number of CEN headers in a central directory extending 1295 * from BEG to END. Might return a bogus answer if the zip file is 1296 * corrupt, but will not crash. 1297 */ 1298 static int countCENHeaders(byte[] cen, int end) { 1299 int count = 0; 1300 int pos = 0; 1301 while (pos + CENHDR <= end) { 1302 count++; 1303 pos += (CENHDR + CENNAM(cen, pos) + CENEXT(cen, pos) + CENCOM(cen, pos)); 1304 } 1305 return count; 1306 } 1307 } 1308 }