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