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      */
ZipFile(String name)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      */
ZipFile(File file, int mode)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      */
ZipFile(File file)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      */
ZipFile(File file, int mode, Charset charset)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      */
ZipFile(String name, Charset charset)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      */
ZipFile(File file, Charset charset)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      */
getComment()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      */
getEntry(String name)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 
getEntry(long jzfile, byte[] name, boolean addSlash)346     private static native long getEntry(long jzfile, byte[] name,
347                                         boolean addSlash);
348 
349     // freeEntry releases the C jzentry struct.
freeEntry(long jzfile, long jzentry)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      */
getInputStream(ZipEntry entry)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 
ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, int size)419         ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
420                 int size) {
421             super(zfin, inf, size);
422             this.zfin = zfin;
423         }
424 
close()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.
fill()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 
available()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 
finalize()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      */
getInflater()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      */
releaseInflater(Inflater inf)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      */
getName()504     public String getName() {
505         return name;
506     }
507 
508     private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
509         private int i = 0;
510 
ZipEntryIterator()511         public ZipEntryIterator() {
512             ensureOpen();
513         }
514 
hasMoreElements()515         public boolean hasMoreElements() {
516             return hasNext();
517         }
518 
hasNext()519         public boolean hasNext() {
520             synchronized (ZipFile.this) {
521                 ensureOpen();
522                 return i < total;
523             }
524         }
525 
nextElement()526         public ZipEntry nextElement() {
527             return next();
528         }
529 
next()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      */
entries()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      */
stream()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 
getZipEntry(String name, long jzentry)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 
getNextEntry(long jzfile, int i)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      */
size()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      */
close()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      */
finalize()694     protected void finalize() throws IOException {
695         if (guard != null) {
696             guard.warnIfOpen();
697         }
698 
699         close();
700     }
701 
close(long jzfile)702     private static native void close(long jzfile);
703 
ensureOpen()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 
ensureOpenOrZipException()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 
ZipFileInputStream(long jzentry)731         ZipFileInputStream(long jzentry) {
732             pos = 0;
733             rem = getEntryCSize(jzentry);
734             size = getEntrySize(jzentry);
735             this.jzentry = jzentry;
736         }
737 
read(byte b[], int off, int len)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 
read()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 
skip(long n)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 
available()791         public int available() {
792             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
793         }
794 
size()795         public long size() {
796             return size;
797         }
798 
close()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 
finalize()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      */
startsWithLocHeader()826     public boolean startsWithLocHeader() {
827         return locsig;
828     }
829 
830     /** @hide */
831     // @VisibleForTesting
getFileDescriptor()832     public int getFileDescriptor() {
833         return getFileDescriptor(jzfile);
834     }
835 
getFileDescriptor(long jzfile)836     private static native int getFileDescriptor(long jzfile);
837 
open(String name, int mode, long lastModified, boolean usemmap)838     private static native long open(String name, int mode, long lastModified,
839                                     boolean usemmap) throws IOException;
getTotal(long jzfile)840     private static native int getTotal(long jzfile);
startsWithLOC(long jzfile)841     private static native boolean startsWithLOC(long jzfile);
read(long jzfile, long jzentry, long pos, byte[] b, int off, int len)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
getEntryTime(long jzentry)846     private static native long getEntryTime(long jzentry);
getEntryCrc(long jzentry)847     private static native long getEntryCrc(long jzentry);
getEntryCSize(long jzentry)848     private static native long getEntryCSize(long jzentry);
getEntrySize(long jzentry)849     private static native long getEntrySize(long jzentry);
getEntryMethod(long jzentry)850     private static native int getEntryMethod(long jzentry);
getEntryFlag(long jzentry)851     private static native int getEntryFlag(long jzentry);
getCommentBytes(long jzfile)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;
getEntryBytes(long jzentry, int type)857     private static native byte[] getEntryBytes(long jzentry, int type);
858 
getZipMessage(long jzfile)859     private static native String getZipMessage(long jzfile);
860 }
861