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