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