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.nio.ByteBuffer;
  34 import java.nio.ByteOrder;
  35 import java.nio.IntBuffer;
  36 import java.nio.charset.Charset;
  37 import java.nio.charset.StandardCharsets;
  38 import java.util.ArrayDeque;
  39 import java.util.Deque;
  40 import java.util.Enumeration;
  41 import java.util.HashMap;
  42 import java.util.Iterator;
  43 import java.util.Map;
  44 import java.util.NoSuchElementException;
  45 import java.util.Spliterator;
  46 import java.util.Spliterators;
  47 import java.util.WeakHashMap;
  48 import java.util.stream.Stream;
  49 import java.util.stream.StreamSupport;
  50 
  51 import static java.util.zip.ZipConstants64.*;
  52 
  53 /**
  54  * This class is used to read entries from a zip file.
  55  *
  56  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
  57  * or method in this class will cause a {@link NullPointerException} to be
  58  * thrown.
  59  *
  60  * @author      David Connelly
  61  */
  62 public
  63 class ZipFile implements ZipConstants, Closeable {
  64     private long jzfile;           // address of jzfile data
  65     private IntBuffer table;       // direct buffer mapping jzfile->table
  66     private ByteBuffer entries;    // direct buffer mapping jzfile->entries
  67     private int falsePositiveProbes;
  68     private final String name;     // zip file name
  69     private final int total;       // total number of entries
  70     private final boolean locsig;  // if zip file starts with LOCSIG (usually true)
  71     private volatile boolean closeRequested = false;
  72 
  73     private static final int STORED = ZipEntry.STORED;
  74     private static final int DEFLATED = ZipEntry.DEFLATED;
  75 
  76     /**
  77      * Mode flag to open a zip file for reading.
  78      */
  79     public static final int OPEN_READ = 0x1;
  80 
  81     /**
  82      * Mode flag to open a zip file and mark it for deletion.  The file will be
  83      * deleted some time between the moment that it is opened and the moment
  84      * that it is closed, but its contents will remain accessible via the
  85      * {@code ZipFile} object until either the close method is invoked or the
  86      * virtual machine exits.
  87      */
  88     public static final int OPEN_DELETE = 0x4;
  89 
  90     static {
  91         /* Zip library is loaded from System.initializeSystemClass */
  92         initIDs();
  93     }
  94 
  95     private static native void initIDs();
  96 
  97     private static final boolean usemmap;
  98     private static final boolean useNativeTableProbe;
  99 
 100     static {
 101         // A system prpperty to disable mmap use to avoid vm crash when
 102         // in-use zip file is accidently overwritten by others.
 103         String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping");
 104         usemmap = (prop == null ||
 105                    !(prop.length() == 0 || prop.equalsIgnoreCase("true")));
 106         // A system property to enable mapping of native hash table of entries
 107         // using direct byte buffers to facilitate a probe that speeds-up lookups
 108         // for non-existent entries (important for class loading with large
 109         // class-paths where JAR files are tried in order to find a class/resource)
 110         prop = System.getProperty("sun.zip.useNativeTableProbe");
 111         useNativeTableProbe = prop != null && prop.equalsIgnoreCase("true");
 112     }
 113 
 114     /**
 115      * Opens a zip file for reading.
 116      *
 117      * <p>First, if there is a security manager, its {@code checkRead}
 118      * method is called with the {@code name} argument as its argument
 119      * to ensure the read is allowed.
 120      *
 121      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 122      * decode the entry names and comments.
 123      *
 124      * @param name the name of the zip file
 125      * @throws ZipException if a ZIP format error has occurred
 126      * @throws IOException if an I/O error has occurred
 127      * @throws SecurityException if a security manager exists and its
 128      *         {@code checkRead} method doesn't allow read access to the file.
 129      *
 130      * @see SecurityManager#checkRead(java.lang.String)
 131      */
 132     public ZipFile(String name) throws IOException {
 133         this(new File(name), OPEN_READ);
 134     }
 135 
 136     /**
 137      * Opens a new {@code ZipFile} to read from the specified
 138      * {@code File} object in the specified mode.  The mode argument
 139      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 140      *
 141      * <p>First, if there is a security manager, its {@code checkRead}
 142      * method is called with the {@code name} argument as its argument to
 143      * ensure the read is allowed.
 144      *
 145      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 146      * decode the entry names and comments
 147      *
 148      * @param file the ZIP file to be opened for reading
 149      * @param mode the mode in which the file is to be opened
 150      * @throws ZipException if a ZIP format error has occurred
 151      * @throws IOException if an I/O error has occurred
 152      * @throws SecurityException if a security manager exists and
 153      *         its {@code checkRead} method
 154      *         doesn't allow read access to the file,
 155      *         or its {@code checkDelete} method doesn't allow deleting
 156      *         the file when the {@code OPEN_DELETE} flag is set.
 157      * @throws IllegalArgumentException if the {@code mode} argument is invalid
 158      * @see SecurityManager#checkRead(java.lang.String)
 159      * @since 1.3
 160      */
 161     public ZipFile(File file, int mode) throws IOException {
 162         this(file, mode, StandardCharsets.UTF_8);
 163     }
 164 
 165     /**
 166      * Opens a ZIP file for reading given the specified File object.
 167      *
 168      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 169      * decode the entry names and comments.
 170      *
 171      * @param file the ZIP file to be opened for reading
 172      * @throws ZipException if a ZIP format error has occurred
 173      * @throws IOException if an I/O error has occurred
 174      */
 175     public ZipFile(File file) throws ZipException, IOException {
 176         this(file, OPEN_READ);
 177     }
 178 
 179     private ZipCoder zc;
 180 
 181     /**
 182      * Opens a new {@code ZipFile} to read from the specified
 183      * {@code File} object in the specified mode.  The mode argument
 184      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 185      *
 186      * <p>First, if there is a security manager, its {@code checkRead}
 187      * method is called with the {@code name} argument as its argument to
 188      * ensure the read is allowed.
 189      *
 190      * @param file the ZIP file to be opened for reading
 191      * @param mode the mode in which the file is to be opened
 192      * @param charset
 193      *        the {@linkplain java.nio.charset.Charset charset} to
 194      *        be used to decode the ZIP entry name and comment that are not
 195      *        encoded by using UTF-8 encoding (indicated by entry's general
 196      *        purpose flag).
 197      *
 198      * @throws ZipException if a ZIP format error has occurred
 199      * @throws IOException if an I/O error has occurred
 200      *
 201      * @throws SecurityException
 202      *         if a security manager exists and its {@code checkRead}
 203      *         method doesn't allow read access to the file,or its
 204      *         {@code checkDelete} method doesn't allow deleting the
 205      *         file when the {@code OPEN_DELETE} flag is set
 206      *
 207      * @throws IllegalArgumentException if the {@code mode} argument is invalid
 208      *
 209      * @see SecurityManager#checkRead(java.lang.String)
 210      *
 211      * @since 1.7
 212      */
 213     public ZipFile(File file, int mode, Charset charset) throws IOException
 214     {
 215         if (((mode & OPEN_READ) == 0) ||
 216             ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
 217             throw new IllegalArgumentException("Illegal mode: 0x"+
 218                                                Integer.toHexString(mode));
 219         }
 220         String name = file.getPath();
 221         SecurityManager sm = System.getSecurityManager();
 222         if (sm != null) {
 223             sm.checkRead(name);
 224             if ((mode & OPEN_DELETE) != 0) {
 225                 sm.checkDelete(name);
 226             }
 227         }
 228         if (charset == null)
 229             throw new NullPointerException("charset is null");
 230         this.zc = ZipCoder.get(charset);
 231         long t0 = System.nanoTime();
 232         ByteBuffer[] tableAndEntries = useNativeTableProbe ? new ByteBuffer[2] : null;
 233         jzfile = open(name, mode, file.lastModified(), usemmap, tableAndEntries);
 234         if (tableAndEntries != null) {
 235             table = tableAndEntries[0].order(ByteOrder.nativeOrder()).asIntBuffer();
 236             entries = tableAndEntries[1].order(ByteOrder.nativeOrder());
 237         }
 238         sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
 239         sun.misc.PerfCounter.getZipFileCount().increment();
 240         this.name = name;
 241         this.total = getTotal(jzfile);
 242         this.locsig = startsWithLOC(jzfile);
 243     }
 244 
 245     /**
 246      * Opens a zip file for reading.
 247      *
 248      * <p>First, if there is a security manager, its {@code checkRead}
 249      * method is called with the {@code name} argument as its argument
 250      * to ensure the read is allowed.
 251      *
 252      * @param name the name of the zip file
 253      * @param charset
 254      *        the {@linkplain java.nio.charset.Charset charset} to
 255      *        be used to decode the ZIP entry name and comment that are not
 256      *        encoded by using UTF-8 encoding (indicated by entry's general
 257      *        purpose flag).
 258      *
 259      * @throws ZipException if a ZIP format error has occurred
 260      * @throws IOException if an I/O error has occurred
 261      * @throws SecurityException
 262      *         if a security manager exists and its {@code checkRead}
 263      *         method doesn't allow read access to the file
 264      *
 265      * @see SecurityManager#checkRead(java.lang.String)
 266      *
 267      * @since 1.7
 268      */
 269     public ZipFile(String name, Charset charset) throws IOException
 270     {
 271         this(new File(name), OPEN_READ, charset);
 272     }
 273 
 274     /**
 275      * Opens a ZIP file for reading given the specified File object.
 276      * @param file the ZIP file to be opened for reading
 277      * @param charset
 278      *        The {@linkplain java.nio.charset.Charset charset} to be
 279      *        used to decode the ZIP entry name and comment (ignored if
 280      *        the <a href="package-summary.html#lang_encoding"> language
 281      *        encoding bit</a> of the ZIP entry's general purpose bit
 282      *        flag is set).
 283      *
 284      * @throws ZipException if a ZIP format error has occurred
 285      * @throws IOException if an I/O error has occurred
 286      *
 287      * @since 1.7
 288      */
 289     public ZipFile(File file, Charset charset) throws IOException
 290     {
 291         this(file, OPEN_READ, charset);
 292     }
 293 
 294     /**
 295      * Returns the zip file comment, or null if none.
 296      *
 297      * @return the comment string for the zip file, or null if none
 298      *
 299      * @throws IllegalStateException if the zip file has been closed
 300      *
 301      * Since 1.7
 302      */
 303     public String getComment() {
 304         synchronized (this) {
 305             ensureOpen();
 306             byte[] bcomm = getCommentBytes(jzfile);
 307             if (bcomm == null)
 308                 return null;
 309             return zc.toString(bcomm, bcomm.length);
 310         }
 311     }
 312 
 313     /**
 314      * Returns the zip file entry for the specified name, or null
 315      * if not found.
 316      *
 317      * @param name the name of the entry
 318      * @return the zip file entry, or null if not found
 319      * @throws IllegalStateException if the zip file has been closed
 320      */
 321     public ZipEntry getEntry(String name) {
 322         if (name == null) {
 323             throw new NullPointerException("name");
 324         }
 325         synchronized (this) {
 326             ensureOpen();
 327             byte[] nameBytes = zc.getBytes(name);
 328             int h = hash(nameBytes);
 329             if (mayContainEntry(h) ||
 330                 (name.charAt(name.length()-1) != '/' && mayContainEntry(31 * h + '/'))) {
 331                 long jzentry = getEntry(jzfile, nameBytes, true);
 332                 if (jzentry != 0) {
 333                     ZipEntry ze = getZipEntry(name, jzentry);
 334                     freeEntry(jzfile, jzentry);
 335                     return ze;
 336                 } else {
 337                     falsePositiveProbes++;
 338                 }
 339             }
 340         }
 341         return null;
 342     }
 343 
 344     /**
 345      * Lookup into a hash table maintained in two native arrays (table and entries)
 346      * to probe whether there may be an entry present with specified 32 bit hash.
 347      */
 348     private boolean mayContainEntry(int h) {
 349         // when sun.zip.useNativeTableProbe is not defined,
 350         // any entry hash may be valid...
 351         if (table == null) return true;
 352 
 353         // use unsigned modulus to calculate index into hash table
 354         int i = Integer.remainderUnsigned(h, table.capacity());
 355         // obtain index into entries array which holds a head of the hash table bucket
 356         int head = table.get(i);
 357         // special -1 value marks end of chain
 358         while (head != -1) {
 359             //
 360             // entries array is composed of the following 16-byte entries (zip_util.h):
 361             //
 362             // typedef struct jzcell {
 363             //     unsigned int hash;    /* 32 bit hashcode on name */
 364             //     unsigned int next;    /* hash chain: index into jzfile->entries */
 365             //     jlong cenpos;         /* Offset of central directory file header */
 366             // } jzcell;
 367             //
 368             int hi = head << 4;            // index -> byte offset
 369             if (entries.getInt(hi) == h) { // entries[head].hash
 370                 // got it
 371                 // TODO: return jzcell.cenpos to save native code from re-executing the same lookup
 372                 return true;
 373             }
 374             // advance to next in chain
 375             head = entries.getInt(hi + 4); // entries[head].next
 376         }
 377         // no luck
 378         return false;
 379     }
 380 
 381     /**
 382      * Equivalent to the following from zip_util.c:
 383      * <pre>
 384      *    static unsigned int
 385      *                    hashN(const char *s, int length)
 386      *    {
 387      *        int h = 0;
 388      *        while (length-- > 0)
 389      *            h = 31*h + *s++;
 390      *        return h;
 391      *    }
 392      * </pre>
 393      */
 394     private static int hash(byte[] bytes) {
 395         int h = 0;
 396         for (int i = 0; i < bytes.length; i++) {
 397             h = 31 * h + ((int) bytes[i] & 0xFF);
 398         }
 399         return h;
 400     }
 401 
 402     private static native long getEntry(long jzfile, byte[] name,
 403                                         boolean addSlash);
 404 
 405     // freeEntry releases the C jzentry struct.
 406     private static native void freeEntry(long jzfile, long jzentry);
 407 
 408     // the outstanding inputstreams that need to be closed,
 409     // mapped to the inflater objects they use.
 410     private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
 411 
 412     /**
 413      * Returns an input stream for reading the contents of the specified
 414      * zip file entry.
 415      *
 416      * <p> Closing this ZIP file will, in turn, close all input
 417      * streams that have been returned by invocations of this method.
 418      *
 419      * @param entry the zip file entry
 420      * @return the input stream for reading the contents of the specified
 421      * zip file entry.
 422      * @throws ZipException if a ZIP format error has occurred
 423      * @throws IOException if an I/O error has occurred
 424      * @throws IllegalStateException if the zip file has been closed
 425      */
 426     public InputStream getInputStream(ZipEntry entry) throws IOException {
 427         if (entry == null) {
 428             throw new NullPointerException("entry");
 429         }
 430         long jzentry = 0;
 431         ZipFileInputStream in = null;
 432         synchronized (this) {
 433             ensureOpen();
 434             if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
 435                 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
 436             } else {
 437                 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
 438             }
 439             if (jzentry == 0) {
 440                 return null;
 441             }
 442             in = new ZipFileInputStream(jzentry);
 443 
 444             switch (getEntryMethod(jzentry)) {
 445             case STORED:
 446                 synchronized (streams) {
 447                     streams.put(in, null);
 448                 }
 449                 return in;
 450             case DEFLATED:
 451                 // MORE: Compute good size for inflater stream:
 452                 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack
 453                 if (size > 65536) size = 8192;
 454                 if (size <= 0) size = 4096;
 455                 Inflater inf = getInflater();
 456                 InputStream is =
 457                     new ZipFileInflaterInputStream(in, inf, (int)size);
 458                 synchronized (streams) {
 459                     streams.put(is, inf);
 460                 }
 461                 return is;
 462             default:
 463                 throw new ZipException("invalid compression method");
 464             }
 465         }
 466     }
 467 
 468     private class ZipFileInflaterInputStream extends InflaterInputStream {
 469         private volatile boolean closeRequested = false;
 470         private boolean eof = false;
 471         private final ZipFileInputStream zfin;
 472 
 473         ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
 474                 int size) {
 475             super(zfin, inf, size);
 476             this.zfin = zfin;
 477         }
 478 
 479         public void close() throws IOException {
 480             if (closeRequested)
 481                 return;
 482             closeRequested = true;
 483 
 484             super.close();
 485             Inflater inf;
 486             synchronized (streams) {
 487                 inf = streams.remove(this);
 488             }
 489             if (inf != null) {
 490                 releaseInflater(inf);
 491             }
 492         }
 493 
 494         // Override fill() method to provide an extra "dummy" byte
 495         // at the end of the input stream. This is required when
 496         // using the "nowrap" Inflater option.
 497         protected void fill() throws IOException {
 498             if (eof) {
 499                 throw new EOFException("Unexpected end of ZLIB input stream");
 500             }
 501             len = in.read(buf, 0, buf.length);
 502             if (len == -1) {
 503                 buf[0] = 0;
 504                 len = 1;
 505                 eof = true;
 506             }
 507             inf.setInput(buf, 0, len);
 508         }
 509 
 510         public int available() throws IOException {
 511             if (closeRequested)
 512                 return 0;
 513             long avail = zfin.size() - inf.getBytesWritten();
 514             return (avail > (long) Integer.MAX_VALUE ?
 515                     Integer.MAX_VALUE : (int) avail);
 516         }
 517 
 518         protected void finalize() throws Throwable {
 519             close();
 520         }
 521     }
 522 
 523     /*
 524      * Gets an inflater from the list of available inflaters or allocates
 525      * a new one.
 526      */
 527     private Inflater getInflater() {
 528         Inflater inf;
 529         synchronized (inflaterCache) {
 530             while (null != (inf = inflaterCache.poll())) {
 531                 if (false == inf.ended()) {
 532                     return inf;
 533                 }
 534             }
 535         }
 536         return new Inflater(true);
 537     }
 538 
 539     /*
 540      * Releases the specified inflater to the list of available inflaters.
 541      */
 542     private void releaseInflater(Inflater inf) {
 543         if (false == inf.ended()) {
 544             inf.reset();
 545             synchronized (inflaterCache) {
 546                 inflaterCache.add(inf);
 547             }
 548         }
 549     }
 550 
 551     // List of available Inflater objects for decompression
 552     private Deque<Inflater> inflaterCache = new ArrayDeque<>();
 553 
 554     /**
 555      * Returns the path name of the ZIP file.
 556      * @return the path name of the ZIP file
 557      */
 558     public String getName() {
 559         return name;
 560     }
 561 
 562     private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
 563         private int i = 0;
 564 
 565         public ZipEntryIterator() {
 566             ensureOpen();
 567         }
 568 
 569         public boolean hasMoreElements() {
 570             return hasNext();
 571         }
 572 
 573         public boolean hasNext() {
 574             synchronized (ZipFile.this) {
 575                 ensureOpen();
 576                 return i < total;
 577             }
 578         }
 579 
 580         public ZipEntry nextElement() {
 581             return next();
 582         }
 583 
 584         public ZipEntry next() {
 585             synchronized (ZipFile.this) {
 586                 ensureOpen();
 587                 if (i >= total) {
 588                     throw new NoSuchElementException();
 589                 }
 590                 long jzentry = getNextEntry(jzfile, i++);
 591                 if (jzentry == 0) {
 592                     String message;
 593                     if (closeRequested) {
 594                         message = "ZipFile concurrently closed";
 595                     } else {
 596                         message = getZipMessage(ZipFile.this.jzfile);
 597                     }
 598                     throw new ZipError("jzentry == 0" +
 599                                        ",\n jzfile = " + ZipFile.this.jzfile +
 600                                        ",\n total = " + ZipFile.this.total +
 601                                        ",\n name = " + ZipFile.this.name +
 602                                        ",\n i = " + i +
 603                                        ",\n message = " + message
 604                         );
 605                 }
 606                 ZipEntry ze = getZipEntry(null, jzentry);
 607                 freeEntry(jzfile, jzentry);
 608                 return ze;
 609             }
 610         }
 611 
 612         public Iterator<ZipEntry> asIterator() {
 613             return this;
 614         }
 615     }
 616 
 617     /**
 618      * Returns an enumeration of the ZIP file entries.
 619      * @return an enumeration of the ZIP file entries
 620      * @throws IllegalStateException if the zip file has been closed
 621      */
 622     public Enumeration<? extends ZipEntry> entries() {
 623         return new ZipEntryIterator();
 624     }
 625 
 626     /**
 627      * Returns an ordered {@code Stream} over the ZIP file entries.
 628      * Entries appear in the {@code Stream} in the order they appear in
 629      * the central directory of the ZIP file.
 630      *
 631      * @return an ordered {@code Stream} of entries in this ZIP file
 632      * @throws IllegalStateException if the zip file has been closed
 633      * @since 1.8
 634      */
 635     public Stream<? extends ZipEntry> stream() {
 636         return StreamSupport.stream(Spliterators.spliterator(
 637                 new ZipEntryIterator(), size(),
 638                 Spliterator.ORDERED | Spliterator.DISTINCT |
 639                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
 640     }
 641 
 642     private ZipEntry getZipEntry(String name, long jzentry) {
 643         ZipEntry e = new ZipEntry();
 644         e.flag = getEntryFlag(jzentry);  // get the flag first
 645         if (name != null) {
 646             e.name = name;
 647         } else {
 648             byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME);
 649             if (!zc.isUTF8() && (e.flag & EFS) != 0) {
 650                 e.name = zc.toStringUTF8(bname, bname.length);
 651             } else {
 652                 e.name = zc.toString(bname, bname.length);
 653             }
 654         }
 655         e.xdostime = getEntryTime(jzentry);
 656         e.crc = getEntryCrc(jzentry);
 657         e.size = getEntrySize(jzentry);
 658         e.csize = getEntryCSize(jzentry);
 659         e.method = getEntryMethod(jzentry);
 660         e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false);
 661         byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT);
 662         if (bcomm == null) {
 663             e.comment = null;
 664         } else {
 665             if (!zc.isUTF8() && (e.flag & EFS) != 0) {
 666                 e.comment = zc.toStringUTF8(bcomm, bcomm.length);
 667             } else {
 668                 e.comment = zc.toString(bcomm, bcomm.length);
 669             }
 670         }
 671         return e;
 672     }
 673 
 674     private static native long getNextEntry(long jzfile, int i);
 675 
 676     /**
 677      * Returns the number of entries in the ZIP file.
 678      * @return the number of entries in the ZIP file
 679      * @throws IllegalStateException if the zip file has been closed
 680      */
 681     public int size() {
 682         ensureOpen();
 683         return total;
 684     }
 685 
 686     /**
 687      * Closes the ZIP file.
 688      * <p> Closing this ZIP file will close all of the input streams
 689      * previously returned by invocations of the {@link #getInputStream
 690      * getInputStream} method.
 691      *
 692      * @throws IOException if an I/O error has occurred
 693      */
 694     public void close() throws IOException {
 695         if (closeRequested)
 696             return;
 697         closeRequested = true;
 698 
 699         synchronized (this) {
 700             // Close streams, release their inflaters
 701             synchronized (streams) {
 702                 if (false == streams.isEmpty()) {
 703                     Map<InputStream, Inflater> copy = new HashMap<>(streams);
 704                     streams.clear();
 705                     for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) {
 706                         e.getKey().close();
 707                         Inflater inf = e.getValue();
 708                         if (inf != null) {
 709                             inf.end();
 710                         }
 711                     }
 712                 }
 713             }
 714 
 715             // Release cached inflaters
 716             Inflater inf;
 717             synchronized (inflaterCache) {
 718                 while (null != (inf = inflaterCache.poll())) {
 719                     inf.end();
 720                 }
 721             }
 722 
 723             if (jzfile != 0) {
 724                 // Close the zip file
 725                 long zf = this.jzfile;
 726                 jzfile = 0;
 727                 table = null;
 728                 entries = null;
 729                 close(zf);
 730             }
 731         }
 732     }
 733 
 734     /**
 735      * Ensures that the system resources held by this ZipFile object are
 736      * released when there are no more references to it.
 737      *
 738      * <p>
 739      * Since the time when GC would invoke this method is undetermined,
 740      * it is strongly recommended that applications invoke the {@code close}
 741      * method as soon they have finished accessing this {@code ZipFile}.
 742      * This will prevent holding up system resources for an undetermined
 743      * length of time.
 744      *
 745      * @throws IOException if an I/O error has occurred
 746      * @see    java.util.zip.ZipFile#close()
 747      */
 748     protected void finalize() throws IOException {
 749         close();
 750     }
 751 
 752     private static native void close(long jzfile);
 753 
 754     private void ensureOpen() {
 755         if (closeRequested) {
 756             throw new IllegalStateException("zip file closed");
 757         }
 758 
 759         if (jzfile == 0) {
 760             throw new IllegalStateException("The object is not initialized.");
 761         }
 762     }
 763 
 764     private void ensureOpenOrZipException() throws IOException {
 765         if (closeRequested) {
 766             throw new ZipException("ZipFile closed");
 767         }
 768     }
 769 
 770     /*
 771      * Inner class implementing the input stream used to read a
 772      * (possibly compressed) zip file entry.
 773      */
 774    private class ZipFileInputStream extends InputStream {
 775         private volatile boolean closeRequested = false;
 776         protected long jzentry; // address of jzentry data
 777         private   long pos;     // current position within entry data
 778         protected long rem;     // number of remaining bytes within entry
 779         protected long size;    // uncompressed size of this entry
 780 
 781         ZipFileInputStream(long jzentry) {
 782             pos = 0;
 783             rem = getEntryCSize(jzentry);
 784             size = getEntrySize(jzentry);
 785             this.jzentry = jzentry;
 786         }
 787 
 788         public int read(byte b[], int off, int len) throws IOException {
 789             synchronized (ZipFile.this) {
 790                 long rem = this.rem;
 791                 long pos = this.pos;
 792                 if (rem == 0) {
 793                     return -1;
 794                 }
 795                 if (len <= 0) {
 796                     return 0;
 797                 }
 798                 if (len > rem) {
 799                     len = (int) rem;
 800                 }
 801 
 802                 ensureOpenOrZipException();
 803                 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
 804                                    off, len);
 805                 if (len > 0) {
 806                     this.pos = (pos + len);
 807                     this.rem = (rem - len);
 808                 }
 809             }
 810             if (rem == 0) {
 811                 close();
 812             }
 813             return len;
 814         }
 815 
 816         public int read() throws IOException {
 817             byte[] b = new byte[1];
 818             if (read(b, 0, 1) == 1) {
 819                 return b[0] & 0xff;
 820             } else {
 821                 return -1;
 822             }
 823         }
 824 
 825         public long skip(long n) {
 826             if (n > rem)
 827                 n = rem;
 828             pos += n;
 829             rem -= n;
 830             if (rem == 0) {
 831                 close();
 832             }
 833             return n;
 834         }
 835 
 836         public int available() {
 837             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
 838         }
 839 
 840         public long size() {
 841             return size;
 842         }
 843 
 844         public void close() {
 845             if (closeRequested)
 846                 return;
 847             closeRequested = true;
 848 
 849             rem = 0;
 850             synchronized (ZipFile.this) {
 851                 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
 852                     freeEntry(ZipFile.this.jzfile, jzentry);
 853                     jzentry = 0;
 854                 }
 855             }
 856             synchronized (streams) {
 857                 streams.remove(this);
 858             }
 859         }
 860 
 861         protected void finalize() {
 862             close();
 863         }
 864     }
 865 
 866     static {
 867         sun.misc.SharedSecrets.setJavaUtilZipFileAccess(
 868             new sun.misc.JavaUtilZipFileAccess() {
 869                 public boolean startsWithLocHeader(ZipFile zip) {
 870                     return zip.startsWithLocHeader();
 871                 }
 872              }
 873         );
 874     }
 875 
 876     /**
 877      * Returns {@code true} if, and only if, the zip file begins with {@code
 878      * LOCSIG}.
 879      */
 880     private boolean startsWithLocHeader() {
 881         return locsig;
 882     }
 883 
 884     private static native long open(String name, int mode, long lastModified,
 885                                     boolean usemmap,
 886                                     ByteBuffer[] tableAndEntries) throws IOException;
 887     private static native int getTotal(long jzfile);
 888     private static native boolean startsWithLOC(long jzfile);
 889     private static native int read(long jzfile, long jzentry,
 890                                    long pos, byte[] b, int off, int len);
 891 
 892     // access to the native zentry object
 893     private static native long getEntryTime(long jzentry);
 894     private static native long getEntryCrc(long jzentry);
 895     private static native long getEntryCSize(long jzentry);
 896     private static native long getEntrySize(long jzentry);
 897     private static native int getEntryMethod(long jzentry);
 898     private static native int getEntryFlag(long jzentry);
 899     private static native byte[] getCommentBytes(long jzfile);
 900 
 901     private static final int JZENTRY_NAME = 0;
 902     private static final int JZENTRY_EXTRA = 1;
 903     private static final int JZENTRY_COMMENT = 2;
 904     private static native byte[] getEntryBytes(long jzentry, int type);
 905 
 906     private static native String getZipMessage(long jzfile);
 907 }