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