ZipFile.java revision 6975f84c2ed72e1e26d20190b6f318718c849008
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
27package java.util.zip;
28
29import java.io.Closeable;
30import java.io.InputStream;
31import java.io.IOException;
32import java.io.EOFException;
33import java.io.File;
34import java.io.FileNotFoundException;
35import java.nio.charset.Charset;
36import java.nio.charset.StandardCharsets;
37import java.util.ArrayDeque;
38import java.util.Deque;
39import java.util.Enumeration;
40import java.util.HashMap;
41import java.util.Iterator;
42import java.util.Map;
43import java.util.NoSuchElementException;
44import java.util.Spliterator;
45import java.util.Spliterators;
46import java.util.WeakHashMap;
47import java.util.stream.Stream;
48import java.util.stream.StreamSupport;
49
50import dalvik.system.CloseGuard;
51
52import 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 */
63public
64class 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