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