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