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