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