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