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