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