1 /* 2 * Copyright (c) 1995, 2020, 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.file.InvalidPathException; 38 import java.nio.file.attribute.BasicFileAttributes; 39 import java.nio.file.Files; 40 import java.util.ArrayDeque; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.Deque; 45 import java.util.Enumeration; 46 import java.util.HashMap; 47 import java.util.Iterator; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.Objects; 51 import java.util.NoSuchElementException; 52 import java.util.Set; 53 import java.util.Spliterator; 54 import java.util.Spliterators; 55 import java.util.TreeSet; 56 import java.util.WeakHashMap; 57 import java.util.function.Consumer; 58 import java.util.function.Function; 59 import java.util.function.IntFunction; 60 import java.util.jar.JarEntry; 61 import java.util.jar.JarFile; 62 import java.util.stream.Stream; 63 import java.util.stream.StreamSupport; 64 import jdk.internal.access.JavaUtilZipFileAccess; 65 import jdk.internal.access.SharedSecrets; 66 import jdk.internal.misc.VM; 67 import jdk.internal.perf.PerfCounter; 68 import jdk.internal.ref.CleanerFactory; 69 import jdk.internal.vm.annotation.Stable; 70 import sun.nio.cs.UTF_8; 71 72 import static java.util.zip.ZipConstants64.*; 73 import static java.util.zip.ZipUtils.*; 74 75 /** 76 * This class is used to read entries from a zip file. 77 * 78 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor 79 * or method in this class will cause a {@link NullPointerException} to be 80 * thrown. 81 * 82 * @apiNote 83 * To release resources used by this {@code ZipFile}, the {@link #close()} method 84 * should be called explicitly or by try-with-resources. Subclasses are responsible 85 * for the cleanup of resources acquired by the subclass. Subclasses that override 86 * {@link #finalize()} in order to perform cleanup should be modified to use alternative 87 * cleanup mechanisms such as {@link java.lang.ref.Cleaner} and remove the overriding 88 * {@code finalize} method. 89 * 90 * @author David Connelly 91 * @since 1.1 92 */ 93 public class ZipFile implements ZipConstants, Closeable { 94 95 private final String name; // zip file name 96 private volatile boolean closeRequested; 97 98 // The "resource" used by this zip file that needs to be 99 // cleaned after use. 100 // a) the input streams that need to be closed 101 // b) the list of cached Inflater objects 102 // c) the "native" source of this zip file. 103 private final @Stable CleanableResource res; 104 105 private static final int STORED = ZipEntry.STORED; 106 private static final int DEFLATED = ZipEntry.DEFLATED; 107 108 /** 109 * Mode flag to open a zip file for reading. 110 */ 111 public static final int OPEN_READ = 0x1; 112 113 /** 114 * Mode flag to open a zip file and mark it for deletion. The file will be 115 * deleted some time between the moment that it is opened and the moment 116 * that it is closed, but its contents will remain accessible via the 117 * {@code ZipFile} object until either the close method is invoked or the 118 * virtual machine exits. 119 */ 120 public static final int OPEN_DELETE = 0x4; 121 122 /** 123 * Opens a zip file for reading. 124 * 125 * <p>First, if there is a security manager, its {@code checkRead} 126 * method is called with the {@code name} argument as its argument 127 * to ensure the read is allowed. 128 * 129 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 130 * decode the entry names and comments. 131 * 132 * @param name the name of the zip file 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 its 136 * {@code checkRead} method doesn't allow read access to the file. 137 * 138 * @see SecurityManager#checkRead(java.lang.String) 139 */ 140 public ZipFile(String name) throws IOException { 141 this(new File(name), OPEN_READ); 142 } 143 144 /** 145 * Opens a new {@code ZipFile} to read from the specified 146 * {@code File} object in the specified mode. The mode argument 147 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 148 * 149 * <p>First, if there is a security manager, its {@code checkRead} 150 * method is called with the {@code name} argument as its argument to 151 * ensure the read is allowed. 152 * 153 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 154 * decode the entry names and comments 155 * 156 * @param file the ZIP file to be opened for reading 157 * @param mode the mode in which the file is to be opened 158 * @throws ZipException if a ZIP format error has occurred 159 * @throws IOException if an I/O error has occurred 160 * @throws SecurityException if a security manager exists and 161 * its {@code checkRead} method 162 * doesn't allow read access to the file, 163 * or its {@code checkDelete} method doesn't allow deleting 164 * the file when the {@code OPEN_DELETE} flag is set. 165 * @throws IllegalArgumentException if the {@code mode} argument is invalid 166 * @see SecurityManager#checkRead(java.lang.String) 167 * @since 1.3 168 */ 169 public ZipFile(File file, int mode) throws IOException { 170 this(file, mode, UTF_8.INSTANCE); 171 } 172 173 /** 174 * Opens a ZIP file for reading given the specified File object. 175 * 176 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 177 * decode the entry names and comments. 178 * 179 * @param file the ZIP file to be opened for reading 180 * @throws ZipException if a ZIP format error has occurred 181 * @throws IOException if an I/O error has occurred 182 */ 183 public ZipFile(File file) throws ZipException, IOException { 184 this(file, OPEN_READ); 185 } 186 187 /** 188 * Opens a new {@code ZipFile} to read from the specified 189 * {@code File} object in the specified mode. The mode argument 190 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 191 * 192 * <p>First, if there is a security manager, its {@code checkRead} 193 * method is called with the {@code name} argument as its argument to 194 * ensure the read is allowed. 195 * 196 * @param file the ZIP file to be opened for reading 197 * @param mode the mode in which the file is to be opened 198 * @param charset 199 * the {@linkplain java.nio.charset.Charset charset} to 200 * be used to decode the ZIP entry name and comment that are not 201 * encoded by using UTF-8 encoding (indicated by entry's general 202 * purpose flag). 203 * 204 * @throws ZipException if a ZIP format error has occurred 205 * @throws IOException if an I/O error has occurred 206 * 207 * @throws SecurityException 208 * if a security manager exists and its {@code checkRead} 209 * method doesn't allow read access to the file,or its 210 * {@code checkDelete} method doesn't allow deleting the 211 * file when the {@code OPEN_DELETE} flag is set 212 * 213 * @throws IllegalArgumentException if the {@code mode} argument is invalid 214 * 215 * @see SecurityManager#checkRead(java.lang.String) 216 * 217 * @since 1.7 218 */ 219 public ZipFile(File file, int mode, Charset charset) throws IOException 220 { 221 if (((mode & OPEN_READ) == 0) || 222 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { 223 throw new IllegalArgumentException("Illegal mode: 0x"+ 224 Integer.toHexString(mode)); 225 } 226 String name = file.getPath(); 227 SecurityManager sm = System.getSecurityManager(); 228 if (sm != null) { 229 sm.checkRead(name); 230 if ((mode & OPEN_DELETE) != 0) { 231 sm.checkDelete(name); 232 } 233 } 234 Objects.requireNonNull(charset, "charset"); 235 236 this.name = name; 237 long t0 = System.nanoTime(); 238 239 this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode); 240 241 PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); 242 PerfCounter.getZipFileCount().increment(); 243 } 244 245 /** 246 * Opens a zip file for reading. 247 * 248 * <p>First, if there is a security manager, its {@code checkRead} 249 * method is called with the {@code name} argument as its argument 250 * to ensure the read is allowed. 251 * 252 * @param name the name of the zip file 253 * @param charset 254 * the {@linkplain java.nio.charset.Charset charset} to 255 * be used to decode the ZIP entry name and comment that are not 256 * encoded by using UTF-8 encoding (indicated by entry's general 257 * purpose flag). 258 * 259 * @throws ZipException if a ZIP format error has occurred 260 * @throws IOException if an I/O error has occurred 261 * @throws SecurityException 262 * if a security manager exists and its {@code checkRead} 263 * method doesn't allow read access to the file 264 * 265 * @see SecurityManager#checkRead(java.lang.String) 266 * 267 * @since 1.7 268 */ 269 public ZipFile(String name, Charset charset) throws IOException 270 { 271 this(new File(name), OPEN_READ, charset); 272 } 273 274 /** 275 * Opens a ZIP file for reading given the specified File object. 276 * 277 * @param file the ZIP file to be opened for reading 278 * @param charset 279 * The {@linkplain java.nio.charset.Charset charset} to be 280 * used to decode the ZIP entry name and comment (ignored if 281 * the <a href="package-summary.html#lang_encoding"> language 282 * encoding bit</a> of the ZIP entry's general purpose bit 283 * flag is set). 284 * 285 * @throws ZipException if a ZIP format error has occurred 286 * @throws IOException if an I/O error has occurred 287 * 288 * @since 1.7 289 */ 290 public ZipFile(File file, Charset charset) throws IOException 291 { 292 this(file, OPEN_READ, charset); 293 } 294 295 /** 296 * Returns the zip file comment, or null if none. 297 * 298 * @return the comment string for the zip file, or null if none 299 * 300 * @throws IllegalStateException if the zip file has been closed 301 * 302 * @since 1.7 303 */ 304 public String getComment() { 305 synchronized (this) { 306 ensureOpen(); 307 if (res.zsrc.comment == null) { 308 return null; 309 } 310 return res.zsrc.zc.toString(res.zsrc.comment); 311 } 312 } 313 314 /** 315 * Returns the zip file entry for the specified name, or null 316 * if not found. 317 * 318 * @param name the name of the entry 319 * @return the zip file entry, or null if not found 320 * @throws IllegalStateException if the zip file has been closed 321 */ 322 public ZipEntry getEntry(String name) { 323 return getEntry(name, ZipEntry::new); 324 } 325 326 /* 327 * Returns the zip file entry for the specified name, or null 328 * if not found. 329 * 330 * @param name the name of the entry 331 * @param func the function that creates the returned entry 332 * 333 * @return the zip file entry, or null if not found 334 * @throws IllegalStateException if the zip file has been closed 335 */ 336 private ZipEntry getEntry(String name, Function<String, ? extends ZipEntry> func) { 337 Objects.requireNonNull(name, "name"); 338 ZipEntry entry = null; 339 synchronized (this) { 340 ensureOpen(); 341 int pos = res.zsrc.getEntryPos(name, true); 342 if (pos != -1) { 343 entry = getZipEntry(name, pos, func); 344 } 345 } 346 return entry; 347 } 348 349 /** 350 * Returns an input stream for reading the contents of the specified 351 * zip file entry. 352 * <p> 353 * Closing this ZIP file will, in turn, close all input streams that 354 * have been returned by invocations of this method. 355 * 356 * @param entry the zip file entry 357 * @return the input stream for reading the contents of the specified 358 * zip file entry. 359 * @throws ZipException if a ZIP format error has occurred 360 * @throws IOException if an I/O error has occurred 361 * @throws IllegalStateException if the zip file has been closed 362 */ 363 public InputStream getInputStream(ZipEntry entry) throws IOException { 364 Objects.requireNonNull(entry, "entry"); 365 int pos; 366 ZipFileInputStream in; 367 Source zsrc = res.zsrc; 368 Set<InputStream> istreams = res.istreams; 369 synchronized (this) { 370 ensureOpen(); 371 if (Objects.equals(lastEntryName, entry.name)) { 372 pos = lastEntryPos; 373 } else { 374 pos = zsrc.getEntryPos(entry.name, false); 375 } 376 if (pos == -1) { 377 return null; 378 } 379 in = new ZipFileInputStream(zsrc.cen, pos); 380 switch (CENHOW(zsrc.cen, pos)) { 381 case STORED: 382 synchronized (istreams) { 383 istreams.add(in); 384 } 385 return in; 386 case DEFLATED: 387 // Inflater likes a bit of slack 388 // MORE: Compute good size for inflater stream: 389 long size = CENLEN(zsrc.cen, pos) + 2; 390 if (size > 65536) { 391 size = 8192; 392 } 393 if (size <= 0) { 394 size = 4096; 395 } 396 InputStream is = new ZipFileInflaterInputStream(in, res, (int)size); 397 synchronized (istreams) { 398 istreams.add(is); 399 } 400 return is; 401 default: 402 throw new ZipException("invalid compression method"); 403 } 404 } 405 } 406 407 private static class InflaterCleanupAction implements Runnable { 408 private final Inflater inf; 409 private final CleanableResource res; 410 411 InflaterCleanupAction(Inflater inf, CleanableResource res) { 412 this.inf = inf; 413 this.res = res; 414 } 415 416 @Override 417 public void run() { 418 res.releaseInflater(inf); 419 } 420 } 421 422 private class ZipFileInflaterInputStream extends InflaterInputStream { 423 private volatile boolean closeRequested; 424 private boolean eof = false; 425 private final Cleanable cleanable; 426 427 ZipFileInflaterInputStream(ZipFileInputStream zfin, 428 CleanableResource res, int size) { 429 this(zfin, res, res.getInflater(), size); 430 } 431 432 private ZipFileInflaterInputStream(ZipFileInputStream zfin, 433 CleanableResource res, 434 Inflater inf, int size) { 435 super(zfin, inf, size); 436 this.cleanable = CleanerFactory.cleaner().register(this, 437 new InflaterCleanupAction(inf, res)); 438 } 439 440 public void close() throws IOException { 441 if (closeRequested) 442 return; 443 closeRequested = true; 444 super.close(); 445 synchronized (res.istreams) { 446 res.istreams.remove(this); 447 } 448 cleanable.clean(); 449 } 450 451 // Override fill() method to provide an extra "dummy" byte 452 // at the end of the input stream. This is required when 453 // using the "nowrap" Inflater option. 454 protected void fill() throws IOException { 455 if (eof) { 456 throw new EOFException("Unexpected end of ZLIB input stream"); 457 } 458 len = in.read(buf, 0, buf.length); 459 if (len == -1) { 460 buf[0] = 0; 461 len = 1; 462 eof = true; 463 } 464 inf.setInput(buf, 0, len); 465 } 466 467 public int available() throws IOException { 468 if (closeRequested) 469 return 0; 470 long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten(); 471 return (avail > (long) Integer.MAX_VALUE ? 472 Integer.MAX_VALUE : (int) avail); 473 } 474 } 475 476 /** 477 * Returns the path name of the ZIP file. 478 * @return the path name of the ZIP file 479 */ 480 public String getName() { 481 return name; 482 } 483 484 private class ZipEntryIterator<T extends ZipEntry> 485 implements Enumeration<T>, Iterator<T> { 486 487 private int i = 0; 488 private final int entryCount; 489 private final Function<String, T> gen; 490 491 public ZipEntryIterator(int entryCount, Function<String, T> gen) { 492 this.entryCount = entryCount; 493 this.gen = gen; 494 } 495 496 @Override 497 public boolean hasMoreElements() { 498 return hasNext(); 499 } 500 501 @Override 502 public boolean hasNext() { 503 return i < entryCount; 504 } 505 506 @Override 507 public T nextElement() { 508 return next(); 509 } 510 511 @Override 512 @SuppressWarnings("unchecked") 513 public T next() { 514 synchronized (ZipFile.this) { 515 ensureOpen(); 516 if (!hasNext()) { 517 throw new NoSuchElementException(); 518 } 519 // each "entry" has 3 ints in table entries 520 return (T)getZipEntry(null, res.zsrc.getEntryPos(i++ * 3), gen); 521 } 522 } 523 524 @Override 525 public Iterator<T> asIterator() { 526 return this; 527 } 528 } 529 530 /** 531 * Returns an enumeration of the ZIP file entries. 532 * @return an enumeration of the ZIP file entries 533 * @throws IllegalStateException if the zip file has been closed 534 */ 535 public Enumeration<? extends ZipEntry> entries() { 536 synchronized (this) { 537 ensureOpen(); 538 return new ZipEntryIterator<ZipEntry>(res.zsrc.total, ZipEntry::new); 539 } 540 } 541 542 private Enumeration<JarEntry> entries(Function<String, JarEntry> func) { 543 synchronized (this) { 544 ensureOpen(); 545 return new ZipEntryIterator<JarEntry>(res.zsrc.total, func); 546 } 547 } 548 549 private class EntrySpliterator<T> extends Spliterators.AbstractSpliterator<T> { 550 private int index; 551 private final int fence; 552 private final IntFunction<T> gen; 553 554 EntrySpliterator(int index, int fence, IntFunction<T> gen) { 555 super((long)fence, 556 Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | 557 Spliterator.NONNULL); 558 this.index = index; 559 this.fence = fence; 560 this.gen = gen; 561 } 562 563 @Override 564 public boolean tryAdvance(Consumer<? super T> action) { 565 if (action == null) 566 throw new NullPointerException(); 567 if (index >= 0 && index < fence) { 568 synchronized (ZipFile.this) { 569 ensureOpen(); 570 action.accept(gen.apply(res.zsrc.getEntryPos(index++ * 3))); 571 } 572 return true; 573 } 574 return false; 575 } 576 } 577 578 /** 579 * Returns an ordered {@code Stream} over the ZIP file entries. 580 * 581 * Entries appear in the {@code Stream} in the order they appear in 582 * the central directory of the ZIP file. 583 * 584 * @return an ordered {@code Stream} of entries in this ZIP file 585 * @throws IllegalStateException if the zip file has been closed 586 * @since 1.8 587 */ 588 public Stream<? extends ZipEntry> stream() { 589 synchronized (this) { 590 ensureOpen(); 591 return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total, 592 pos -> getZipEntry(null, pos, ZipEntry::new)), false); 593 } 594 } 595 596 private String getEntryName(int pos) { 597 byte[] cen = res.zsrc.cen; 598 int nlen = CENNAM(cen, pos); 599 ZipCoder zc = res.zsrc.zipCoderForPos(pos); 600 return zc.toString(cen, pos + CENHDR, nlen); 601 } 602 603 /* 604 * Returns an ordered {@code Stream} over the zip file entry names. 605 * 606 * Entry names appear in the {@code Stream} in the order they appear in 607 * the central directory of the ZIP file. 608 * 609 * @return an ordered {@code Stream} of entry names in this zip file 610 * @throws IllegalStateException if the zip file has been closed 611 * @since 10 612 */ 613 private Stream<String> entryNameStream() { 614 synchronized (this) { 615 ensureOpen(); 616 return StreamSupport.stream( 617 new EntrySpliterator<>(0, res.zsrc.total, this::getEntryName), false); 618 } 619 } 620 621 /* 622 * Returns an ordered {@code Stream} over the zip file entries. 623 * 624 * Entries appear in the {@code Stream} in the order they appear in 625 * the central directory of the jar file. 626 * 627 * @param func the function that creates the returned entry 628 * @return an ordered {@code Stream} of entries in this zip file 629 * @throws IllegalStateException if the zip file has been closed 630 * @since 10 631 */ 632 private Stream<JarEntry> stream(Function<String, JarEntry> func) { 633 synchronized (this) { 634 ensureOpen(); 635 return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total, 636 pos -> (JarEntry)getZipEntry(null, pos, func)), false); 637 } 638 } 639 640 private String lastEntryName; 641 private int lastEntryPos; 642 643 /* Check ensureOpen() before invoking this method */ 644 private ZipEntry getZipEntry(String name, int pos, 645 Function<String, ? extends ZipEntry> func) { 646 byte[] cen = res.zsrc.cen; 647 int nlen = CENNAM(cen, pos); 648 int elen = CENEXT(cen, pos); 649 int clen = CENCOM(cen, pos); 650 651 ZipCoder zc = res.zsrc.zipCoderForPos(pos); 652 if (name != null) { 653 // only need to check for mismatch of trailing slash 654 if (nlen > 0 && 655 !name.isEmpty() && 656 zc.hasTrailingSlash(cen, pos + CENHDR + nlen) && 657 !name.endsWith("/")) 658 { 659 name += '/'; 660 } 661 } else { 662 // invoked from iterator, use the entry name stored in cen 663 name = zc.toString(cen, pos + CENHDR, nlen); 664 } 665 ZipEntry e = func.apply(name); //ZipEntry e = new ZipEntry(name); 666 e.flag = CENFLG(cen, pos); 667 e.xdostime = CENTIM(cen, pos); 668 e.crc = CENCRC(cen, pos); 669 e.size = CENLEN(cen, pos); 670 e.csize = CENSIZ(cen, pos); 671 e.method = CENHOW(cen, pos); 672 if (elen != 0) { 673 int start = pos + CENHDR + nlen; 674 e.setExtra0(Arrays.copyOfRange(cen, start, start + elen), true, false); 675 } 676 if (clen != 0) { 677 int start = pos + CENHDR + nlen + elen; 678 e.comment = zc.toString(cen, start, clen); 679 } 680 lastEntryName = e.name; 681 lastEntryPos = pos; 682 return e; 683 } 684 685 /** 686 * Returns the number of entries in the ZIP file. 687 * 688 * @return the number of entries in the ZIP file 689 * @throws IllegalStateException if the zip file has been closed 690 */ 691 public int size() { 692 synchronized (this) { 693 ensureOpen(); 694 return res.zsrc.total; 695 } 696 } 697 698 private static class CleanableResource implements Runnable { 699 // The outstanding inputstreams that need to be closed 700 final Set<InputStream> istreams; 701 702 // List of cached Inflater objects for decompression 703 Deque<Inflater> inflaterCache; 704 705 final Cleanable cleanable; 706 707 Source zsrc; 708 709 CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException { 710 this.cleanable = CleanerFactory.cleaner().register(zf, this); 711 this.istreams = Collections.newSetFromMap(new WeakHashMap<>()); 712 this.inflaterCache = new ArrayDeque<>(); 713 this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc); 714 } 715 716 void clean() { 717 cleanable.clean(); 718 } 719 720 /* 721 * Gets an inflater from the list of available inflaters or allocates 722 * a new one. 723 */ 724 Inflater getInflater() { 725 Inflater inf; 726 synchronized (inflaterCache) { 727 if ((inf = inflaterCache.poll()) != null) { 728 return inf; 729 } 730 } 731 return new Inflater(true); 732 } 733 734 /* 735 * Releases the specified inflater to the list of available inflaters. 736 */ 737 void releaseInflater(Inflater inf) { 738 Deque<Inflater> inflaters = this.inflaterCache; 739 if (inflaters != null) { 740 synchronized (inflaters) { 741 // double checked! 742 if (inflaters == this.inflaterCache) { 743 inf.reset(); 744 inflaters.add(inf); 745 return; 746 } 747 } 748 } 749 // inflaters cache already closed - just end it. 750 inf.end(); 751 } 752 753 public void run() { 754 IOException ioe = null; 755 756 // Release cached inflaters and close the cache first 757 Deque<Inflater> inflaters = this.inflaterCache; 758 if (inflaters != null) { 759 synchronized (inflaters) { 760 // no need to double-check as only one thread gets a 761 // chance to execute run() (Cleaner guarantee)... 762 Inflater inf; 763 while ((inf = inflaters.poll()) != null) { 764 inf.end(); 765 } 766 // close inflaters cache 767 this.inflaterCache = null; 768 } 769 } 770 771 // Close streams, release their inflaters 772 if (istreams != null) { 773 synchronized (istreams) { 774 if (!istreams.isEmpty()) { 775 InputStream[] copy = istreams.toArray(new InputStream[0]); 776 istreams.clear(); 777 for (InputStream is : copy) { 778 try { 779 is.close(); 780 } catch (IOException e) { 781 if (ioe == null) ioe = e; 782 else ioe.addSuppressed(e); 783 } 784 } 785 } 786 } 787 } 788 789 // Release zip src 790 if (zsrc != null) { 791 synchronized (zsrc) { 792 try { 793 Source.release(zsrc); 794 zsrc = null; 795 } catch (IOException e) { 796 if (ioe == null) ioe = e; 797 else ioe.addSuppressed(e); 798 } 799 } 800 } 801 if (ioe != null) { 802 throw new UncheckedIOException(ioe); 803 } 804 } 805 806 } 807 808 /** 809 * Closes the ZIP file. 810 * 811 * <p> Closing this ZIP file will close all of the input streams 812 * previously returned by invocations of the {@link #getInputStream 813 * getInputStream} method. 814 * 815 * @throws IOException if an I/O error has occurred 816 */ 817 public void close() throws IOException { 818 if (closeRequested) { 819 return; 820 } 821 closeRequested = true; 822 823 synchronized (this) { 824 // Close streams, release their inflaters, release cached inflaters 825 // and release zip source 826 try { 827 res.clean(); 828 } catch (UncheckedIOException ioe) { 829 throw ioe.getCause(); 830 } 831 } 832 } 833 834 private void ensureOpen() { 835 if (closeRequested) { 836 throw new IllegalStateException("zip file closed"); 837 } 838 if (res.zsrc == null) { 839 throw new IllegalStateException("The object is not initialized."); 840 } 841 } 842 843 private void ensureOpenOrZipException() throws IOException { 844 if (closeRequested) { 845 throw new ZipException("ZipFile closed"); 846 } 847 } 848 849 /* 850 * Inner class implementing the input stream used to read a 851 * (possibly compressed) zip file entry. 852 */ 853 private class ZipFileInputStream extends InputStream { 854 private volatile boolean closeRequested; 855 private long pos; // current position within entry data 856 private long startingPos; // Start position for the entry data 857 protected long rem; // number of remaining bytes within entry 858 protected long size; // uncompressed size of this entry 859 860 ZipFileInputStream(byte[] cen, int cenpos) { 861 rem = CENSIZ(cen, cenpos); 862 size = CENLEN(cen, cenpos); 863 pos = CENOFF(cen, cenpos); 864 // zip64 865 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || 866 pos == ZIP64_MAGICVAL) { 867 checkZIP64(cen, cenpos); 868 } 869 // negative for lazy initialization, see getDataOffset(); 870 pos = - (pos + ZipFile.this.res.zsrc.locpos); 871 } 872 873 private void checkZIP64(byte[] cen, int cenpos) { 874 int off = cenpos + CENHDR + CENNAM(cen, cenpos); 875 int end = off + CENEXT(cen, cenpos); 876 while (off + 4 < end) { 877 int tag = get16(cen, off); 878 int sz = get16(cen, off + 2); 879 off += 4; 880 if (off + sz > end) // invalid data 881 break; 882 if (tag == EXTID_ZIP64) { 883 if (size == ZIP64_MAGICVAL) { 884 if (sz < 8 || (off + 8) > end) 885 break; 886 size = get64(cen, off); 887 sz -= 8; 888 off += 8; 889 } 890 if (rem == ZIP64_MAGICVAL) { 891 if (sz < 8 || (off + 8) > end) 892 break; 893 rem = get64(cen, off); 894 sz -= 8; 895 off += 8; 896 } 897 if (pos == ZIP64_MAGICVAL) { 898 if (sz < 8 || (off + 8) > end) 899 break; 900 pos = get64(cen, off); 901 sz -= 8; 902 off += 8; 903 } 904 break; 905 } 906 off += sz; 907 } 908 } 909 910 /* 911 * The Zip file spec explicitly allows the LOC extra data size to 912 * be different from the CEN extra data size. Since we cannot trust 913 * the CEN extra data size, we need to read the LOC to determine 914 * the entry data offset. 915 */ 916 private long initDataOffset() throws IOException { 917 if (pos <= 0) { 918 byte[] loc = new byte[LOCHDR]; 919 pos = -pos; 920 int len = ZipFile.this.res.zsrc.readFullyAt(loc, 0, loc.length, pos); 921 if (len != LOCHDR) { 922 throw new ZipException("ZipFile error reading zip file"); 923 } 924 if (LOCSIG(loc) != LOCSIG) { 925 throw new ZipException("ZipFile invalid LOC header (bad signature)"); 926 } 927 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc); 928 startingPos = pos; // Save starting position for the entry 929 } 930 return pos; 931 } 932 933 public int read(byte b[], int off, int len) throws IOException { 934 synchronized (ZipFile.this) { 935 ensureOpenOrZipException(); 936 initDataOffset(); 937 if (rem == 0) { 938 return -1; 939 } 940 if (len > rem) { 941 len = (int) rem; 942 } 943 if (len <= 0) { 944 return 0; 945 } 946 len = ZipFile.this.res.zsrc.readAt(b, off, len, pos); 947 if (len > 0) { 948 pos += len; 949 rem -= len; 950 } 951 } 952 if (rem == 0) { 953 close(); 954 } 955 return len; 956 } 957 958 public int read() throws IOException { 959 byte[] b = new byte[1]; 960 if (read(b, 0, 1) == 1) { 961 return b[0] & 0xff; 962 } else { 963 return -1; 964 } 965 } 966 967 public long skip(long n) throws IOException { 968 synchronized (ZipFile.this) { 969 initDataOffset(); 970 long newPos = pos + n; 971 if (n > 0) { 972 // If we overflowed adding the skip value or are moving 973 // past EOF, set the skip value to number of bytes remaining 974 // to reach EOF 975 if (newPos < 0 || n > rem) { 976 n = rem; 977 } 978 } else if (newPos < startingPos) { 979 // Tried to position before BOF so set position to the 980 // BOF and return the number of bytes we moved backwards 981 // to reach BOF 982 n = startingPos - pos; 983 } 984 pos += n; 985 rem -= n; 986 } 987 if (rem == 0) { 988 close(); 989 } 990 return n; 991 } 992 993 public int available() { 994 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 995 } 996 997 public long size() { 998 return size; 999 } 1000 1001 public void close() { 1002 if (closeRequested) { 1003 return; 1004 } 1005 closeRequested = true; 1006 rem = 0; 1007 synchronized (res.istreams) { 1008 res.istreams.remove(this); 1009 } 1010 } 1011 1012 } 1013 1014 /** 1015 * Returns the names of the META-INF/MANIFEST.MF entry - if exists - 1016 * and any signature-related files under META-INF. This method is used in 1017 * JarFile, via SharedSecrets, as an optimization. 1018 */ 1019 private List<String> getManifestAndSignatureRelatedFiles() { 1020 synchronized (this) { 1021 ensureOpen(); 1022 Source zsrc = res.zsrc; 1023 int[] metanames = zsrc.signatureMetaNames; 1024 List<String> files = null; 1025 if (zsrc.manifestPos >= 0) { 1026 files = new ArrayList<>(); 1027 files.add(getEntryName(zsrc.manifestPos)); 1028 } 1029 if (metanames != null) { 1030 if (files == null) { 1031 files = new ArrayList<>(); 1032 } 1033 for (int i = 0; i < metanames.length; i++) { 1034 files.add(getEntryName(metanames[i])); 1035 } 1036 } 1037 return files == null ? List.of() : files; 1038 } 1039 } 1040 1041 /** 1042 * Returns the name of the META-INF/MANIFEST.MF entry, ignoring 1043 * case. If {@code onlyIfSignatureRelatedFiles} is true, we only return the 1044 * manifest if there is also at least one signature-related file. 1045 * This method is used in JarFile, via SharedSecrets, as an optimization 1046 * when looking up the manifest file. 1047 */ 1048 private String getManifestName(boolean onlyIfSignatureRelatedFiles) { 1049 synchronized (this) { 1050 ensureOpen(); 1051 Source zsrc = res.zsrc; 1052 int pos = zsrc.manifestPos; 1053 if (pos >= 0 && (!onlyIfSignatureRelatedFiles || zsrc.signatureMetaNames != null)) { 1054 return getEntryName(pos); 1055 } 1056 } 1057 return null; 1058 } 1059 1060 /** 1061 * Returns the versions for which there exists a non-directory 1062 * entry that begin with "META-INF/versions/" (case ignored). 1063 * This method is used in JarFile, via SharedSecrets, as an 1064 * optimization when looking up potentially versioned entries. 1065 * Returns an empty array if no versioned entries exist. 1066 */ 1067 private int[] getMetaInfVersions() { 1068 synchronized (this) { 1069 ensureOpen(); 1070 return res.zsrc.metaVersions; 1071 } 1072 } 1073 1074 private static boolean isWindows; 1075 1076 static { 1077 SharedSecrets.setJavaUtilZipFileAccess( 1078 new JavaUtilZipFileAccess() { 1079 @Override 1080 public boolean startsWithLocHeader(ZipFile zip) { 1081 return zip.res.zsrc.startsWithLoc; 1082 } 1083 @Override 1084 public List<String> getManifestAndSignatureRelatedFiles(JarFile jar) { 1085 return ((ZipFile)jar).getManifestAndSignatureRelatedFiles(); 1086 } 1087 @Override 1088 public String getManifestName(JarFile jar, boolean onlyIfHasSignatureRelatedFiles) { 1089 return ((ZipFile)jar).getManifestName(onlyIfHasSignatureRelatedFiles); 1090 } 1091 @Override 1092 public int[] getMetaInfVersions(JarFile jar) { 1093 return ((ZipFile)jar).getMetaInfVersions(); 1094 } 1095 @Override 1096 public JarEntry getEntry(ZipFile zip, String name, 1097 Function<String, JarEntry> func) { 1098 return (JarEntry)zip.getEntry(name, func); 1099 } 1100 @Override 1101 public Enumeration<JarEntry> entries(ZipFile zip, 1102 Function<String, JarEntry> func) { 1103 return zip.entries(func); 1104 } 1105 @Override 1106 public Stream<JarEntry> stream(ZipFile zip, 1107 Function<String, JarEntry> func) { 1108 return zip.stream(func); 1109 } 1110 @Override 1111 public Stream<String> entryNameStream(ZipFile zip) { 1112 return zip.entryNameStream(); 1113 } 1114 } 1115 ); 1116 isWindows = VM.getSavedProperty("os.name").contains("Windows"); 1117 } 1118 1119 private static class Source { 1120 // "META-INF/".length() 1121 private static final int META_INF_LENGTH = 9; 1122 private static final int[] EMPTY_META_VERSIONS = new int[0]; 1123 1124 private final Key key; // the key in files 1125 private final @Stable ZipCoder zc; // zip coder used to decode/encode 1126 1127 private int refs = 1; 1128 1129 private RandomAccessFile zfile; // zfile of the underlying zip file 1130 private byte[] cen; // CEN & ENDHDR 1131 private long locpos; // position of first LOC header (usually 0) 1132 private byte[] comment; // zip file comment 1133 // list of meta entries in META-INF dir 1134 private int manifestPos = -1; // position of the META-INF/MANIFEST.MF, if exists 1135 private int[] signatureMetaNames; // positions of signature related entries, if such exist 1136 private int[] metaVersions; // list of unique versions found in META-INF/versions/ 1137 private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) 1138 1139 // A Hashmap for all entries. 1140 // 1141 // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR, 1142 // We might have a lot of these in a typical system. In order to save space we don't 1143 // keep the name in memory, but merely remember a 32 bit {@code hash} value of the 1144 // entry name and its offset {@code pos} in the central directory hdeader. 1145 // 1146 // private static class Entry { 1147 // int hash; // 32 bit hashcode on name 1148 // int next; // hash chain: index into entries 1149 // int pos; // Offset of central directory file header 1150 // } 1151 // private Entry[] entries; // array of hashed cen entry 1152 // 1153 // To reduce the total size of entries further, we use a int[] here to store 3 "int" 1154 // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be 1155 // referred by their index of their positions in the {@code entries}. 1156 // 1157 private int[] entries; // array of hashed cen entry 1158 private int addEntry(int index, int hash, int next, int pos) { 1159 entries[index++] = hash; 1160 entries[index++] = next; 1161 entries[index++] = pos; 1162 return index; 1163 } 1164 private int getEntryHash(int index) { return entries[index]; } 1165 private int getEntryNext(int index) { return entries[index + 1]; } 1166 private int getEntryPos(int index) { return entries[index + 2]; } 1167 private static final int ZIP_ENDCHAIN = -1; 1168 private int total; // total number of entries 1169 private int[] table; // Hash chain heads: indexes into entries 1170 private int tablelen; // number of hash heads 1171 1172 private static class Key { 1173 final BasicFileAttributes attrs; 1174 File file; 1175 final boolean utf8; 1176 1177 public Key(File file, BasicFileAttributes attrs, ZipCoder zc) { 1178 this.attrs = attrs; 1179 this.file = file; 1180 this.utf8 = zc.isUTF8(); 1181 } 1182 1183 public int hashCode() { 1184 long t = utf8 ? 0 : Long.MAX_VALUE; 1185 t += attrs.lastModifiedTime().toMillis(); 1186 return ((int)(t ^ (t >>> 32))) + file.hashCode(); 1187 } 1188 1189 public boolean equals(Object obj) { 1190 if (obj instanceof Key) { 1191 Key key = (Key)obj; 1192 if (key.utf8 != utf8) { 1193 return false; 1194 } 1195 if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { 1196 return false; 1197 } 1198 Object fk = attrs.fileKey(); 1199 if (fk != null) { 1200 return fk.equals(key.attrs.fileKey()); 1201 } else { 1202 return file.equals(key.file); 1203 } 1204 } 1205 return false; 1206 } 1207 } 1208 private static final HashMap<Key, Source> files = new HashMap<>(); 1209 1210 1211 static Source get(File file, boolean toDelete, ZipCoder zc) throws IOException { 1212 final Key key; 1213 try { 1214 key = new Key(file, 1215 Files.readAttributes(file.toPath(), BasicFileAttributes.class), 1216 zc); 1217 } catch (InvalidPathException ipe) { 1218 throw new IOException(ipe); 1219 } 1220 Source src; 1221 synchronized (files) { 1222 src = files.get(key); 1223 if (src != null) { 1224 src.refs++; 1225 return src; 1226 } 1227 } 1228 src = new Source(key, toDelete, zc); 1229 1230 synchronized (files) { 1231 if (files.containsKey(key)) { // someone else put in first 1232 src.close(); // close the newly created one 1233 src = files.get(key); 1234 src.refs++; 1235 return src; 1236 } 1237 files.put(key, src); 1238 return src; 1239 } 1240 } 1241 1242 static void release(Source src) throws IOException { 1243 synchronized (files) { 1244 if (src != null && --src.refs == 0) { 1245 files.remove(src.key); 1246 src.close(); 1247 } 1248 } 1249 } 1250 1251 private Source(Key key, boolean toDelete, ZipCoder zc) throws IOException { 1252 this.zc = zc; 1253 this.key = key; 1254 if (toDelete) { 1255 if (isWindows) { 1256 this.zfile = SharedSecrets.getJavaIORandomAccessFileAccess() 1257 .openAndDelete(key.file, "r"); 1258 } else { 1259 this.zfile = new RandomAccessFile(key.file, "r"); 1260 key.file.delete(); 1261 } 1262 } else { 1263 this.zfile = new RandomAccessFile(key.file, "r"); 1264 } 1265 try { 1266 initCEN(-1); 1267 byte[] buf = new byte[4]; 1268 readFullyAt(buf, 0, 4, 0); 1269 this.startsWithLoc = (LOCSIG(buf) == LOCSIG); 1270 } catch (IOException x) { 1271 try { 1272 this.zfile.close(); 1273 } catch (IOException xx) {} 1274 throw x; 1275 } 1276 } 1277 1278 private void close() throws IOException { 1279 zfile.close(); 1280 zfile = null; 1281 cen = null; 1282 entries = null; 1283 table = null; 1284 manifestPos = -1; 1285 signatureMetaNames = null; 1286 metaVersions = EMPTY_META_VERSIONS; 1287 } 1288 1289 private static final int BUF_SIZE = 8192; 1290 private final int readFullyAt(byte[] buf, int off, int len, long pos) 1291 throws IOException 1292 { 1293 synchronized (zfile) { 1294 zfile.seek(pos); 1295 int N = len; 1296 while (N > 0) { 1297 int n = Math.min(BUF_SIZE, N); 1298 zfile.readFully(buf, off, n); 1299 off += n; 1300 N -= n; 1301 } 1302 return len; 1303 } 1304 } 1305 1306 private final int readAt(byte[] buf, int off, int len, long pos) 1307 throws IOException 1308 { 1309 synchronized (zfile) { 1310 zfile.seek(pos); 1311 return zfile.read(buf, off, len); 1312 } 1313 } 1314 1315 private static class End { 1316 int centot; // 4 bytes 1317 long cenlen; // 4 bytes 1318 long cenoff; // 4 bytes 1319 long endpos; // 4 bytes 1320 } 1321 1322 /* 1323 * Searches for end of central directory (END) header. The contents of 1324 * the END header will be read and placed in endbuf. Returns the file 1325 * position of the END header, otherwise returns -1 if the END header 1326 * was not found or an error occurred. 1327 */ 1328 private End findEND() throws IOException { 1329 long ziplen = zfile.length(); 1330 if (ziplen <= 0) 1331 zerror("zip file is empty"); 1332 End end = new End(); 1333 byte[] buf = new byte[READBLOCKSZ]; 1334 long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0; 1335 long minPos = minHDR - (buf.length - ENDHDR); 1336 for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) { 1337 int off = 0; 1338 if (pos < 0) { 1339 // Pretend there are some NUL bytes before start of file 1340 off = (int)-pos; 1341 Arrays.fill(buf, 0, off, (byte)0); 1342 } 1343 int len = buf.length - off; 1344 if (readFullyAt(buf, off, len, pos + off) != len ) { 1345 zerror("zip END header not found"); 1346 } 1347 // Now scan the block backwards for END header signature 1348 for (int i = buf.length - ENDHDR; i >= 0; i--) { 1349 if (buf[i+0] == (byte)'P' && 1350 buf[i+1] == (byte)'K' && 1351 buf[i+2] == (byte)'\005' && 1352 buf[i+3] == (byte)'\006') { 1353 // Found ENDSIG header 1354 byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR); 1355 end.centot = ENDTOT(endbuf); 1356 end.cenlen = ENDSIZ(endbuf); 1357 end.cenoff = ENDOFF(endbuf); 1358 end.endpos = pos + i; 1359 int comlen = ENDCOM(endbuf); 1360 if (end.endpos + ENDHDR + comlen != ziplen) { 1361 // ENDSIG matched, however the size of file comment in it does 1362 // not match the real size. One "common" cause for this problem 1363 // is some "extra" bytes are padded at the end of the zipfile. 1364 // Let's do some extra verification, we don't care about the 1365 // performance in this situation. 1366 byte[] sbuf = new byte[4]; 1367 long cenpos = end.endpos - end.cenlen; 1368 long locpos = cenpos - end.cenoff; 1369 if (cenpos < 0 || 1370 locpos < 0 || 1371 readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 || 1372 GETSIG(sbuf) != CENSIG || 1373 readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 || 1374 GETSIG(sbuf) != LOCSIG) { 1375 continue; 1376 } 1377 } 1378 if (comlen > 0) { // this zip file has comlen 1379 comment = new byte[comlen]; 1380 if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) { 1381 zerror("zip comment read failed"); 1382 } 1383 } 1384 // must check for a zip64 end record; it is always permitted to be present 1385 try { 1386 byte[] loc64 = new byte[ZIP64_LOCHDR]; 1387 if (end.endpos < ZIP64_LOCHDR || 1388 readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) 1389 != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { 1390 return end; 1391 } 1392 long end64pos = ZIP64_LOCOFF(loc64); 1393 byte[] end64buf = new byte[ZIP64_ENDHDR]; 1394 if (readFullyAt(end64buf, 0, end64buf.length, end64pos) 1395 != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { 1396 return end; 1397 } 1398 // end64 candidate found, 1399 long cenlen64 = ZIP64_ENDSIZ(end64buf); 1400 long cenoff64 = ZIP64_ENDOFF(end64buf); 1401 long centot64 = ZIP64_ENDTOT(end64buf); 1402 // double-check 1403 if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MAGICVAL || 1404 cenoff64 != end.cenoff && end.cenoff != ZIP64_MAGICVAL || 1405 centot64 != end.centot && end.centot != ZIP64_MAGICCOUNT) { 1406 return end; 1407 } 1408 // to use the end64 values 1409 end.cenlen = cenlen64; 1410 end.cenoff = cenoff64; 1411 end.centot = (int)centot64; // assume total < 2g 1412 end.endpos = end64pos; 1413 } catch (IOException x) {} // no zip64 loc/end 1414 return end; 1415 } 1416 } 1417 } 1418 zerror("zip END header not found"); 1419 return null; //make compiler happy 1420 } 1421 1422 // Reads zip file central directory. 1423 private void initCEN(int knownTotal) throws IOException { 1424 // Prefer locals for better performance during startup 1425 byte[] cen; 1426 if (knownTotal == -1) { 1427 End end = findEND(); 1428 if (end.endpos == 0) { 1429 locpos = 0; 1430 total = 0; 1431 entries = new int[0]; 1432 this.cen = null; 1433 return; // only END header present 1434 } 1435 if (end.cenlen > end.endpos) 1436 zerror("invalid END header (bad central directory size)"); 1437 long cenpos = end.endpos - end.cenlen; // position of CEN table 1438 // Get position of first local file (LOC) header, taking into 1439 // account that there may be a stub prefixed to the zip file. 1440 locpos = cenpos - end.cenoff; 1441 if (locpos < 0) { 1442 zerror("invalid END header (bad central directory offset)"); 1443 } 1444 // read in the CEN and END 1445 cen = this.cen = new byte[(int)(end.cenlen + ENDHDR)]; 1446 if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { 1447 zerror("read CEN tables failed"); 1448 } 1449 total = end.centot; 1450 } else { 1451 cen = this.cen; 1452 total = knownTotal; 1453 } 1454 // hash table for entries 1455 entries = new int[total * 3]; 1456 1457 this.tablelen = ((total/2) | 1); // Odd -> fewer collisions 1458 int tablelen = this.tablelen; 1459 1460 this.table = new int[tablelen]; 1461 int[] table = this.table; 1462 1463 Arrays.fill(table, ZIP_ENDCHAIN); 1464 int idx = 0; 1465 int hash; 1466 int next; 1467 1468 // list for all meta entries 1469 ArrayList<Integer> signatureNames = null; 1470 // Set of all version numbers seen in META-INF/versions/ 1471 Set<Integer> metaVersionsSet = null; 1472 1473 // Iterate through the entries in the central directory 1474 int i = 0; 1475 int hsh; 1476 int pos = 0; 1477 int entryPos = CENHDR; 1478 int limit = cen.length - ENDHDR; 1479 while (entryPos <= limit) { 1480 if (i >= total) { 1481 // This will only happen if the zip file has an incorrect 1482 // ENDTOT field, which usually means it contains more than 1483 // 65535 entries. 1484 initCEN(countCENHeaders(cen, limit)); 1485 return; 1486 } 1487 if (CENSIG(cen, pos) != CENSIG) 1488 zerror("invalid CEN header (bad signature)"); 1489 int method = CENHOW(cen, pos); 1490 int nlen = CENNAM(cen, pos); 1491 int elen = CENEXT(cen, pos); 1492 int clen = CENCOM(cen, pos); 1493 if ((CENFLG(cen, pos) & 1) != 0) 1494 zerror("invalid CEN header (encrypted entry)"); 1495 if (method != STORED && method != DEFLATED) 1496 zerror("invalid CEN header (bad compression method: " + method + ")"); 1497 if (entryPos + nlen > limit) 1498 zerror("invalid CEN header (bad header size)"); 1499 // Record the CEN offset and the name hash in our hash cell. 1500 hash = zipCoderForPos(pos).normalizedHash(cen, entryPos, nlen); 1501 hsh = (hash & 0x7fffffff) % tablelen; 1502 next = table[hsh]; 1503 table[hsh] = idx; 1504 idx = addEntry(idx, hash, next, pos); 1505 // Adds name to metanames. 1506 if (isMetaName(cen, entryPos, nlen)) { 1507 if (isManifestName(cen, entryPos + META_INF_LENGTH, 1508 nlen - META_INF_LENGTH)) { 1509 manifestPos = entryPos; 1510 } else { 1511 if (isSignatureRelated(cen, entryPos, nlen)) { 1512 if (signatureNames == null) 1513 signatureNames = new ArrayList<>(4); 1514 signatureNames.add(pos); 1515 } 1516 1517 // If this is a versioned entry, parse the version 1518 // and store it for later. This optimizes lookup 1519 // performance in multi-release jar files 1520 int version = getMetaVersion(cen, 1521 entryPos + META_INF_LENGTH, nlen - META_INF_LENGTH); 1522 if (version > 0) { 1523 if (metaVersionsSet == null) 1524 metaVersionsSet = new TreeSet<>(); 1525 metaVersionsSet.add(version); 1526 } 1527 } 1528 } 1529 // skip ext and comment 1530 pos = entryPos + nlen + elen + clen; 1531 entryPos = pos + CENHDR; 1532 i++; 1533 } 1534 total = i; 1535 if (signatureNames != null) { 1536 int len = signatureNames.size(); 1537 signatureMetaNames = new int[len]; 1538 for (int j = 0; j < len; j++) { 1539 signatureMetaNames[j] = signatureNames.get(j); 1540 } 1541 } 1542 if (metaVersionsSet != null) { 1543 metaVersions = new int[metaVersionsSet.size()]; 1544 int c = 0; 1545 for (Integer version : metaVersionsSet) { 1546 metaVersions[c++] = version; 1547 } 1548 } else { 1549 metaVersions = EMPTY_META_VERSIONS; 1550 } 1551 if (pos + ENDHDR != cen.length) { 1552 zerror("invalid CEN header (bad header size)"); 1553 } 1554 } 1555 1556 private static void zerror(String msg) throws ZipException { 1557 throw new ZipException(msg); 1558 } 1559 1560 /* 1561 * Returns the {@code pos} of the zip cen entry corresponding to the 1562 * specified entry name, or -1 if not found. 1563 */ 1564 private int getEntryPos(String name, boolean addSlash) { 1565 if (total == 0) { 1566 return -1; 1567 } 1568 1569 int hsh = ZipCoder.normalizedHash(name); 1570 int idx = table[(hsh & 0x7fffffff) % tablelen]; 1571 1572 // Search down the target hash chain for a entry whose 1573 // 32 bit hash matches the hashed name. 1574 while (idx != ZIP_ENDCHAIN) { 1575 if (getEntryHash(idx) == hsh) { 1576 // The CEN name must match the specfied one 1577 int pos = getEntryPos(idx); 1578 1579 try { 1580 ZipCoder zc = zipCoderForPos(pos); 1581 String entry = zc.toString(cen, pos + CENHDR, CENNAM(cen, pos)); 1582 1583 // If addSlash is true we'll test for name+/ in addition to 1584 // name, unless name is the empty string or already ends with a 1585 // slash 1586 int entryLen = entry.length(); 1587 int nameLen = name.length(); 1588 if ((entryLen == nameLen && entry.equals(name)) || 1589 (addSlash && 1590 nameLen + 1 == entryLen && 1591 entry.startsWith(name) && 1592 entry.charAt(entryLen - 1) == '/')) { 1593 return pos; 1594 } 1595 } catch (IllegalArgumentException iae) { 1596 // Ignore 1597 } 1598 } 1599 idx = getEntryNext(idx); 1600 } 1601 return -1; 1602 } 1603 1604 private ZipCoder zipCoderForPos(int pos) { 1605 if (zc.isUTF8()) { 1606 return zc; 1607 } 1608 if ((CENFLG(cen, pos) & USE_UTF8) != 0) { 1609 return ZipCoder.UTF8; 1610 } 1611 return zc; 1612 } 1613 1614 /** 1615 * Returns true if the bytes represent a non-directory name 1616 * beginning with "META-INF/", disregarding ASCII case. 1617 */ 1618 private static boolean isMetaName(byte[] name, int off, int len) { 1619 // Use the "oldest ASCII trick in the book" 1620 return len > META_INF_LENGTH // "META-INF/".length() 1621 && name[off + len - 1] != '/' // non-directory 1622 && (name[off++] | 0x20) == 'm' 1623 && (name[off++] | 0x20) == 'e' 1624 && (name[off++] | 0x20) == 't' 1625 && (name[off++] | 0x20) == 'a' 1626 && (name[off++] ) == '-' 1627 && (name[off++] | 0x20) == 'i' 1628 && (name[off++] | 0x20) == 'n' 1629 && (name[off++] | 0x20) == 'f' 1630 && (name[off] ) == '/'; 1631 } 1632 1633 /* 1634 * Check if the bytes represents a name equals to MANIFEST.MF 1635 */ 1636 private static boolean isManifestName(byte[] name, int off, int len) { 1637 return (len == 11 // "MANIFEST.MF".length() 1638 && (name[off++] | 0x20) == 'm' 1639 && (name[off++] | 0x20) == 'a' 1640 && (name[off++] | 0x20) == 'n' 1641 && (name[off++] | 0x20) == 'i' 1642 && (name[off++] | 0x20) == 'f' 1643 && (name[off++] | 0x20) == 'e' 1644 && (name[off++] | 0x20) == 's' 1645 && (name[off++] | 0x20) == 't' 1646 && (name[off++] | 0x20) == '.' 1647 && (name[off++] | 0x20) == 'm' 1648 && (name[off++] | 0x20) == 'f'); 1649 } 1650 1651 private boolean isSignatureRelated(byte[] cen, int start, int len) { 1652 // Check if entry ends with .EC and .SF 1653 if (cen[start + len - 3] == '.') { 1654 int b1 = cen[start + len - 2] | 0x20; 1655 int b2 = cen[start + len - 1] | 0x20; 1656 if ((b1 == 'e' && b2 == 'c') || (b1 == 's' && b2 == 'f')) { 1657 return true; 1658 } 1659 } 1660 // Check if entry ends with .DSA and .RSA 1661 if (cen[start + len - 4] == '.') { 1662 int b1 = cen[start + len - 3] | 0x20; 1663 int b2 = cen[start + len - 2] | 0x20; 1664 int b3 = cen[start + len - 1] | 0x20; 1665 if ((b1 == 'r' || b1 == 'd') && b2 == 's' && b3 == 'a') { 1666 return true; 1667 } 1668 } 1669 return false; 1670 } 1671 1672 /* 1673 * If the bytes represents a non-directory name beginning 1674 * with "versions/", continuing with a positive integer, 1675 * followed by a '/', then return that integer value. 1676 * Otherwise, return 0 1677 */ 1678 private static int getMetaVersion(byte[] name, int off, int len) { 1679 int nend = off + len; 1680 if (!(len > 10 // "versions//".length() 1681 && name[off + len - 1] != '/' // non-directory 1682 && (name[off++] | 0x20) == 'v' 1683 && (name[off++] | 0x20) == 'e' 1684 && (name[off++] | 0x20) == 'r' 1685 && (name[off++] | 0x20) == 's' 1686 && (name[off++] | 0x20) == 'i' 1687 && (name[off++] | 0x20) == 'o' 1688 && (name[off++] | 0x20) == 'n' 1689 && (name[off++] | 0x20) == 's' 1690 && (name[off++] ) == '/')) { 1691 return 0; 1692 } 1693 int version = 0; 1694 while (off < nend) { 1695 final byte c = name[off++]; 1696 if (c == '/') { 1697 return version; 1698 } 1699 if (c < '0' || c > '9') { 1700 return 0; 1701 } 1702 version = version * 10 + c - '0'; 1703 // Check for overflow and leading zeros 1704 if (version <= 0) { 1705 return 0; 1706 } 1707 } 1708 return 0; 1709 } 1710 1711 /** 1712 * Returns the number of CEN headers in a central directory. 1713 * Will not throw, even if the zip file is corrupt. 1714 * 1715 * @param cen copy of the bytes in a zip file's central directory 1716 * @param size number of bytes in central directory 1717 */ 1718 private static int countCENHeaders(byte[] cen, int size) { 1719 int count = 0; 1720 for (int p = 0; 1721 p + CENHDR <= size; 1722 p += CENHDR + CENNAM(cen, p) + CENEXT(cen, p) + CENCOM(cen, p)) 1723 count++; 1724 return count; 1725 } 1726 } 1727 }