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