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