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