1 /*
   2  * Copyright (c) 1995, 2015, 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.nio.charset.Charset;
  35 import java.nio.charset.StandardCharsets;
  36 import java.nio.file.attribute.BasicFileAttributes;
  37 import java.nio.file.Path;
  38 import java.nio.file.Files;
  39 
  40 import java.util.ArrayDeque;
  41 import java.util.ArrayList;
  42 import java.util.Arrays;
  43 import java.util.Deque;
  44 import java.util.Enumeration;
  45 import java.util.HashMap;
  46 import java.util.Iterator;
  47 import java.util.Map;
  48 import java.util.Objects;
  49 import java.util.NoSuchElementException;
  50 import java.util.Spliterator;
  51 import java.util.Spliterators;
  52 import java.util.WeakHashMap;
  53 import java.util.stream.Stream;
  54 import java.util.stream.StreamSupport;
  55 import jdk.internal.misc.JavaUtilZipFileAccess;
  56 import jdk.internal.misc.SharedSecrets;
  57 import jdk.internal.perf.PerfCounter;
  58 
  59 import static java.util.zip.ZipConstants.*;
  60 import static java.util.zip.ZipConstants64.*;
  61 import static java.util.zip.ZipUtils.*;
  62 
  63 /**
  64  * This class is used to read entries from a zip file.
  65  *
  66  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
  67  * or method in this class will cause a {@link NullPointerException} to be
  68  * thrown.
  69  *
  70  * @author      David Connelly
  71  */
  72 public
  73 class ZipFile implements ZipConstants, Closeable {
  74 
  75     private final String name;     // zip file name
  76     private volatile boolean closeRequested;
  77     private Source zsrc;
  78     private ZipCoder zc;
  79 
  80     private static final int STORED = ZipEntry.STORED;
  81     private static final int DEFLATED = ZipEntry.DEFLATED;
  82 
  83     /**
  84      * Mode flag to open a zip file for reading.
  85      */
  86     public static final int OPEN_READ = 0x1;
  87 
  88     /**
  89      * Mode flag to open a zip file and mark it for deletion.  The file will be
  90      * deleted some time between the moment that it is opened and the moment
  91      * that it is closed, but its contents will remain accessible via the
  92      * {@code ZipFile} object until either the close method is invoked or the
  93      * virtual machine exits.
  94      */
  95     public static final int OPEN_DELETE = 0x4;
  96 
  97     /**
  98      * Opens a zip file for reading.
  99      *
 100      * <p>First, if there is a security manager, its {@code checkRead}
 101      * method is called with the {@code name} argument as its argument
 102      * to ensure the read is allowed.
 103      *
 104      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 105      * decode the entry names and comments.
 106      *
 107      * @param name the name of the zip file
 108      * @throws ZipException if a ZIP format error has occurred
 109      * @throws IOException if an I/O error has occurred
 110      * @throws SecurityException if a security manager exists and its
 111      *         {@code checkRead} method doesn't allow read access to the file.
 112      *
 113      * @see SecurityManager#checkRead(java.lang.String)
 114      */
 115     public ZipFile(String name) throws IOException {
 116         this(new File(name), OPEN_READ);
 117     }
 118 
 119     /**
 120      * Opens a new {@code ZipFile} to read from the specified
 121      * {@code File} object in the specified mode.  The mode argument
 122      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 123      *
 124      * <p>First, if there is a security manager, its {@code checkRead}
 125      * method is called with the {@code name} argument as its argument to
 126      * ensure the read is allowed.
 127      *
 128      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 129      * decode the entry names and comments
 130      *
 131      * @param file the ZIP file to be opened for reading
 132      * @param mode the mode in which the file is to be opened
 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
 136      *         its {@code checkRead} method
 137      *         doesn't allow read access to the file,
 138      *         or its {@code checkDelete} method doesn't allow deleting
 139      *         the file when the {@code OPEN_DELETE} flag is set.
 140      * @throws IllegalArgumentException if the {@code mode} argument is invalid
 141      * @see SecurityManager#checkRead(java.lang.String)
 142      * @since 1.3
 143      */
 144     public ZipFile(File file, int mode) throws IOException {
 145         this(file, mode, StandardCharsets.UTF_8);
 146     }
 147 
 148     /**
 149      * Opens a ZIP file for reading given the specified File object.
 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      * @throws ZipException if a ZIP format error has occurred
 156      * @throws IOException if an I/O error has occurred
 157      */
 158     public ZipFile(File file) throws ZipException, IOException {
 159         this(file, OPEN_READ);
 160     }
 161 
 162     /**
 163      * Opens a new {@code ZipFile} to read from the specified
 164      * {@code File} object in the specified mode.  The mode argument
 165      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 166      *
 167      * <p>First, if there is a security manager, its {@code checkRead}
 168      * method is called with the {@code name} argument as its argument to
 169      * ensure the read is allowed.
 170      *
 171      * @param file the ZIP file to be opened for reading
 172      * @param mode the mode in which the file is to be opened
 173      * @param charset
 174      *        the {@linkplain java.nio.charset.Charset charset} to
 175      *        be used to decode the ZIP entry name and comment that are not
 176      *        encoded by using UTF-8 encoding (indicated by entry's general
 177      *        purpose flag).
 178      *
 179      * @throws ZipException if a ZIP format error has occurred
 180      * @throws IOException if an I/O error has occurred
 181      *
 182      * @throws SecurityException
 183      *         if a security manager exists and its {@code checkRead}
 184      *         method doesn't allow read access to the file,or its
 185      *         {@code checkDelete} method doesn't allow deleting the
 186      *         file when the {@code OPEN_DELETE} flag is set
 187      *
 188      * @throws IllegalArgumentException if the {@code mode} argument is invalid
 189      *
 190      * @see SecurityManager#checkRead(java.lang.String)
 191      *
 192      * @since 1.7
 193      */
 194     public ZipFile(File file, int mode, Charset charset) throws IOException
 195     {
 196         if (((mode & OPEN_READ) == 0) ||
 197             ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
 198             throw new IllegalArgumentException("Illegal mode: 0x"+
 199                                                Integer.toHexString(mode));
 200         }
 201         String name = file.getPath();
 202         SecurityManager sm = System.getSecurityManager();
 203         if (sm != null) {
 204             sm.checkRead(name);
 205             if ((mode & OPEN_DELETE) != 0) {
 206                 sm.checkDelete(name);
 207             }
 208         }
 209         Objects.requireNonNull(charset, "charset");
 210         this.zc = ZipCoder.get(charset);
 211         this.name = name;
 212         long t0 = System.nanoTime();
 213         this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0);
 214         PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
 215         PerfCounter.getZipFileCount().increment();
 216     }
 217 
 218     /**
 219      * Opens a zip file for reading.
 220      *
 221      * <p>First, if there is a security manager, its {@code checkRead}
 222      * method is called with the {@code name} argument as its argument
 223      * to ensure the read is allowed.
 224      *
 225      * @param name the name of the zip file
 226      * @param charset
 227      *        the {@linkplain java.nio.charset.Charset charset} to
 228      *        be used to decode the ZIP entry name and comment that are not
 229      *        encoded by using UTF-8 encoding (indicated by entry's general
 230      *        purpose flag).
 231      *
 232      * @throws ZipException if a ZIP format error has occurred
 233      * @throws IOException if an I/O error has occurred
 234      * @throws SecurityException
 235      *         if a security manager exists and its {@code checkRead}
 236      *         method doesn't allow read access to the file
 237      *
 238      * @see SecurityManager#checkRead(java.lang.String)
 239      *
 240      * @since 1.7
 241      */
 242     public ZipFile(String name, Charset charset) throws IOException
 243     {
 244         this(new File(name), OPEN_READ, charset);
 245     }
 246 
 247     /**
 248      * Opens a ZIP file for reading given the specified File object.
 249      *
 250      * @param file the ZIP file to be opened for reading
 251      * @param charset
 252      *        The {@linkplain java.nio.charset.Charset charset} to be
 253      *        used to decode the ZIP entry name and comment (ignored if
 254      *        the <a href="package-summary.html#lang_encoding"> language
 255      *        encoding bit</a> of the ZIP entry's general purpose bit
 256      *        flag is set).
 257      *
 258      * @throws ZipException if a ZIP format error has occurred
 259      * @throws IOException if an I/O error has occurred
 260      *
 261      * @since 1.7
 262      */
 263     public ZipFile(File file, Charset charset) throws IOException
 264     {
 265         this(file, OPEN_READ, charset);
 266     }
 267 
 268     /**
 269      * Returns the zip file comment, or null if none.
 270      *
 271      * @return the comment string for the zip file, or null if none
 272      *
 273      * @throws IllegalStateException if the zip file has been closed
 274      *
 275      * Since 1.7
 276      */
 277     public String getComment() {
 278         synchronized (this) {
 279             ensureOpen();
 280             if (zsrc.comment == null) {
 281                 return null;
 282             }
 283             return zc.toString(zsrc.comment);
 284         }
 285     }
 286 
 287     /**
 288      * Returns the zip file entry for the specified name, or null
 289      * if not found.
 290      *
 291      * @param name the name of the entry
 292      * @return the zip file entry, or null if not found
 293      * @throws IllegalStateException if the zip file has been closed
 294      */
 295     public ZipEntry getEntry(String name) {
 296 
 297         Objects.requireNonNull(name, "name");
 298         synchronized (this) {
 299             ensureOpen();
 300             int pos = zsrc.getEntryPos(zc.getBytes(name), true);
 301             if (pos != -1) {
 302                 return getZipEntry(name, pos);
 303             }
 304         }
 305         return null;
 306     }
 307 
 308     // The outstanding inputstreams that need to be closed,
 309     // mapped to the inflater objects they use.
 310     private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
 311 
 312     /**
 313      * Returns an input stream for reading the contents of the specified
 314      * zip file entry.
 315      * <p>
 316      * Closing this ZIP file will, in turn, close all input streams that
 317      * have been returned by invocations of this method.
 318      *
 319      * @param entry the zip file entry
 320      * @return the input stream for reading the contents of the specified
 321      * zip file entry.
 322      * @throws ZipException if a ZIP format error has occurred
 323      * @throws IOException if an I/O error has occurred
 324      * @throws IllegalStateException if the zip file has been closed
 325      */
 326     public InputStream getInputStream(ZipEntry entry) throws IOException {
 327         Objects.requireNonNull(entry, "entry");
 328         int pos = -1;
 329         ZipFileInputStream in = null;
 330         synchronized (this) {
 331             ensureOpen();
 332             if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
 333                 pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false);
 334             } else {
 335                 pos = zsrc.getEntryPos(zc.getBytes(entry.name), false);
 336             }
 337             if (pos == -1) {
 338                 return null;
 339             }
 340             in = new ZipFileInputStream(zsrc.cen, pos);
 341             switch (CENHOW(zsrc.cen, pos)) {
 342             case STORED:
 343                 synchronized (streams) {
 344                     streams.put(in, null);
 345                 }
 346                 return in;
 347             case DEFLATED:
 348                 // Inflater likes a bit of slack
 349                 // MORE: Compute good size for inflater stream:
 350                 long size = CENLEN(zsrc.cen, pos) + 2;
 351                 if (size > 65536) {
 352                     size = 8192;
 353                 }
 354                 if (size <= 0) {
 355                     size = 4096;
 356                 }
 357                 Inflater inf = getInflater();
 358                 InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size);
 359                 synchronized (streams) {
 360                     streams.put(is, inf);
 361                 }
 362                 return is;
 363             default:
 364                 throw new ZipException("invalid compression method");
 365             }
 366         }
 367     }
 368 
 369     private class ZipFileInflaterInputStream extends InflaterInputStream {
 370         private volatile boolean closeRequested;
 371         private boolean eof = false;
 372         private final ZipFileInputStream zfin;
 373 
 374         ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
 375                 int size) {
 376             super(zfin, inf, size);
 377             this.zfin = zfin;
 378         }
 379 
 380         public void close() throws IOException {
 381             if (closeRequested)
 382                 return;
 383             closeRequested = true;
 384 
 385             super.close();
 386             Inflater inf;
 387             synchronized (streams) {
 388                 inf = streams.remove(this);
 389             }
 390             if (inf != null) {
 391                 releaseInflater(inf);
 392             }
 393         }
 394 
 395         // Override fill() method to provide an extra "dummy" byte
 396         // at the end of the input stream. This is required when
 397         // using the "nowrap" Inflater option.
 398         protected void fill() throws IOException {
 399             if (eof) {
 400                 throw new EOFException("Unexpected end of ZLIB input stream");
 401             }
 402             len = in.read(buf, 0, buf.length);
 403             if (len == -1) {
 404                 buf[0] = 0;
 405                 len = 1;
 406                 eof = true;
 407             }
 408             inf.setInput(buf, 0, len);
 409         }
 410 
 411         public int available() throws IOException {
 412             if (closeRequested)
 413                 return 0;
 414             long avail = zfin.size() - inf.getBytesWritten();
 415             return (avail > (long) Integer.MAX_VALUE ?
 416                     Integer.MAX_VALUE : (int) avail);
 417         }
 418 
 419         protected void finalize() throws Throwable {
 420             close();
 421         }
 422     }
 423 
 424     /*
 425      * Gets an inflater from the list of available inflaters or allocates
 426      * a new one.
 427      */
 428     private Inflater getInflater() {
 429         Inflater inf;
 430         synchronized (inflaterCache) {
 431             while ((inf = inflaterCache.poll()) != null) {
 432                 if (!inf.ended()) {
 433                     return inf;
 434                 }
 435             }
 436         }
 437         return new Inflater(true);
 438     }
 439 
 440     /*
 441      * Releases the specified inflater to the list of available inflaters.
 442      */
 443     private void releaseInflater(Inflater inf) {
 444         if (!inf.ended()) {
 445             inf.reset();
 446             synchronized (inflaterCache) {
 447                 inflaterCache.add(inf);
 448             }
 449         }
 450     }
 451 
 452     // List of available Inflater objects for decompression
 453     private final Deque<Inflater> inflaterCache = new ArrayDeque<>();
 454 
 455     /**
 456      * Returns the path name of the ZIP file.
 457      * @return the path name of the ZIP file
 458      */
 459     public String getName() {
 460         return name;
 461     }
 462 
 463     private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
 464         private int i = 0;
 465 
 466         public ZipEntryIterator() {
 467             ensureOpen();
 468         }
 469 
 470         public boolean hasMoreElements() {
 471             return hasNext();
 472         }
 473 
 474         public boolean hasNext() {
 475             synchronized (ZipFile.this) {
 476                 ensureOpen();
 477                 return i < zsrc.total;
 478             }
 479         }
 480 
 481         public ZipEntry nextElement() {
 482             return next();
 483         }
 484 
 485         public ZipEntry next() {
 486             synchronized (ZipFile.this) {
 487                 ensureOpen();
 488                 if (i >= zsrc.total) {
 489                     throw new NoSuchElementException();
 490                 }
 491                 // each "entry" has 3 ints in table entries
 492                 return getZipEntry(null, zsrc.getEntryPos(i++ * 3));
 493             }
 494         }
 495 
 496         public Iterator<ZipEntry> asIterator() {
 497             return this;
 498         }
 499     }
 500 
 501     /**
 502      * Returns an enumeration of the ZIP file entries.
 503      * @return an enumeration of the ZIP file entries
 504      * @throws IllegalStateException if the zip file has been closed
 505      */
 506     public Enumeration<? extends ZipEntry> entries() {
 507         return new ZipEntryIterator();
 508     }
 509 
 510     /**
 511      * Returns an ordered {@code Stream} over the ZIP file entries.
 512      * Entries appear in the {@code Stream} in the order they appear in
 513      * the central directory of the ZIP file.
 514      *
 515      * @return an ordered {@code Stream} of entries in this ZIP file
 516      * @throws IllegalStateException if the zip file has been closed
 517      * @since 1.8
 518      */
 519     public Stream<? extends ZipEntry> stream() {
 520         return StreamSupport.stream(Spliterators.spliterator(
 521                 new ZipEntryIterator(), size(),
 522                 Spliterator.ORDERED | Spliterator.DISTINCT |
 523                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
 524     }
 525 
 526     /* Checks ensureOpen() before invoke this method */
 527     private ZipEntry getZipEntry(String name, int pos) {
 528         byte[] cen = zsrc.cen;
 529         ZipEntry e = new ZipEntry();
 530         int nlen = CENNAM(cen, pos);
 531         int elen = CENEXT(cen, pos);
 532         int clen = CENCOM(cen, pos);
 533         e.flag = CENFLG(cen, pos);  // get the flag first
 534         if (name != null) {
 535             e.name = name;
 536         } else {
 537             if (!zc.isUTF8() && (e.flag & EFS) != 0) {
 538                 e.name = zc.toStringUTF8(cen, pos + CENHDR, nlen);
 539             } else {
 540                 e.name = zc.toString(cen, pos + CENHDR, nlen);
 541             }
 542         }
 543         e.xdostime = CENTIM(cen, pos);
 544         e.crc = CENCRC(cen, pos);
 545         e.size = CENLEN(cen, pos);
 546         e.csize = CENSIZ(cen, pos);
 547         e.method = CENHOW(cen, pos);
 548         if (elen != 0) {
 549             e.setExtra0(Arrays.copyOfRange(cen, pos + CENHDR + nlen,
 550                                            pos + CENHDR + nlen + elen), true);
 551         }
 552         if (clen != 0) {
 553             if (!zc.isUTF8() && (e.flag & EFS) != 0) {
 554                 e.comment = zc.toStringUTF8(cen, pos + CENHDR + nlen + elen, clen);
 555             } else {
 556                 e.comment = zc.toString(cen, pos + CENHDR + nlen + elen, clen);
 557             }
 558         }
 559         return e;
 560     }
 561 
 562     /**
 563      * Returns the number of entries in the ZIP file.
 564      *
 565      * @return the number of entries in the ZIP file
 566      * @throws IllegalStateException if the zip file has been closed
 567      */
 568     public int size() {
 569         synchronized (this) {
 570             ensureOpen();
 571             return zsrc.total;
 572         }
 573     }
 574 
 575     /**
 576      * Closes the ZIP file.
 577      * <p> Closing this ZIP file will close all of the input streams
 578      * previously returned by invocations of the {@link #getInputStream
 579      * getInputStream} method.
 580      *
 581      * @throws IOException if an I/O error has occurred
 582      */
 583     public void close() throws IOException {
 584         if (closeRequested) {
 585             return;
 586         }
 587         closeRequested = true;
 588 
 589         synchronized (this) {
 590             // Close streams, release their inflaters
 591             synchronized (streams) {
 592                 if (!streams.isEmpty()) {
 593                     Map<InputStream, Inflater> copy = new HashMap<>(streams);
 594                     streams.clear();
 595                     for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) {
 596                         e.getKey().close();
 597                         Inflater inf = e.getValue();
 598                         if (inf != null) {
 599                             inf.end();
 600                         }
 601                     }
 602                 }
 603             }
 604             // Release cached inflaters
 605             synchronized (inflaterCache) {
 606                 Inflater inf;
 607                 while ((inf = inflaterCache.poll()) != null) {
 608                     inf.end();
 609                 }
 610             }
 611             // Release zip src
 612             if (zsrc != null) {
 613                 Source.close(zsrc);
 614                 zsrc = null;
 615             }
 616         }
 617     }
 618 
 619     /**
 620      * Ensures that the system resources held by this ZipFile object are
 621      * released when there are no more references to it.
 622      *
 623      * <p>
 624      * Since the time when GC would invoke this method is undetermined,
 625      * it is strongly recommended that applications invoke the {@code close}
 626      * method as soon they have finished accessing this {@code ZipFile}.
 627      * This will prevent holding up system resources for an undetermined
 628      * length of time.
 629      *
 630      * @throws IOException if an I/O error has occurred
 631      * @see    java.util.zip.ZipFile#close()
 632      */
 633     protected void finalize() throws IOException {
 634         close();
 635     }
 636 
 637     private void ensureOpen() {
 638         if (closeRequested) {
 639             throw new IllegalStateException("zip file closed");
 640         }
 641         if (zsrc == null) {
 642             throw new IllegalStateException("The object is not initialized.");
 643         }
 644     }
 645 
 646     private void ensureOpenOrZipException() throws IOException {
 647         if (closeRequested) {
 648             throw new ZipException("ZipFile closed");
 649         }
 650     }
 651 
 652     /*
 653      * Inner class implementing the input stream used to read a
 654      * (possibly compressed) zip file entry.
 655      */
 656    private class ZipFileInputStream extends InputStream {
 657         private volatile boolean closeRequested;
 658         private   long pos;     // current position within entry data
 659         protected long rem;     // number of remaining bytes within entry
 660         protected long size;    // uncompressed size of this entry
 661 
 662         ZipFileInputStream(byte[] cen, int cenpos) throws IOException {
 663             rem = CENSIZ(cen, cenpos);
 664             size = CENLEN(cen, cenpos);
 665             pos = CENOFF(cen, cenpos);
 666             // zip64
 667             if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL ||
 668                 pos == ZIP64_MAGICVAL) {
 669                 checkZIP64(cen, cenpos);
 670             }
 671             // negative for lazy initialization, see getDataOffset();
 672             pos = - (pos + ZipFile.this.zsrc.locpos);
 673         }
 674 
 675         private void checkZIP64(byte[] cen, int cenpos) throws IOException {
 676             int off = cenpos + CENHDR + CENNAM(cen, cenpos);
 677             int end = off + CENEXT(cen, cenpos);
 678             while (off + 4 < end) {
 679                 int tag = get16(cen, off);
 680                 int sz = get16(cen, off + 2);
 681                 off += 4;
 682                 if (off + sz > end)         // invalid data
 683                     break;
 684                 if (tag == EXTID_ZIP64) {
 685                     if (size == ZIP64_MAGICVAL) {
 686                         if (sz < 8 || (off + 8) > end)
 687                             break;
 688                         size = get64(cen, off);
 689                         sz -= 8;
 690                         off += 8;
 691                     }
 692                     if (rem == ZIP64_MAGICVAL) {
 693                         if (sz < 8 || (off + 8) > end)
 694                             break;
 695                         rem = get64(cen, off);
 696                         sz -= 8;
 697                         off += 8;
 698                     }
 699                     if (pos == ZIP64_MAGICVAL) {
 700                         if (sz < 8 || (off + 8) > end)
 701                             break;
 702                         pos = get64(cen, off);
 703                         sz -= 8;
 704                         off += 8;
 705                     }
 706                     break;
 707                 }
 708                 off += sz;
 709             }
 710         }
 711 
 712        /* The Zip file spec explicitly allows the LOC extra data size to
 713         * be different from the CEN extra data size. Since we cannot trust
 714         * the CEN extra data size, we need to read the LOC to determine
 715         * the entry data offset.
 716         */
 717         private long initDataOffset() throws IOException {
 718             if (pos <= 0) {
 719                 byte[] loc = new byte[LOCHDR];
 720                 pos = -pos;
 721                 int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos);
 722                 if (len != LOCHDR) {
 723                     throw new ZipException("ZipFile error reading zip file");
 724                 }
 725                 if (LOCSIG(loc) != LOCSIG) {
 726                     throw new ZipException("ZipFile invalid LOC header (bad signature)");
 727                 }
 728                 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc);
 729             }
 730             return pos;
 731         }
 732 
 733         public int read(byte b[], int off, int len) throws IOException {
 734             synchronized (ZipFile.this) {
 735                 ensureOpenOrZipException();
 736                 initDataOffset();
 737                 if (rem == 0) {
 738                     return -1;
 739                 }
 740                 if (len > rem) {
 741                     len = (int) rem;
 742                 }
 743                 if (len <= 0) {
 744                     return 0;
 745                 }
 746                 len = ZipFile.this.zsrc.readAt(b, off, len, pos);
 747                 if (len > 0) {
 748                     pos += len;
 749                     rem -= len;
 750                 }
 751             }
 752             if (rem == 0) {
 753                 close();
 754             }
 755             return len;
 756         }
 757 
 758         public int read() throws IOException {
 759             byte[] b = new byte[1];
 760             if (read(b, 0, 1) == 1) {
 761                 return b[0] & 0xff;
 762             } else {
 763                 return -1;
 764             }
 765         }
 766 
 767         public long skip(long n) throws IOException {
 768             synchronized (ZipFile.this) {
 769                 ensureOpenOrZipException();
 770                 initDataOffset();
 771                 if (n > rem) {
 772                     n = rem;
 773                 }
 774                 pos += n;
 775                 rem -= n;
 776             }
 777             if (rem == 0) {
 778                 close();
 779             }
 780             return n;
 781         }
 782 
 783         public int available() {
 784             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
 785         }
 786 
 787         public long size() {
 788             return size;
 789         }
 790 
 791         public void close() {
 792             if (closeRequested) {
 793                 return;
 794             }
 795             closeRequested = true;
 796             rem = 0;
 797             synchronized (streams) {
 798                 streams.remove(this);
 799             }
 800         }
 801 
 802         protected void finalize() {
 803             close();
 804         }
 805     }
 806 
 807     static {
 808         SharedSecrets.setJavaUtilZipFileAccess(
 809             new JavaUtilZipFileAccess() {
 810                 public boolean startsWithLocHeader(ZipFile zip) {
 811                     return zip.zsrc.startsWithLoc;
 812                 }
 813                 public String[] getMetaInfEntryNames(ZipFile zip) {
 814                     return zip.getMetaInfEntryNames();
 815                 }
 816              }
 817         );
 818     }
 819 
 820     /*
 821      * Returns an array of strings representing the names of all entries
 822      * that begin with "META-INF/" (case ignored). This method is used
 823      * in JarFile, via SharedSecrets, as an optimization when looking up
 824      * manifest and signature file entries. Returns null if no entries
 825      * were found.
 826      */
 827     private String[] getMetaInfEntryNames() {
 828         synchronized (this) {
 829             ensureOpen();
 830             if (zsrc.metanames.size() == 0) {
 831                 return null;
 832             }
 833             String[] names = new String[zsrc.metanames.size()];
 834             byte[] cen = zsrc.cen;
 835             for (int i = 0; i < names.length; i++) {
 836                 int pos = zsrc.metanames.get(i);
 837                 names[i] = new String(cen, pos + CENHDR,  CENNAM(cen, pos),
 838                                       StandardCharsets.UTF_8);
 839             }
 840             return names;
 841         }
 842     }
 843 
 844     private static class Source {
 845         private final Key key;               // the key in files
 846         private int refs = 1;
 847 
 848         private RandomAccessFile zfile;      // zfile of the underlying zip file
 849         private byte[] cen;                  // CEN & ENDHDR
 850         private long locpos;                 // position of first LOC header (usually 0)
 851         private byte[] comment;              // zip file comment
 852                                              // list of meta entries in META-INF dir
 853         private ArrayList<Integer> metanames = new ArrayList<>();
 854         private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true)
 855 
 856         // A Hashmap for all entries.
 857         //
 858         // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR,
 859         // We might have a lot of these in a typical system. In order to save space we don't
 860         // keep the name in memory, but merely remember a 32 bit {@code hash} value of the
 861         // entry name and its offset {@code pos} in the central directory hdeader.
 862         //
 863         // private static class Entry {
 864         //     int hash;       // 32 bit hashcode on name
 865         //     int next;       // hash chain: index into entries
 866         //     int pos;        // Offset of central directory file header
 867         // }
 868         // private Entry[] entries;             // array of hashed cen entry
 869         //
 870         // To reduce the total size of entries further, we use a int[] here to store 3 "int"
 871         // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be
 872         // referred by their index of their positions in the {@code entries}.
 873         //
 874         private int[] entries;                  // array of hashed cen entry
 875         private int addEntry(int index, int hash, int next, int pos) {
 876             entries[index++] = hash;
 877             entries[index++] = next;
 878             entries[index++] = pos;
 879             return index;
 880         }
 881         private int getEntryHash(int index) { return entries[index]; }
 882         private int getEntryNext(int index) { return entries[index + 1]; }
 883         private int getEntryPos(int index)  { return entries[index + 2]; }
 884         private static final int ZIP_ENDCHAIN  = -1;
 885         private int total;                   // total number of entries
 886         private int[] table;                 // Hash chain heads: indexes into entries
 887         private int tablelen;                // number of hash heads
 888 
 889         private static class Key {
 890             BasicFileAttributes attrs;
 891             File file;
 892 
 893             public Key(File file, BasicFileAttributes attrs) {
 894                 this.attrs = attrs;
 895                 this.file = file;
 896             }
 897 
 898             public int hashCode() {
 899                 long t = attrs.lastModifiedTime().toMillis();
 900                 return ((int)(t ^ (t >>> 32))) + file.hashCode();
 901             }
 902 
 903             public boolean equals(Object obj) {
 904                 if (obj instanceof Key) {
 905                     Key key = (Key)obj;
 906                     if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) {
 907                         return false;
 908                     }
 909                     Object fk = attrs.fileKey();
 910                     if (fk != null) {
 911                         return  fk.equals(key.attrs.fileKey());
 912                     } else {
 913                         return file.equals(key.file);
 914                     }
 915                 }
 916                 return false;
 917             }
 918         }
 919         private static final HashMap<Key, Source> files = new HashMap<>();
 920 
 921 
 922         public static Source get(File file, boolean toDelete) throws IOException {
 923             Key key = new Key(file,
 924                               Files.readAttributes(file.toPath(), BasicFileAttributes.class));
 925             Source src = null;
 926             synchronized (files) {
 927                 src = files.get(key);
 928                 if (src != null) {
 929                     src.refs++;
 930                     return src;
 931                 }
 932             }
 933             src = new Source(key, toDelete);
 934 
 935             synchronized (files) {
 936                 if (files.containsKey(key)) {    // someone else put in first
 937                     src.close();                 // close the newly created one
 938                     src = files.get(key);
 939                     src.refs++;
 940                     return src;
 941                 }
 942                 files.put(key, src);
 943                 return src;
 944             }
 945         }
 946 
 947         private static void close(Source src) throws IOException {
 948             synchronized (files) {
 949                 if (--src.refs == 0) {
 950                     files.remove(src.key);
 951                     src.close();
 952                 }
 953             }
 954         }
 955 
 956         private Source(Key key, boolean toDelete) throws IOException {
 957             this.key = key;
 958             this.zfile = new RandomAccessFile(key.file, "r");
 959             if (toDelete) {
 960                 key.file.delete();
 961             }
 962             try {
 963                 initCEN(-1);
 964                 byte[] buf = new byte[4];
 965                 readFullyAt(buf, 0, 4, 0);
 966                 this.startsWithLoc = (LOCSIG(buf) == LOCSIG);
 967             } catch (IOException x) {
 968                 try {
 969                     this.zfile.close();
 970                 } catch (IOException xx) {}
 971                 throw x;
 972             }
 973         }
 974 
 975         private void close() throws IOException {
 976             zfile.close();
 977             zfile = null;
 978             cen = null;
 979             entries = null;
 980             table = null;
 981             metanames = null;
 982         }
 983 
 984         private static final int BUF_SIZE = 8192;
 985         private final int readFullyAt(byte[] buf, int off, int len, long pos)
 986             throws IOException
 987         {
 988             synchronized(zfile) {
 989                 zfile.seek(pos);
 990                 int N = len;
 991                 while (N > 0) {
 992                     int n = Math.min(BUF_SIZE, N);
 993                     zfile.readFully(buf, off, n);
 994                     off += n;
 995                     N -= n;
 996                 }
 997                 return len;
 998             }
 999         }
