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