1000 
1001         private final int readAt(byte[] buf, int off, int len, long pos)
1002             throws IOException
1003         {
1004             synchronized(zfile) {
1005                 zfile.seek(pos);
1006                 return zfile.read(buf, off, len);
1007             }
1008         }
1009 
1010         private static final int hashN(byte[] a, int off, int len) {
1011             int h = 1;
1012             while (len-- > 0) {
1013                 h = 31 * h + a[off++];
1014             }
1015             return h;
1016         }
1017 
1018         private static final int hash_append(int hash, byte b) {
1019             return hash * 31 + b;
1020         }
1021 
1022         private static class End {
1023             int  centot;     // 4 bytes
1024             long cenlen;     // 4 bytes
1025             long cenoff;     // 4 bytes
1026             long endpos;     // 4 bytes
1027         }
1028 
1029         /*
1030          * Searches for end of central directory (END) header. The contents of
1031          * the END header will be read and placed in endbuf. Returns the file
1032          * position of the END header, otherwise returns -1 if the END header
1033          * was not found or an error occurred.
1034          */
1035         private End findEND() throws IOException {
1036             long ziplen = zfile.length();
1037             if (ziplen <= 0)
1038                 zerror("zip file is empty");
1039             End end = new End();
1040             byte[] buf = new byte[READBLOCKSZ];
1041             long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0;
1042             long minPos = minHDR - (buf.length - ENDHDR);
1043             for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) {
1044                 int off = 0;
1045                 if (pos < 0) {
1046                     // Pretend there are some NUL bytes before start of file
1047                     off = (int)-pos;
1048                     Arrays.fill(buf, 0, off, (byte)0);
1049                 }
1050                 int len = buf.length - off;
1051                 if (readFullyAt(buf, off, len, pos + off) != len ) {
1052                     zerror("zip END header not found");
1053                 }
1054                 // Now scan the block backwards for END header signature
1055                 for (int i = buf.length - ENDHDR; i >= 0; i--) {
1056                     if (buf[i+0] == (byte)'P'    &&
1057                         buf[i+1] == (byte)'K'    &&
1058                         buf[i+2] == (byte)'\005' &&
1059                         buf[i+3] == (byte)'\006') {
1060                         // Found ENDSIG header
1061                         byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR);
1062                         end.centot = ENDTOT(endbuf);
1063                         end.cenlen = ENDSIZ(endbuf);
1064                         end.cenoff = ENDOFF(endbuf);
1065                         end.endpos = pos + i;
1066                         int comlen = ENDCOM(endbuf);
1067                         if (end.endpos + ENDHDR + comlen != ziplen) {
1068                             // ENDSIG matched, however the size of file comment in it does
1069                             // not match the real size. One "common" cause for this problem
1070                             // is some "extra" bytes are padded at the end of the zipfile.
1071                             // Let's do some extra verification, we don't care about the
1072                             // performance in this situation.
1073                             byte[] sbuf = new byte[4];
1074                             long cenpos = end.endpos - end.cenlen;
1075                             long locpos = cenpos - end.cenoff;
1076                             if  (cenpos < 0 ||
1077                                  locpos < 0 ||
1078                                  readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 ||
1079                                  GETSIG(sbuf) != CENSIG ||
1080                                  readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 ||
1081                                  GETSIG(sbuf) != LOCSIG) {
1082                                 continue;
1083                             }
1084                         }
1085                         if (comlen > 0) {    // this zip file has comlen
1086                             comment = new byte[comlen];
1087                             if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) {
1088                                 zerror("zip comment read failed");
1089                             }
1090                         }
1091                         if (end.cenlen == ZIP64_MAGICVAL ||
1092                             end.cenoff == ZIP64_MAGICVAL ||
1093                             end.centot == ZIP64_MAGICCOUNT)
1094                         {
1095                             // need to find the zip64 end;
1096                             try {
1097                                 byte[] loc64 = new byte[ZIP64_LOCHDR];
1098                                 if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR)
1099                                     != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) {
1100                                     return end;
1101                                 }
1102                                 long end64pos = ZIP64_LOCOFF(loc64);
1103                                 byte[] end64buf = new byte[ZIP64_ENDHDR];
1104                                 if (readFullyAt(end64buf, 0, end64buf.length, end64pos)
1105                                     != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) {
1106                                     return end;
1107                                 }
1108                                 // end64 found, re-calcualte everything.
1109                                 end.cenlen = ZIP64_ENDSIZ(end64buf);
1110                                 end.cenoff = ZIP64_ENDOFF(end64buf);
1111                                 end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g
1112                                 end.endpos = end64pos;
1113                             } catch (IOException x) {}    // no zip64 loc/end
1114                         }
1115                         return end;
1116                     }
1117                 }
1118             }
1119             zerror("zip END header not found");
1120             return null; //make compiler happy
1121         }
1122 
1123         // Reads zip file central directory.
1124         private void initCEN(int knownTotal) throws IOException {
1125             if (knownTotal == -1) {
1126                 End end = findEND();
1127                 if (end.endpos == 0) {
1128                     locpos = 0;
1129                     total = 0;
1130                     entries  = new int[0];
1131                     cen = null;
1132                     return;         // only END header present
1133                 }
1134                 if (end.cenlen > end.endpos)
1135                     zerror("invalid END header (bad central directory size)");
1136                 long cenpos = end.endpos - end.cenlen;     // position of CEN table
1137                 // Get position of first local file (LOC) header, taking into
1138                 // account that there may be a stub prefixed to the zip file.
1139                 locpos = cenpos - end.cenoff;
1140                 if (locpos < 0) {
1141                     zerror("invalid END header (bad central directory offset)");
1142                 }
1143                 // read in the CEN and END
1144                 cen = new byte[(int)(end.cenlen + ENDHDR)];
1145                 if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) {
1146                     zerror("read CEN tables failed");
1147                 }
1148                 total = end.centot;
1149             } else {
1150                 total = knownTotal;
1151             }
1152             // hash table for entries
1153             entries  = new int[total * 3];
1154             tablelen = ((total/2) | 1); // Odd -> fewer collisions
1155             table    =  new int[tablelen];
1156             Arrays.fill(table, ZIP_ENDCHAIN);
1157             int idx = 0;
1158             int hash = 0;
1159             int next = -1;
1160 
1161             // list for all meta entries
1162             metanames = new ArrayList<>();
1163 
1164             // Iterate through the entries in the central directory
1165             int i = 0;
1166             int hsh = 0;
1167             int pos = 0;
1168             int limit = cen.length - ENDHDR;
1169             while (pos + CENHDR  <= limit) {
1170                 if (i >= total) {
1171                     // This will only happen if the zip file has an incorrect
1172                     // ENDTOT field, which usually means it contains more than
1173                     // 65535 entries.
1174                     initCEN(countCENHeaders(cen, limit));
1175                     return;
1176                 }
1177                 if (CENSIG(cen, pos) != CENSIG)
1178                     zerror("invalid CEN header (bad signature)");
1179                 int method = CENHOW(cen, pos);
1180                 int nlen   = CENNAM(cen, pos);
1181                 int elen   = CENEXT(cen, pos);
1182                 int clen   = CENCOM(cen, pos);
1183                 if ((CENFLG(cen, pos) & 1) != 0)
1184                     zerror("invalid CEN header (encrypted entry)");
1185                 if (method != STORED && method != DEFLATED)
1186                     zerror("invalid CEN header (bad compression method: " + method + ")");
1187                 if (pos + CENHDR + nlen > limit)
1188                     zerror("invalid CEN header (bad header size)");
1189                 // Record the CEN offset and the name hash in our hash cell.
1190                 hash = hashN(cen, pos + CENHDR, nlen);
1191                 hsh = (hash & 0x7fffffff) % tablelen;
1192                 next = table[hsh];
1193                 table[hsh] = idx;
1194                 idx = addEntry(idx, hash, next, pos);
1195                 // Adds name to metanames.
1196                 if (isMetaName(cen, pos + CENHDR, nlen)) {
1197                     metanames.add(pos);
1198                 }
1199                 // skip ext and comment
1200                 pos += (CENHDR + nlen + elen + clen);
1201                 i++;
1202             }
1203             total = i;
1204             if (pos + ENDHDR != cen.length) {
1205                 zerror("invalid CEN header (bad header size)");
1206             }
1207         }
1208 
1209         private static void zerror(String msg) throws ZipException {
1210             throw new ZipException(msg);
1211         }
1212 
1213         /*
1214          * Returns the {@code pos} of the zip cen entry corresponding to the
1215          * specified entry name, or -1 if not found.
1216          */
1217         private int getEntryPos(byte[] name, boolean addSlash) {
1218             if (total == 0) {
1219                 return -1;
1220             }
1221             int hsh = hashN(name, 0, name.length);
1222             int idx = table[(hsh & 0x7fffffff) % tablelen];
1223             /*
1224              * This while loop is an optimization where a double lookup
1225              * for name and name+/ is being performed. The name char
1226              * array has enough room at the end to try again with a
1227              * slash appended if the first table lookup does not succeed.
1228              */
1229             while(true) {
1230                 /*
1231                  * Search down the target hash chain for a entry whose
1232                  * 32 bit hash matches the hashed name.
1233                  */
1234                 while (idx != ZIP_ENDCHAIN) {
1235                     if (getEntryHash(idx) == hsh) {
1236                         // The CEN name must match the specfied one
1237                         int pos = getEntryPos(idx);
1238                         if (name.length == CENNAM(cen, pos)) {
1239                             boolean matched = true;
1240                             int nameoff = pos + CENHDR;
1241                             for (int i = 0; i < name.length; i++) {
1242                                 if (name[i] != cen[nameoff++]) {
1243                                     matched = false;
1244                                     break;
1245                                 }
1246                             }
1247                             if (matched) {
1248                                 return pos;
1249                             }
1250                          }
1251                     }
1252                     idx = getEntryNext(idx);
1253                 }
1254                 /* If not addSlash, or slash is already there, we are done */
1255                 if (!addSlash  || name.length == 0 || name[name.length - 1] == '/') {
1256                      return -1;
1257                 }
1258                 /* Add slash and try once more */
1259                 name = Arrays.copyOf(name, name.length + 1);
1260                 name[name.length - 1] = '/';
1261                 hsh = hash_append(hsh, (byte)'/');
1262                 //idx = table[hsh % tablelen];
1263                 idx = table[(hsh & 0x7fffffff) % tablelen];
1264                 addSlash = false;
1265             }
1266         }
1267 
1268         private static byte[] metainf = new byte[] {
1269             'M', 'E', 'T', 'A', '-', 'I' , 'N', 'F', '/',
1270         };
1271 
1272         /*
1273          * Returns true if the specified entry's name begins with the string
1274          * "META-INF/" irrespective of case.
1275          */
1276         private static boolean isMetaName(byte[] name,  int off, int len) {
1277             if (len < 9 || (name[off] != 'M' && name[off] != 'm')) {  //  sizeof("META-INF/") - 1
1278                 return false;
1279             }
1280             off++;
1281             for (int i = 1; i < metainf.length; i++) {
1282                 byte c = name[off++];
1283                 // Avoid toupper; it's locale-dependent
1284                 if (c >= 'a' && c <= 'z') {
1285                     c += 'A' - 'a';
1286                 }
1287                 if (metainf[i] != c) {
1288                     return false;
1289                 }
1290             }
1291             return true;
1292         }
1293 
1294         /*
1295          * Counts the number of CEN headers in a central directory extending
1296          * from BEG to END.  Might return a bogus answer if the zip file is
1297          * corrupt, but will not crash.
1298          */
1299         static int countCENHeaders(byte[] cen, int end) {
1300             int count = 0;
1301             int pos = 0;
1302             while (pos + CENHDR <= end) {
1303                 count++;
1304                 pos += (CENHDR + CENNAM(cen, pos) + CENEXT(cen, pos) + CENCOM(cen, pos));
1305             }
1306             return count;
1307         }
1308     }
1309 }