• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1995, 2021, 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.io.RandomAccessFile;
36 import java.io.UncheckedIOException;
37 import java.lang.ref.Cleaner.Cleanable;
38 import java.nio.DirectByteBuffer;
39 import java.nio.ByteOrder;
40 import java.nio.channels.FileChannel;
41 import java.nio.channels.FileChannel.MapMode;
42 import java.nio.charset.CharacterCodingException;
43 import java.nio.charset.Charset;
44 import java.nio.charset.StandardCharsets;
45 import java.nio.file.InvalidPathException;
46 import java.nio.file.attribute.BasicFileAttributes;
47 import java.nio.file.Files;
48 import java.util.ArrayDeque;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.Deque;
53 import java.util.Enumeration;
54 import java.util.HashMap;
55 import java.util.HashSet;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Objects;
60 import java.util.NoSuchElementException;
61 import java.util.Set;
62 import java.util.Spliterator;
63 import java.util.Spliterators;
64 import java.util.TreeSet;
65 import java.util.WeakHashMap;
66 import java.util.function.Consumer;
67 import java.util.function.IntFunction;
68 import java.util.jar.JarEntry;
69 import java.util.jar.JarFile;
70 import java.util.stream.Stream;
71 import java.util.stream.StreamSupport;
72 import jdk.internal.access.SharedSecrets;
73 import jdk.internal.misc.VM;
74 import jdk.internal.ref.CleanerFactory;
75 import jdk.internal.vm.annotation.Stable;
76 import sun.misc.Cleaner;
77 import sun.security.util.SignatureFileVerifier;
78 
79 import dalvik.system.CloseGuard;
80 import dalvik.system.ZipPathValidator;
81 
82 import static java.util.zip.ZipConstants64.*;
83 import static java.util.zip.ZipUtils.*;
84 
85 /**
86  * This class is used to read entries from a zip file.
87  *
88  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
89  * or method in this class will cause a {@link NullPointerException} to be
90  * thrown.
91  *
92  * @apiNote
93  * To release resources used by this {@code ZipFile}, the {@link #close()} method
94  * should be called explicitly or by try-with-resources. Subclasses are responsible
95  * for the cleanup of resources acquired by the subclass. Subclasses that override
96  * {@link #finalize()} in order to perform cleanup should be modified to use alternative
97  * cleanup mechanisms such as {@link java.lang.ref.Cleaner} and remove the overriding
98  * {@code finalize} method.
99  *
100  * @author      David Connelly
101  * @since 1.1
102  */
103 public class ZipFile implements ZipConstants, Closeable {
104 
105     private final String name;     // zip file name
106     private volatile boolean closeRequested;
107 
108     // The "resource" used by this zip file that needs to be
109     // cleaned after use.
110     // a) the input streams that need to be closed
111     // b) the list of cached Inflater objects
112     // c) the "native" source of this zip file.
113     private final @Stable CleanableResource res;
114 
115     private static final int STORED = ZipEntry.STORED;
116     private static final int DEFLATED = ZipEntry.DEFLATED;
117 
118     /**
119      * Mode flag to open a zip file for reading.
120      */
121     public static final int OPEN_READ = 0x1;
122 
123     /**
124      * Mode flag to open a zip file and mark it for deletion.  The file will be
125      * deleted some time between the moment that it is opened and the moment
126      * that it is closed, but its contents will remain accessible via the
127      * {@code ZipFile} object until either the close method is invoked or the
128      * virtual machine exits.
129      */
130     public static final int OPEN_DELETE = 0x4;
131 
132     // Android-changed: Additional ZipException throw scenario with ZipPathValidator.
133     /**
134      * Opens a zip file for reading.
135      *
136      * <p>First, if there is a security manager, its {@code checkRead}
137      * method is called with the {@code name} argument as its argument
138      * to ensure the read is allowed.
139      *
140      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
141      * decode the entry names and comments.
142      *
143      * <p>If the app targets Android U or above, zip file entry names containing
144      * ".." or starting with "/" passed here will throw a {@link ZipException}.
145      * For more details, see {@link dalvik.system.ZipPathValidator}.
146      *
147      * @param name the name of the zip file
148      * @throws ZipException if (1) a ZIP format error has occurred or
149      *         (2) <code>targetSdkVersion >= BUILD.VERSION_CODES.UPSIDE_DOWN_CAKE</code>
150      *         and (the <code>name</code> argument contains ".." or starts with "/").
151      * @throws IOException if an I/O error has occurred
152      * @throws SecurityException if a security manager exists and its
153      *         {@code checkRead} 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} to read from the specified
163      * {@code File} object in the specified mode.  The mode argument
164      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
165      *
166      * <p>First, if there is a security manager, its {@code checkRead}
167      * method is called with the {@code name} 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} method
179      *         doesn't allow read access to the file,
180      *         or its {@code checkDelete} method doesn't allow deleting
181      *         the file when the {@code OPEN_DELETE} flag is set.
182      * @throws IllegalArgumentException if the {@code mode} 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         // Android-changed: Use StandardCharsets.UTF_8.
188         // this(file, mode, UTF_8.INSTANCE);
189         this(file, mode, StandardCharsets.UTF_8);
190     }
191 
192     /**
193      * Opens a ZIP file for reading given the specified File object.
194      *
195      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
196      * decode the entry names and comments.
197      *
198      * @param file the ZIP file to be opened for reading
199      * @throws ZipException if a ZIP format error has occurred
200      * @throws IOException if an I/O error has occurred
201      */
ZipFile(File file)202     public ZipFile(File file) throws ZipException, IOException {
203         this(file, OPEN_READ);
204     }
205 
206     // Android-changed: Use of the hidden constructor with a new argument for zip path validation.
207     /**
208      * Opens a new {@code ZipFile} to read from the specified
209      * {@code File} object in the specified mode.  The mode argument
210      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
211      *
212      * <p>First, if there is a security manager, its {@code checkRead}
213      * method is called with the {@code name} argument as its argument to
214      * ensure the read is allowed.
215      *
216      * @param file the ZIP file to be opened for reading
217      * @param mode the mode in which the file is to be opened
218      * @param charset
219      *        the {@linkplain java.nio.charset.Charset charset} to
220      *        be used to decode the ZIP entry name and comment that are not
221      *        encoded by using UTF-8 encoding (indicated by entry's general
222      *        purpose flag).
223      *
224      * @throws ZipException if a ZIP format error has occurred
225      * @throws IOException if an I/O error has occurred
226      *
227      * @throws SecurityException
228      *         if a security manager exists and its {@code checkRead}
229      *         method doesn't allow read access to the file,or its
230      *         {@code checkDelete} method doesn't allow deleting the
231      *         file when the {@code OPEN_DELETE} flag is set
232      *
233      * @throws IllegalArgumentException if the {@code mode} argument is invalid
234      *
235      * @see SecurityManager#checkRead(java.lang.String)
236      *
237      * @since 1.7
238      */
ZipFile(File file, int mode, Charset charset)239     public ZipFile(File file, int mode, Charset charset) throws IOException
240     {
241         this(file, mode, charset, /* enableZipPathValidator */ true);
242     }
243 
244     // Android-added: New hidden constructor with an argument for zip path validation.
245     /** @hide */
ZipFile(File file, int mode, boolean enableZipPathValidator)246     public ZipFile(File file, int mode, boolean enableZipPathValidator) throws IOException {
247         this(file, mode, StandardCharsets.UTF_8, enableZipPathValidator);
248     }
249 
250     // Android-changed: Change existing constructor ZipFile(File file, int mode, Charset charset)
251     // to have a new argument enableZipPathValidator in order to set the isZipPathValidatorEnabled
252     // variable before calling the native method open().
253     /** @hide */
ZipFile(File file, int mode, Charset charset, boolean enableZipPathValidator)254     public ZipFile(File file, int mode, Charset charset, boolean enableZipPathValidator)
255             throws IOException {
256         if (((mode & OPEN_READ) == 0) ||
257             ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
258             throw new IllegalArgumentException("Illegal mode: 0x"+
259                                                Integer.toHexString(mode));
260         }
261         String name = file.getPath();
262         file = new File(name);
263         // Android-removed: SecurityManager is always null.
264         /*
265         @SuppressWarnings("removal")
266         SecurityManager sm = System.getSecurityManager();
267         if (sm != null) {
268             sm.checkRead(name);
269             if ((mode & OPEN_DELETE) != 0) {
270                 sm.checkDelete(name);
271             }
272         }
273         */
274 
275         Objects.requireNonNull(charset, "charset");
276 
277         this.name = name;
278         // Android-removed: Skip perf counters.
279         // long t0 = System.nanoTime();
280 
281         // Android-changed: pass isZipPathValidatorEnabled flag.
282         // this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode);
283         boolean isZipPathValidatorEnabled = enableZipPathValidator && !ZipPathValidator.isClear();
284         this.res = new CleanableResource(
285                 this, ZipCoder.get(charset), file, mode, isZipPathValidatorEnabled);
286 
287         // Android-removed: Skip perf counters.
288         // PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
289         // PerfCounter.getZipFileCount().increment();
290     }
291 
292     /**
293      * Opens a zip file for reading.
294      *
295      * <p>First, if there is a security manager, its {@code checkRead}
296      * method is called with the {@code name} argument as its argument
297      * to ensure the read is allowed.
298      *
299      * @param name the name of the zip file
300      * @param charset
301      *        the {@linkplain java.nio.charset.Charset charset} to
302      *        be used to decode the ZIP entry name and comment that are not
303      *        encoded by using UTF-8 encoding (indicated by entry's general
304      *        purpose flag).
305      *
306      * @throws ZipException if a ZIP format error has occurred
307      * @throws IOException if an I/O error has occurred
308      * @throws SecurityException
309      *         if a security manager exists and its {@code checkRead}
310      *         method doesn't allow read access to the file
311      *
312      * @see SecurityManager#checkRead(java.lang.String)
313      *
314      * @since 1.7
315      */
ZipFile(String name, Charset charset)316     public ZipFile(String name, Charset charset) throws IOException
317     {
318         this(new File(name), OPEN_READ, charset);
319     }
320 
321     /**
322      * Opens a ZIP file for reading given the specified File object.
323      *
324      * @param file the ZIP file to be opened for reading
325      * @param charset
326      *        The {@linkplain java.nio.charset.Charset charset} to be
327      *        used to decode the ZIP entry name and comment (ignored if
328      *        the <a href="package-summary.html#lang_encoding"> language
329      *        encoding bit</a> of the ZIP entry's general purpose bit
330      *        flag is set).
331      *
332      * @throws ZipException if a ZIP format error has occurred
333      * @throws IOException if an I/O error has occurred
334      *
335      * @since 1.7
336      */
ZipFile(File file, Charset charset)337     public ZipFile(File file, Charset charset) throws IOException
338     {
339         this(file, OPEN_READ, charset);
340     }
341 
342     /**
343      * Returns the zip file comment, or null if none.
344      *
345      * @return the comment string for the zip file, or null if none
346      *
347      * @throws IllegalStateException if the zip file has been closed
348      *
349      * @since 1.7
350      */
getComment()351     public String getComment() {
352         synchronized (this) {
353             ensureOpen();
354             if (res.zsrc.comment == null) {
355                 return null;
356             }
357             return res.zsrc.zc.toString(res.zsrc.comment);
358         }
359     }
360 
361     /**
362      * Returns the zip file entry for the specified name, or null
363      * if not found.
364      *
365      * @param name the name of the entry
366      * @return the zip file entry, or null if not found
367      * @throws IllegalStateException if the zip file has been closed
368      */
getEntry(String name)369     public ZipEntry getEntry(String name) {
370         Objects.requireNonNull(name, "name");
371         ZipEntry entry = null;
372         synchronized (this) {
373             ensureOpen();
374             int pos = res.zsrc.getEntryPos(name, true);
375             if (pos != -1) {
376                 entry = getZipEntry(name, pos);
377             }
378         }
379         return entry;
380     }
381 
382     /**
383      * Returns an input stream for reading the contents of the specified
384      * zip file entry.
385      * <p>
386      * Closing this ZIP file will, in turn, close all input streams that
387      * have been returned by invocations of this method.
388      *
389      * @param entry the zip file entry
390      * @return the input stream for reading the contents of the specified
391      * zip file entry.
392      * @throws ZipException if a ZIP format error has occurred
393      * @throws IOException if an I/O error has occurred
394      * @throws IllegalStateException if the zip file has been closed
395      */
getInputStream(ZipEntry entry)396     public InputStream getInputStream(ZipEntry entry) throws IOException {
397         Objects.requireNonNull(entry, "entry");
398         int pos;
399         ZipFileInputStream in;
400         Source zsrc = res.zsrc;
401         Set<InputStream> istreams = res.istreams;
402         synchronized (this) {
403             ensureOpen();
404             if (Objects.equals(lastEntryName, entry.name)) {
405                 pos = lastEntryPos;
406             } else {
407                 pos = zsrc.getEntryPos(entry.name, false);
408             }
409             if (pos == -1) {
410                 return null;
411             }
412             in = new ZipFileInputStream(zsrc.cen, pos);
413             switch (CENHOW(zsrc.cen, pos)) {
414             case STORED:
415                 synchronized (istreams) {
416                     istreams.add(in);
417                 }
418                 return in;
419             case DEFLATED:
420                 // Inflater likes a bit of slack
421                 // MORE: Compute good size for inflater stream:
422                 long size = CENLEN(zsrc.cen, pos) + 2;
423                 if (size > 65536) {
424                     // Android-changed: Use 64k buffer size, performs
425                     // better than 8k. See http://b/65491407.
426                     // size = 8192;
427                     size = 65536;
428                 }
429                 if (size <= 0) {
430                     size = 4096;
431                 }
432                 InputStream is = new ZipFileInflaterInputStream(in, res, (int)size);
433                 synchronized (istreams) {
434                     istreams.add(is);
435                 }
436                 return is;
437             default:
438                 throw new ZipException("invalid compression method");
439             }
440         }
441     }
442 
443     private static class InflaterCleanupAction implements Runnable {
444         private final Inflater inf;
445         private final CleanableResource res;
446 
InflaterCleanupAction(Inflater inf, CleanableResource res)447         InflaterCleanupAction(Inflater inf, CleanableResource res) {
448             this.inf = inf;
449             this.res = res;
450         }
451 
452         @Override
run()453         public void run() {
454             res.releaseInflater(inf);
455         }
456     }
457 
458     private class ZipFileInflaterInputStream extends InflaterInputStream {
459         private volatile boolean closeRequested;
460         private boolean eof = false;
461         private final Cleanable cleanable;
462 
ZipFileInflaterInputStream(ZipFileInputStream zfin, CleanableResource res, int size)463         ZipFileInflaterInputStream(ZipFileInputStream zfin,
464                                    CleanableResource res, int size) {
465             this(zfin, res, res.getInflater(), size);
466         }
467 
ZipFileInflaterInputStream(ZipFileInputStream zfin, CleanableResource res, Inflater inf, int size)468         private ZipFileInflaterInputStream(ZipFileInputStream zfin,
469                                            CleanableResource res,
470                                            Inflater inf, int size) {
471             // Android-changed: ZipFileInflaterInputStream does not control its inflater's lifetime
472             // and hence it shouldn't be closed when the stream is closed.
473             // super(zfin, inf, size);
474             super(zfin, inf, size, /* ownsInflater */ false);
475             this.cleanable = CleanerFactory.cleaner().register(this,
476                     new InflaterCleanupAction(inf, res));
477         }
478 
close()479         public void close() throws IOException {
480             if (closeRequested)
481                 return;
482             closeRequested = true;
483             super.close();
484             synchronized (res.istreams) {
485                 res.istreams.remove(this);
486             }
487             cleanable.clean();
488         }
489 
490         // Override fill() method to provide an extra "dummy" byte
491         // at the end of the input stream. This is required when
492         // using the "nowrap" Inflater option.
fill()493         protected void fill() throws IOException {
494             if (eof) {
495                 throw new EOFException("Unexpected end of ZLIB input stream");
496             }
497             len = in.read(buf, 0, buf.length);
498             if (len == -1) {
499                 buf[0] = 0;
500                 len = 1;
501                 eof = true;
502             }
503             inf.setInput(buf, 0, len);
504         }
505 
available()506         public int available() throws IOException {
507             if (closeRequested)
508                 return 0;
509             long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten();
510             return (avail > (long) Integer.MAX_VALUE ?
511                     Integer.MAX_VALUE : (int) avail);
512         }
513     }
514 
515     /**
516      * Returns the path name of the ZIP file.
517      * @return the path name of the ZIP file
518      */
getName()519     public String getName() {
520         return name;
521     }
522 
523     private class ZipEntryIterator<T extends ZipEntry>
524             implements Enumeration<T>, Iterator<T> {
525 
526         private int i = 0;
527         private final int entryCount;
528 
ZipEntryIterator(int entryCount)529         public ZipEntryIterator(int entryCount) {
530             this.entryCount = entryCount;
531         }
532 
533         @Override
hasMoreElements()534         public boolean hasMoreElements() {
535             return hasNext();
536         }
537 
538         @Override
hasNext()539         public boolean hasNext() {
540             // Android-changed: check that file is open.
541             // return i < entryCount;
542             synchronized (ZipFile.this) {
543                 ensureOpen();
544                 return i < entryCount;
545             }
546         }
547 
548         @Override
nextElement()549         public T nextElement() {
550             return next();
551         }
552 
553         @Override
554         @SuppressWarnings("unchecked")
next()555         public T next() {
556             synchronized (ZipFile.this) {
557                 ensureOpen();
558                 if (!hasNext()) {
559                     throw new NoSuchElementException();
560                 }
561                 // each "entry" has 3 ints in table entries
562                 return (T)getZipEntry(null, res.zsrc.getEntryPos(i++ * 3));
563             }
564         }
565 
566         @Override
asIterator()567         public Iterator<T> asIterator() {
568             return this;
569         }
570     }
571 
572     /**
573      * Returns an enumeration of the ZIP file entries.
574      * @return an enumeration of the ZIP file entries
575      * @throws IllegalStateException if the zip file has been closed
576      */
entries()577     public Enumeration<? extends ZipEntry> entries() {
578         synchronized (this) {
579             ensureOpen();
580             return new ZipEntryIterator<ZipEntry>(res.zsrc.total);
581         }
582     }
583 
jarEntries()584     private Enumeration<JarEntry> jarEntries() {
585         synchronized (this) {
586             ensureOpen();
587             return new ZipEntryIterator<JarEntry>(res.zsrc.total);
588         }
589     }
590 
591     private class EntrySpliterator<T> extends Spliterators.AbstractSpliterator<T> {
592         private int index;
593         private final int fence;
594         private final IntFunction<T> gen;
595 
EntrySpliterator(int index, int fence, IntFunction<T> gen)596         EntrySpliterator(int index, int fence, IntFunction<T> gen) {
597             super((long)fence,
598                   Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE |
599                   Spliterator.NONNULL);
600             this.index = index;
601             this.fence = fence;
602             this.gen = gen;
603         }
604 
605         @Override
tryAdvance(Consumer<? super T> action)606         public boolean tryAdvance(Consumer<? super T> action) {
607             if (action == null)
608                 throw new NullPointerException();
609             if (index >= 0 && index < fence) {
610                 synchronized (ZipFile.this) {
611                     ensureOpen();
612                     action.accept(gen.apply(res.zsrc.getEntryPos(index++ * 3)));
613                 }
614                 return true;
615             }
616             return false;
617         }
618     }
619 
620     /**
621      * Returns an ordered {@code Stream} over the ZIP file entries.
622      *
623      * Entries appear in the {@code Stream} in the order they appear in
624      * the central directory of the ZIP file.
625      *
626      * @return an ordered {@code Stream} of entries in this ZIP file
627      * @throws IllegalStateException if the zip file has been closed
628      * @since 1.8
629      */
stream()630     public Stream<? extends ZipEntry> stream() {
631         synchronized (this) {
632             ensureOpen();
633             return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total,
634                 pos -> getZipEntry(null, pos)), false);
635        }
636     }
637 
getEntryName(int pos)638     private String getEntryName(int pos) {
639         // Android-changed: don't keep CEN bytes in heap memory after initialization.
640         //byte[] cen = res.zsrc.cen;
641         DirectByteBuffer cen = res.zsrc.cen;
642         int nlen = CENNAM(cen, pos);
643         ZipCoder zc = res.zsrc.zipCoderForPos(pos);
644         return zc.toString(cen, pos + CENHDR, nlen);
645     }
646 
647     /*
648      * Returns an ordered {@code Stream} over the zip file entry names.
649      *
650      * Entry names appear in the {@code Stream} in the order they appear in
651      * the central directory of the ZIP file.
652      *
653      * @return an ordered {@code Stream} of entry names in this zip file
654      * @throws IllegalStateException if the zip file has been closed
655      * @since 10
656      */
entryNameStream()657     private Stream<String> entryNameStream() {
658         synchronized (this) {
659             ensureOpen();
660             return StreamSupport.stream(
661                 new EntrySpliterator<>(0, res.zsrc.total, this::getEntryName), false);
662         }
663     }
664 
665     /*
666      * Returns an ordered {@code Stream} over the zip file entries.
667      *
668      * Entries appear in the {@code Stream} in the order they appear in
669      * the central directory of the jar file.
670      *
671      * @return an ordered {@code Stream} of entries in this zip file
672      * @throws IllegalStateException if the zip file has been closed
673      * @since 10
674      */
jarStream()675     private Stream<JarEntry> jarStream() {
676         synchronized (this) {
677             ensureOpen();
678             return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total,
679                 pos -> (JarEntry)getZipEntry(null, pos)), false);
680         }
681     }
682 
683     private String lastEntryName;
684     private int lastEntryPos;
685 
686     /* Check ensureOpen() before invoking this method */
getZipEntry(String name, int pos)687     private ZipEntry getZipEntry(String name, int pos) {
688         // Android-changed: don't keep CEN bytes in heap memory after initialization.
689         //byte[] cen = res.zsrc.cen;
690         DirectByteBuffer cen = res.zsrc.cen;
691         int nlen = CENNAM(cen, pos);
692         int elen = CENEXT(cen, pos);
693         int clen = CENCOM(cen, pos);
694 
695         ZipCoder zc = res.zsrc.zipCoderForPos(pos);
696         if (name != null) {
697             // only need to check for mismatch of trailing slash
698             if (nlen > 0 &&
699                 !name.isEmpty() &&
700                 zc.hasTrailingSlash(cen, pos + CENHDR + nlen) &&
701                 !name.endsWith("/"))
702             {
703                 name += '/';
704             }
705         } else {
706             // invoked from iterator, use the entry name stored in cen
707             name = zc.toString(cen, pos + CENHDR, nlen);
708         }
709         ZipEntry e;
710         if (this instanceof JarFile) {
711             // Android-changed: access method directly.
712             // e = Source.JUJA.entryFor((JarFile)this, name);
713             e = ((JarFile) this).entryFor(name);
714         } else {
715             e = new ZipEntry(name);
716         }
717         e.flag = CENFLG(cen, pos);
718         e.xdostime = CENTIM(cen, pos);
719         e.crc = CENCRC(cen, pos);
720         e.size = CENLEN(cen, pos);
721         e.csize = CENSIZ(cen, pos);
722         e.method = CENHOW(cen, pos);
723         if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
724             // read all bits in this field, including sym link attributes
725             e.extraAttributes = CENATX_PERMS(cen, pos) & 0xFFFF;
726         }
727 
728         if (elen != 0) {
729             int start = pos + CENHDR + nlen;
730             // BEGIN Android-changed: don't keep CEN bytes in heap memory after initialization.
731             //e.setExtra0(Arrays.copyOfRange(cen, start, start + elen), true, false);
732             byte[] bytes = new byte[elen];
733             cen.get(start, bytes, 0, elen);
734             e.setExtra0(bytes, true, false);
735             // END Android-changed: don't keep CEN bytes in heap memory after initialization.
736         }
737         if (clen != 0) {
738             int start = pos + CENHDR + nlen + elen;
739             e.comment = zc.toString(cen, start, clen);
740         }
741         lastEntryName = e.name;
742         lastEntryPos = pos;
743         return e;
744     }
745 
746     /**
747      * Returns the number of entries in the ZIP file.
748      *
749      * @return the number of entries in the ZIP file
750      * @throws IllegalStateException if the zip file has been closed
751      */
size()752     public int size() {
753         synchronized (this) {
754             ensureOpen();
755             return res.zsrc.total;
756         }
757     }
758 
759     private static class CleanableResource implements Runnable {
760         // The outstanding inputstreams that need to be closed
761         final Set<InputStream> istreams;
762 
763         // List of cached Inflater objects for decompression
764         Deque<Inflater> inflaterCache;
765 
766         final Cleanable cleanable;
767 
768         // Android-added: CloseGuard support.
769         final CloseGuard guard = CloseGuard.get();
770 
771         Source zsrc;
772 
CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode)773         CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException {
774             this(zf, zc, file, mode, false);
775         }
776 
777         // Android-added: added extra enableZipPathValidator argument.
CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode, boolean enableZipPathValidator)778         CleanableResource(ZipFile zf, ZipCoder zc, File file,
779                 int mode, boolean enableZipPathValidator) throws IOException {
780             this.cleanable = CleanerFactory.cleaner().register(zf, this);
781             this.istreams = Collections.newSetFromMap(new WeakHashMap<>());
782             this.inflaterCache = new ArrayDeque<>();
783             this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc, enableZipPathValidator);
784             // Android-added: CloseGuard support.
785             this.guard.open("ZipFile.close");
786         }
787 
clean()788         void clean() {
789             cleanable.clean();
790         }
791 
792         /*
793          * Gets an inflater from the list of available inflaters or allocates
794          * a new one.
795          */
getInflater()796         Inflater getInflater() {
797             Inflater inf;
798             synchronized (inflaterCache) {
799                 if ((inf = inflaterCache.poll()) != null) {
800                     return inf;
801                 }
802             }
803             return new Inflater(true);
804         }
805 
806         /*
807          * Releases the specified inflater to the list of available inflaters.
808          */
releaseInflater(Inflater inf)809         void releaseInflater(Inflater inf) {
810             Deque<Inflater> inflaters = this.inflaterCache;
811             if (inflaters != null) {
812                 synchronized (inflaters) {
813                     // double checked!
814                     if (inflaters == this.inflaterCache) {
815                         inf.reset();
816                         inflaters.add(inf);
817                         return;
818                     }
819                 }
820             }
821             // inflaters cache already closed - just end it.
822             inf.end();
823         }
824 
run()825         public void run() {
826             IOException ioe = null;
827             // Android-added: CloseGuard support.
828             guard.warnIfOpen();
829 
830             // Release cached inflaters and close the cache first
831             Deque<Inflater> inflaters = this.inflaterCache;
832             if (inflaters != null) {
833                 synchronized (inflaters) {
834                     // no need to double-check as only one thread gets a
835                     // chance to execute run() (Cleaner guarantee)...
836                     Inflater inf;
837                     while ((inf = inflaters.poll()) != null) {
838                         inf.end();
839                     }
840                     // close inflaters cache
841                     this.inflaterCache = null;
842                 }
843             }
844 
845             // Close streams, release their inflaters
846             if (istreams != null) {
847                 synchronized (istreams) {
848                     if (!istreams.isEmpty()) {
849                         InputStream[] copy = istreams.toArray(new InputStream[0]);
850                         istreams.clear();
851                         for (InputStream is : copy) {
852                             try {
853                                 is.close();
854                             } catch (IOException e) {
855                                 if (ioe == null) ioe = e;
856                                 else ioe.addSuppressed(e);
857                             }
858                         }
859                     }
860                 }
861             }
862 
863             // Release zip src
864             if (zsrc != null) {
865                 synchronized (zsrc) {
866                     try {
867                         Source.release(zsrc);
868                         zsrc = null;
869                     } catch (IOException e) {
870                         if (ioe == null) ioe = e;
871                         else ioe.addSuppressed(e);
872                     }
873                 }
874             }
875             if (ioe != null) {
876                 throw new UncheckedIOException(ioe);
877             }
878         }
879     }
880 
881     /**
882      * Closes the ZIP file.
883      *
884      * <p> Closing this ZIP file will close all of the input streams
885      * previously returned by invocations of the {@link #getInputStream
886      * getInputStream} method.
887      *
888      * @throws IOException if an I/O error has occurred
889      */
close()890     public void close() throws IOException {
891         if (closeRequested) {
892             return;
893         }
894         closeRequested = true;
895 
896         synchronized (this) {
897             // Close streams, release their inflaters, release cached inflaters
898             // and release zip source
899             try {
900                 // Android-added: CloseGuard support.
901                 res.guard.close();
902                 res.clean();
903             } catch (UncheckedIOException ioe) {
904                 throw ioe.getCause();
905             }
906         }
907     }
908 
ensureOpen()909     private void ensureOpen() {
910         if (closeRequested) {
911             throw new IllegalStateException("zip file closed");
912         }
913         if (res.zsrc == null) {
914             throw new IllegalStateException("The object is not initialized.");
915         }
916     }
917 
ensureOpenOrZipException()918     private void ensureOpenOrZipException() throws IOException {
919         if (closeRequested) {
920             throw new ZipException("ZipFile closed");
921         }
922     }
923 
924     /*
925      * Inner class implementing the input stream used to read a
926      * (possibly compressed) zip file entry.
927      */
928     private class ZipFileInputStream extends InputStream {
929         private volatile boolean closeRequested;
930         private   long pos;     // current position within entry data
931         private   long startingPos; // Start position for the entry data
932         protected long rem;     // number of remaining bytes within entry
933         protected long size;    // uncompressed size of this entry
934 
935         // Android-changed: don't keep CEN bytes in heap memory after initialization.
936         //ZipFileInputStream(byte[] cen, int cenpos) {
ZipFileInputStream(DirectByteBuffer cen, int cenpos)937         ZipFileInputStream(DirectByteBuffer cen, int cenpos) {
938             rem = CENSIZ(cen, cenpos);
939             size = CENLEN(cen, cenpos);
940             pos = CENOFF(cen, cenpos);
941             // zip64
942             if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL ||
943                 pos == ZIP64_MAGICVAL) {
944                 checkZIP64(cen, cenpos);
945             }
946             // negative for lazy initialization, see getDataOffset();
947             pos = - (pos + ZipFile.this.res.zsrc.locpos);
948         }
949 
950         // Android-changed: don't keep CEN bytes in heap memory after initialization.
951         //private void checkZIP64(byte[] cen, int cenpos) {
checkZIP64(DirectByteBuffer cen, int cenpos)952         private void checkZIP64(DirectByteBuffer cen, int cenpos) {
953             int off = cenpos + CENHDR + CENNAM(cen, cenpos);
954             int end = off + CENEXT(cen, cenpos);
955             while (off + 4 < end) {
956                 int tag = get16(cen, off);
957                 int sz = get16(cen, off + 2);
958                 off += 4;
959                 if (off + sz > end)         // invalid data
960                     break;
961                 if (tag == EXTID_ZIP64) {
962                     if (size == ZIP64_MAGICVAL) {
963                         if (sz < 8 || (off + 8) > end)
964                             break;
965                         size = get64(cen, off);
966                         sz -= 8;
967                         off += 8;
968                     }
969                     if (rem == ZIP64_MAGICVAL) {
970                         if (sz < 8 || (off + 8) > end)
971                             break;
972                         rem = get64(cen, off);
973                         sz -= 8;
974                         off += 8;
975                     }
976                     if (pos == ZIP64_MAGICVAL) {
977                         if (sz < 8 || (off + 8) > end)
978                             break;
979                         pos = get64(cen, off);
980                         sz -= 8;
981                         off += 8;
982                     }
983                     break;
984                 }
985                 off += sz;
986             }
987         }
988 
989         /*
990          * The Zip file spec explicitly allows the LOC extra data size to
991          * be different from the CEN extra data size. Since we cannot trust
992          * the CEN extra data size, we need to read the LOC to determine
993          * the entry data offset.
994          */
initDataOffset()995         private long initDataOffset() throws IOException {
996             if (pos <= 0) {
997                 byte[] loc = new byte[LOCHDR];
998                 pos = -pos;
999                 int len = ZipFile.this.res.zsrc.readFullyAt(loc, 0, loc.length, pos);
1000                 if (len != LOCHDR) {
1001                     throw new ZipException("ZipFile error reading zip file");
1002                 }
1003                 if (LOCSIG(loc) != LOCSIG) {
1004                     throw new ZipException("ZipFile invalid LOC header (bad signature)");
1005                 }
1006                 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc);
1007                 startingPos = pos; // Save starting position for the entry
1008             }
1009             return pos;
1010         }
1011 
read(byte b[], int off, int len)1012         public int read(byte b[], int off, int len) throws IOException {
1013             synchronized (ZipFile.this) {
1014                 ensureOpenOrZipException();
1015                 initDataOffset();
1016                 if (rem == 0) {
1017                     return -1;
1018                 }
1019                 if (len > rem) {
1020                     len = (int) rem;
1021                 }
1022                 if (len <= 0) {
1023                     return 0;
1024                 }
1025                 len = ZipFile.this.res.zsrc.readAt(b, off, len, pos);
1026                 if (len > 0) {
1027                     pos += len;
1028                     rem -= len;
1029                 }
1030             }
1031             if (rem == 0) {
1032                 close();
1033             }
1034             return len;
1035         }
1036 
read()1037         public int read() throws IOException {
1038             byte[] b = new byte[1];
1039             if (read(b, 0, 1) == 1) {
1040                 return b[0] & 0xff;
1041             } else {
1042                 return -1;
1043             }
1044         }
1045 
skip(long n)1046         public long skip(long n) throws IOException {
1047             synchronized (ZipFile.this) {
1048                 initDataOffset();
1049                 long newPos = pos + n;
1050                 if (n > 0) {
1051                     // If we overflowed adding the skip value or are moving
1052                     // past EOF, set the skip value to number of bytes remaining
1053                     // to reach EOF
1054                     if (newPos < 0 || n > rem) {
1055                         n = rem;
1056                     }
1057                 } else if (newPos < startingPos) {
1058                     // Tried to position before BOF so set position to the
1059                     // BOF and return the number of bytes we moved backwards
1060                     // to reach BOF
1061                     n = startingPos - pos;
1062                 }
1063                 pos += n;
1064                 rem -= n;
1065             }
1066             if (rem == 0) {
1067                 close();
1068             }
1069             return n;
1070         }
1071 
available()1072         public int available() {
1073             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1074         }
1075 
size()1076         public long size() {
1077             return size;
1078         }
1079 
close()1080         public void close() {
1081             if (closeRequested) {
1082                 return;
1083             }
1084             closeRequested = true;
1085             rem = 0;
1086             synchronized (res.istreams) {
1087                 res.istreams.remove(this);
1088             }
1089         }
1090 
1091     }
1092 
1093     /**
1094      * Returns {@code true} if, and only if, the zip file begins with {@code
1095      * LOCSIG}.
1096      * @hide
1097      */
1098     // Android-added: Access startsWithLocHeader() directly.
1099     // Make hidden public for use by sun.misc.URLClassPath
startsWithLocHeader()1100     public boolean startsWithLocHeader() {
1101         return res.zsrc.startsWithLoc;
1102     }
1103 
1104     // Android-changed: marked as protected so JarFile can access it.
1105     /**
1106      * Returns the names of the META-INF/MANIFEST.MF entry - if exists -
1107      * and any signature-related files under META-INF. This method is used in
1108      * JarFile, via SharedSecrets, as an optimization.
1109      * @hide
1110      */
getManifestAndSignatureRelatedFiles()1111     protected List<String> getManifestAndSignatureRelatedFiles() {
1112         synchronized (this) {
1113             ensureOpen();
1114             Source zsrc = res.zsrc;
1115             int[] metanames = zsrc.signatureMetaNames;
1116             List<String> files = null;
1117             if (zsrc.manifestPos >= 0) {
1118                 files = new ArrayList<>();
1119                 files.add(getEntryName(zsrc.manifestPos));
1120             }
1121             if (metanames != null) {
1122                 if (files == null) {
1123                     files = new ArrayList<>();
1124                 }
1125                 for (int i = 0; i < metanames.length; i++) {
1126                     files.add(getEntryName(metanames[i]));
1127                 }
1128             }
1129             return files == null ? List.of() : files;
1130         }
1131     }
1132 
1133     /**
1134      * Returns the number of the META-INF/MANIFEST.MF entries, case insensitive.
1135      * When this number is greater than 1, JarVerifier will treat a file as
1136      * unsigned.
1137      */
getManifestNum()1138     private int getManifestNum() {
1139         synchronized (this) {
1140             ensureOpen();
1141             return res.zsrc.manifestNum;
1142         }
1143     }
1144 
1145     // Android-changed: marked public and @hide as alternative to JavaUtilZipFileAccess.getManifestName.
1146     /**
1147      * Returns the name of the META-INF/MANIFEST.MF entry, ignoring
1148      * case. If {@code onlyIfSignatureRelatedFiles} is true, we only return the
1149      * manifest if there is also at least one signature-related file.
1150      * This method is used in JarFile, via SharedSecrets, as an optimization
1151      * when looking up the manifest file.
1152      * @hide
1153      */
getManifestName(boolean onlyIfSignatureRelatedFiles)1154     protected String getManifestName(boolean onlyIfSignatureRelatedFiles) {
1155         synchronized (this) {
1156             ensureOpen();
1157             Source zsrc = res.zsrc;
1158             int pos = zsrc.manifestPos;
1159             if (pos >= 0 && (!onlyIfSignatureRelatedFiles || zsrc.signatureMetaNames != null)) {
1160                 return getEntryName(pos);
1161             }
1162         }
1163         return null;
1164     }
1165 
1166     /**
1167      * Returns the versions for which there exists a non-directory
1168      * entry that begin with "META-INF/versions/" (case ignored).
1169      * This method is used in JarFile, via SharedSecrets, as an
1170      * optimization when looking up potentially versioned entries.
1171      * Returns an empty array if no versioned entries exist.
1172      */
getMetaInfVersions()1173     private int[] getMetaInfVersions() {
1174         synchronized (this) {
1175             ensureOpen();
1176             return res.zsrc.metaVersions;
1177         }
1178     }
1179 
1180     // Android-removed: this code does not run on Windows and JavaUtilZipFileAccess is not imported.
1181     /*
1182     private static boolean isWindows;
1183 
1184     static {
1185         SharedSecrets.setJavaUtilZipFileAccess(
1186             new JavaUtilZipFileAccess() {
1187                 @Override
1188                 public boolean startsWithLocHeader(ZipFile zip) {
1189                     return zip.res.zsrc.startsWithLoc;
1190                 }
1191                 @Override
1192                 public List<String> getManifestAndSignatureRelatedFiles(JarFile jar) {
1193                     return ((ZipFile)jar).getManifestAndSignatureRelatedFiles();
1194                 }
1195                 @Override
1196                 public int getManifestNum(JarFile jar) {
1197                     return ((ZipFile)jar).getManifestNum();
1198                 }
1199                 @Override
1200                 public String getManifestName(JarFile jar, boolean onlyIfHasSignatureRelatedFiles) {
1201                     return ((ZipFile)jar).getManifestName(onlyIfHasSignatureRelatedFiles);
1202                 }
1203                 @Override
1204                 public int[] getMetaInfVersions(JarFile jar) {
1205                     return ((ZipFile)jar).getMetaInfVersions();
1206                 }
1207                 @Override
1208                 public Enumeration<JarEntry> entries(ZipFile zip) {
1209                     return zip.jarEntries();
1210                 }
1211                 @Override
1212                 public Stream<JarEntry> stream(ZipFile zip) {
1213                     return zip.jarStream();
1214                 }
1215                 @Override
1216                 public Stream<String> entryNameStream(ZipFile zip) {
1217                     return zip.entryNameStream();
1218                 }
1219                 @Override
1220                 public int getExtraAttributes(ZipEntry ze) {
1221                     return ze.extraAttributes;
1222                 }
1223                 @Override
1224                 public void setExtraAttributes(ZipEntry ze, int extraAttrs) {
1225                     ze.extraAttributes = extraAttrs;
1226                 }
1227 
1228              }
1229         );
1230         isWindows = VM.getSavedProperty("os.name").contains("Windows");
1231     }
1232     */
1233 
1234     private static class Source {
1235         // While this is only used from ZipFile, defining it there would cause
1236         // a bootstrap cycle that would leave this initialized as null
1237         // Android-removed: JavaUtilJarAccess is not available.
1238         // private static final JavaUtilJarAccess JUJA = SharedSecrets.javaUtilJarAccess();
1239         // "META-INF/".length()
1240         private static final int META_INF_LEN = 9;
1241         private static final int[] EMPTY_META_VERSIONS = new int[0];
1242 
1243         private final Key key;               // the key in files
1244         private final @Stable ZipCoder zc;   // zip coder used to decode/encode
1245 
1246         private int refs = 1;
1247 
1248         private RandomAccessFile zfile;      // zfile of the underlying zip file
1249         private DirectByteBuffer cen;        // CEN & ENDHDR
1250         private int cenlen;                  // length of CEN & ENDHDR
1251         private long cenpos;                 // position of CEN & ENDHDR
1252         private long locpos;                 // position of first LOC header (usually 0)
1253         private byte[] comment;              // zip file comment
1254                                              // list of meta entries in META-INF dir
1255         private int   manifestPos = -1;      // position of the META-INF/MANIFEST.MF, if exists
1256         private int   manifestNum = 0;       // number of META-INF/MANIFEST.MF, case insensitive
1257         private int[] signatureMetaNames;    // positions of signature related entries, if such exist
1258         private int[] metaVersions;          // list of unique versions found in META-INF/versions/
1259         private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true)
1260 
1261         // A Hashmap for all entries.
1262         //
1263         // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR,
1264         // We might have a lot of these in a typical system. In order to save space we don't
1265         // keep the name in memory, but merely remember a 32 bit {@code hash} value of the
1266         // entry name and its offset {@code pos} in the central directory hdeader.
1267         //
1268         // private static class Entry {
1269         //     int hash;       // 32 bit hashcode on name
1270         //     int next;       // hash chain: index into entries
1271         //     int pos;        // Offset of central directory file header
1272         // }
1273         // private Entry[] entries;             // array of hashed cen entry
1274         //
1275         // To reduce the total size of entries further, we use a int[] here to store 3 "int"
1276         // {@code hash}, {@code next} and {@code pos} for each entry. The entry can then be
1277         // referred by their index of their positions in the {@code entries}.
1278         //
1279         private int[] entries;                  // array of hashed cen entry
1280 
1281         // Checks the entry at offset pos in the CEN, calculates the Entry values as per above,
1282         // then returns the length of the entry name.
1283         // Android-changed: don't keep CEN bytes in heap memory after initialization.
1284         //private int checkAndAddEntry(int pos, int index)
checkAndAddEntry(byte[] cen, int pos, int index)1285         private int checkAndAddEntry(byte[] cen, int pos, int index)
1286             throws ZipException
1287         {
1288             // Android-changed: don't keep CEN bytes in heap memory after initialization.
1289             //byte[] cen = this.cen;
1290             if (CENSIG(cen, pos) != CENSIG) {
1291                 zerror("invalid CEN header (bad signature)");
1292             }
1293             int method = CENHOW(cen, pos);
1294             int flag   = CENFLG(cen, pos);
1295             if ((flag & 1) != 0) {
1296                 zerror("invalid CEN header (encrypted entry)");
1297             }
1298             if (method != STORED && method != DEFLATED) {
1299                 zerror("invalid CEN header (bad compression method: " + method + ")");
1300             }
1301             int entryPos = pos + CENHDR;
1302             int nlen = CENNAM(cen, pos);
1303             if (entryPos + nlen > cen.length - ENDHDR) {
1304                 zerror("invalid CEN header (bad header size)");
1305             }
1306             try {
1307                 ZipCoder zcp = zipCoderForPos(cen, pos);
1308                 int hash = zcp.checkedHash(cen, entryPos, nlen);
1309                 int hsh = (hash & 0x7fffffff) % tablelen;
1310                 int next = table[hsh];
1311                 table[hsh] = index;
1312                 // Record the CEN offset and the name hash in our hash cell.
1313                 entries[index++] = hash;
1314                 entries[index++] = next;
1315                 entries[index  ] = pos;
1316             } catch (Exception e) {
1317                 zerror("invalid CEN header (bad entry name)");
1318             }
1319             return nlen;
1320         }
1321 
getEntryHash(int index)1322         private int getEntryHash(int index) { return entries[index]; }
getEntryNext(int index)1323         private int getEntryNext(int index) { return entries[index + 1]; }
getEntryPos(int index)1324         private int getEntryPos(int index)  { return entries[index + 2]; }
1325         private static final int ZIP_ENDCHAIN  = -1;
1326         private int total;                   // total number of entries
1327         private int[] table;                 // Hash chain heads: indexes into entries
1328         private int tablelen;                // number of hash heads
1329 
1330         // Android-changed: isZipFilePathValidatorEnabled added as Key part. Key is used as key in
1331         // files HashMap, so not including it could lead to opening ZipFile w/o entry names
1332         // validation.
1333         private static class Key {
1334             final BasicFileAttributes attrs;
1335             File file;
1336             final boolean utf8;
1337             // Android-added: isZipFilePathValidatorEnabled added as Key part.
1338             final boolean isZipFilePathValidatorEnabled;
1339 
Key(File file, BasicFileAttributes attrs, ZipCoder zc)1340             public Key(File file, BasicFileAttributes attrs, ZipCoder zc) {
1341                 this(file, attrs, zc, /* isZipFilePathValidatorEnabled= */ false);
1342             }
1343 
1344             // Android-added: added constructor with isZipFilePathValidatorEnabled argument.
Key(File file, BasicFileAttributes attrs, ZipCoder zc, boolean isZipFilePathValidatorEnabled)1345             public Key(File file, BasicFileAttributes attrs, ZipCoder zc,
1346                     boolean isZipFilePathValidatorEnabled) {
1347                 this.attrs = attrs;
1348                 this.file = file;
1349                 this.utf8 = zc.isUTF8();
1350                 this.isZipFilePathValidatorEnabled = isZipFilePathValidatorEnabled;
1351             }
1352 
hashCode()1353             public int hashCode() {
1354                 long t = utf8 ? 0 : Long.MAX_VALUE;
1355                 t += attrs.lastModifiedTime().toMillis();
1356                 // Android-changed: include izZipFilePathValidatorEnabled in hash computation.
1357                 // return ((int)(t ^ (t >>> 32))) + file.hashCode();
1358                 return ((int)(t ^ (t >>> 32))) + file.hashCode()
1359                         + Boolean.hashCode(isZipFilePathValidatorEnabled);
1360             }
1361 
equals(Object obj)1362             public boolean equals(Object obj) {
1363                 if (obj instanceof Key key) {
1364                     if (key.utf8 != utf8) {
1365                         return false;
1366                     }
1367                     if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) {
1368                         return false;
1369                     }
1370                     // Android-added: include isZipFilePathValidatorEnabled as equality part.
1371                     if (key.isZipFilePathValidatorEnabled != isZipFilePathValidatorEnabled) {
1372                         return false;
1373                     }
1374                     Object fk = attrs.fileKey();
1375                     if (fk != null) {
1376                         return fk.equals(key.attrs.fileKey());
1377                     } else {
1378                         return file.equals(key.file);
1379                     }
1380                 }
1381                 return false;
1382             }
1383         }
1384         private static final HashMap<Key, Source> files = new HashMap<>();
1385 
1386 
1387         // Android-changed: pass izZipFilePathValidatorEnabled argument.
1388         // static Source get(File file, boolean toDelete, ZipCoder zc) throws IOException {
get(File file, boolean toDelete, ZipCoder zc, boolean isZipPathValidatorEnabled)1389         static Source get(File file, boolean toDelete, ZipCoder zc,
1390                 boolean isZipPathValidatorEnabled) throws IOException {
1391             final Key key;
1392             try {
1393                 // BEGIN Android-changed: isZipFilePathValidatorEnabled passed as part of Key.
1394                 /*
1395                 key = new Key(file,
1396                         Files.readAttributes(file.toPath(), BasicFileAttributes.class),
1397                         zc);
1398                 */
1399                 key = new Key(file,
1400                         Files.readAttributes(file.toPath(), BasicFileAttributes.class),
1401                         zc, isZipPathValidatorEnabled);
1402                 // END Android-changed: isZipFilePathValidatorEnabled passed as part of Key.
1403             } catch (InvalidPathException ipe) {
1404                 throw new IOException(ipe);
1405             }
1406             Source src;
1407             synchronized (files) {
1408                 src = files.get(key);
1409                 if (src != null) {
1410                     src.refs++;
1411                     return src;
1412                 }
1413             }
1414             src = new Source(key, toDelete, zc);
1415 
1416             synchronized (files) {
1417                 if (files.containsKey(key)) {    // someone else put in first
1418                     src.close();                 // close the newly created one
1419                     src = files.get(key);
1420                     src.refs++;
1421                     return src;
1422                 }
1423                 files.put(key, src);
1424                 return src;
1425             }
1426         }
1427 
release(Source src)1428         static void release(Source src) throws IOException {
1429             synchronized (files) {
1430                 if (src != null && --src.refs == 0) {
1431                     files.remove(src.key);
1432                     src.close();
1433                 }
1434             }
1435         }
1436 
Source(Key key, boolean toDelete, ZipCoder zc)1437         private Source(Key key, boolean toDelete, ZipCoder zc) throws IOException {
1438             this.zc = zc;
1439             this.key = key;
1440             if (toDelete) {
1441                 // BEGIN Android-changed: we are not targeting Windows, keep else branch only. Also
1442                 // open file with O_CLOEXEC flag set.
1443                 /*
1444                 if (isWindows) {
1445                     this.zfile = SharedSecrets.getJavaIORandomAccessFileAccess()
1446                                               .openAndDelete(key.file, "r");
1447                 } else {
1448                     this.zfile = new RandomAccessFile(key.file, "r");
1449                     key.file.delete();
1450                 }
1451                 */
1452                 this.zfile = new RandomAccessFile(key.file, "r", /* setCloExecFlag= */ true);
1453                 key.file.delete();
1454                 // END Android-changed: we are not targeting Windows, keep else branch only.
1455             } else {
1456                 // Android-changed: open with O_CLOEXEC flag set.
1457                 // this.zfile = new RandomAccessFile(key.file, "r");
1458                 this.zfile = new RandomAccessFile(key.file, "r", /* setCloExecFlag= */ true);
1459             }
1460             try {
1461                 initCEN(null, -1);
1462                 byte[] buf = new byte[4];
1463                 readFullyAt(buf, 0, 4, 0);
1464                 // BEGIN Android-changed: do not accept files with invalid header
1465                 // this.startsWithLoc = (LOCSIG(buf) == LOCSIG);
1466                 long locsig = LOCSIG(buf);
1467                 this.startsWithLoc = (locsig == LOCSIG);
1468                 // If a zip file starts with "end of central directory record" it means that such
1469                 // file is empty.
1470                 if (locsig != LOCSIG && locsig != ENDSIG) {
1471                     String msg = "Entry at offset zero has invalid LFH signature "
1472                                     + Long.toHexString(locsig);
1473                     throw new ZipException(msg);
1474                 }
1475                 // END Android-changed: do not accept files with invalid header
1476             } catch (IOException x) {
1477                 try {
1478                     this.zfile.close();
1479                 } catch (IOException xx) {}
1480                 throw x;
1481             }
1482         }
1483 
close()1484         private void close() throws IOException {
1485             zfile.close();
1486             zfile = null;
1487             if (cen != null) {
1488                 Cleaner cleaner = cen.cleaner();
1489                 if (cleaner != null) {
1490                     cleaner.clean();
1491                 }
1492                 cen = null;
1493             }
1494             entries = null;
1495             table = null;
1496             manifestPos = -1;
1497             manifestNum = 0;
1498             signatureMetaNames = null;
1499             metaVersions = EMPTY_META_VERSIONS;
1500         }
1501 
1502         private static final int BUF_SIZE = 8192;
readFullyAt(byte[] buf, int off, int len, long pos)1503         private final int readFullyAt(byte[] buf, int off, int len, long pos)
1504             throws IOException
1505         {
1506             synchronized (zfile) {
1507                 zfile.seek(pos);
1508                 int N = len;
1509                 while (N > 0) {
1510                     int n = Math.min(BUF_SIZE, N);
1511                     zfile.readFully(buf, off, n);
1512                     off += n;
1513                     N -= n;
1514                 }
1515                 return len;
1516             }
1517         }
1518 
readAt(byte[] buf, int off, int len, long pos)1519         private final int readAt(byte[] buf, int off, int len, long pos)
1520             throws IOException
1521         {
1522             synchronized (zfile) {
1523                 zfile.seek(pos);
1524                 return zfile.read(buf, off, len);
1525             }
1526         }
1527 
1528 
1529         private static class End {
1530             int  centot;     // 4 bytes
1531             long cenlen;     // 4 bytes
1532             long cenoff;     // 4 bytes
1533             long endpos;     // 4 bytes
1534         }
1535 
1536         /*
1537          * Searches for end of central directory (END) header. The contents of
1538          * the END header will be read and placed in endbuf. Returns the file
1539          * position of the END header, otherwise returns -1 if the END header
1540          * was not found or an error occurred.
1541          */
findEND()1542         private End findEND() throws IOException {
1543             long ziplen = zfile.length();
1544             if (ziplen <= 0)
1545                 zerror("zip file is empty");
1546             End end = new End();
1547             byte[] buf = new byte[READBLOCKSZ];
1548             long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0;
1549             long minPos = minHDR - (buf.length - ENDHDR);
1550             for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) {
1551                 int off = 0;
1552                 if (pos < 0) {
1553                     // Pretend there are some NUL bytes before start of file
1554                     off = (int)-pos;
1555                     Arrays.fill(buf, 0, off, (byte)0);
1556                 }
1557                 int len = buf.length - off;
1558                 if (readFullyAt(buf, off, len, pos + off) != len ) {
1559                     zerror("zip END header not found");
1560                 }
1561                 // Now scan the block backwards for END header signature
1562                 for (int i = buf.length - ENDHDR; i >= 0; i--) {
1563                     if (buf[i+0] == (byte)'P'    &&
1564                         buf[i+1] == (byte)'K'    &&
1565                         buf[i+2] == (byte)'\005' &&
1566                         buf[i+3] == (byte)'\006') {
1567                         // Found ENDSIG header
1568                         byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR);
1569                         end.centot = ENDTOT(endbuf);
1570                         end.cenlen = ENDSIZ(endbuf);
1571                         end.cenoff = ENDOFF(endbuf);
1572                         end.endpos = pos + i;
1573                         int comlen = ENDCOM(endbuf);
1574                         if (end.endpos + ENDHDR + comlen != ziplen) {
1575                             // ENDSIG matched, however the size of file comment in it does
1576                             // not match the real size. One "common" cause for this problem
1577                             // is some "extra" bytes are padded at the end of the zipfile.
1578                             // Let's do some extra verification, we don't care about the
1579                             // performance in this situation.
1580                             byte[] sbuf = new byte[4];
1581                             long cenpos = end.endpos - end.cenlen;
1582                             long locpos = cenpos - end.cenoff;
1583                             if  (cenpos < 0 ||
1584                                  locpos < 0 ||
1585                                  readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 ||
1586                                  GETSIG(sbuf) != CENSIG ||
1587                                  readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 ||
1588                                  GETSIG(sbuf) != LOCSIG) {
1589                                 continue;
1590                             }
1591                         }
1592                         if (comlen > 0) {    // this zip file has comlen
1593                             comment = new byte[comlen];
1594                             if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) {
1595                                 zerror("zip comment read failed");
1596                             }
1597                         }
1598                         // must check for a zip64 end record; it is always permitted to be present
1599                         try {
1600                             byte[] loc64 = new byte[ZIP64_LOCHDR];
1601                             if (end.endpos < ZIP64_LOCHDR ||
1602                                 readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR)
1603                                 != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) {
1604                                 return end;
1605                             }
1606                             long end64pos = ZIP64_LOCOFF(loc64);
1607                             byte[] end64buf = new byte[ZIP64_ENDHDR];
1608                             if (readFullyAt(end64buf, 0, end64buf.length, end64pos)
1609                                 != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) {
1610                                 return end;
1611                             }
1612                             // end64 candidate found,
1613                             long cenlen64 = ZIP64_ENDSIZ(end64buf);
1614                             long cenoff64 = ZIP64_ENDOFF(end64buf);
1615                             long centot64 = ZIP64_ENDTOT(end64buf);
1616                             // double-check
1617                             if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MAGICVAL ||
1618                                 cenoff64 != end.cenoff && end.cenoff != ZIP64_MAGICVAL ||
1619                                 centot64 != end.centot && end.centot != ZIP64_MAGICCOUNT) {
1620                                 return end;
1621                             }
1622                             // to use the end64 values
1623                             end.cenlen = cenlen64;
1624                             end.cenoff = cenoff64;
1625                             end.centot = (int)centot64; // assume total < 2g
1626                             end.endpos = end64pos;
1627                         } catch (IOException x) {}    // no zip64 loc/end
1628                         return end;
1629                     }
1630                 }
1631             }
1632             throw new ZipException("zip END header not found");
1633         }
1634 
1635         // Reads zip file central directory.
1636         // BEGIN Android-changed: don't keep CEN bytes in heap memory after initialization.
1637         //private void initCEN(int knownTotal) throws IOException {
initCEN(byte[] cen, int knownTotal)1638         private void initCEN(byte[] cen, int knownTotal) throws IOException {
1639             // Prefer locals for better performance during startup
1640             //byte[] cen;
1641             // END Android-changed: don't keep CEN bytes in heap memory after initialization.
1642             if (knownTotal == -1) {
1643                 End end = findEND();
1644                 if (end.endpos == 0) {
1645                     locpos = 0;
1646                     total = 0;
1647                     entries = new int[0];
1648                     this.cen = null;
1649                     return;         // only END header present
1650                 }
1651                 if (end.cenlen > end.endpos)
1652                     zerror("invalid END header (bad central directory size)");
1653                 // Android-changed: don't keep CEN bytes in heap memory after initialization.
1654                 /*long */cenpos = end.endpos - end.cenlen;     // position of CEN table
1655                 // Get position of first local file (LOC) header, taking into
1656                 // account that there may be a stub prefixed to the zip file.
1657                 locpos = cenpos - end.cenoff;
1658                 if (locpos < 0) {
1659                     zerror("invalid END header (bad central directory offset)");
1660                 }
1661                 // read in the CEN and END
1662                 // BEGIN Android-changed: don't keep CEN bytes in heap memory after initialization.
1663                 // cen = this.cen = new byte[(int)(end.cenlen + ENDHDR)];
1664                 cenlen = (int) (end.cenlen + ENDHDR);
1665                 DirectByteBuffer cenBuf = this.cen = (DirectByteBuffer) zfile.getChannel()
1666                         .map(MapMode.READ_ONLY, cenpos, cenlen);
1667                 cenBuf.order(ByteOrder.LITTLE_ENDIAN);
1668                 cen = new byte[cenlen];
1669                 cenBuf.get(0, cen, 0, cenlen);
1670                 // END Android-changed: don't keep CEN bytes in heap memory after initialization.
1671                 this.total = end.centot;
1672             } else {
1673                 // Android-changed: don't keep CEN bytes in heap memory after initialization.
1674                 //cen = this.cen;
1675                 this.total = knownTotal;
1676             }
1677             // hash table for entries
1678             int entriesLength = this.total * 3;
1679             entries = new int[entriesLength];
1680 
1681             int tablelen = ((total/2) | 1); // Odd -> fewer collisions
1682             this.tablelen = tablelen;
1683 
1684             int[] table = new int[tablelen];
1685             this.table = table;
1686 
1687             Arrays.fill(table, ZIP_ENDCHAIN);
1688 
1689             // list for all meta entries
1690             ArrayList<Integer> signatureNames = null;
1691             // Set of all version numbers seen in META-INF/versions/
1692             Set<Integer> metaVersionsSet = null;
1693 
1694             // Iterate through the entries in the central directory
1695             int idx = 0; // Index into the entries array
1696             int pos = 0;
1697             int entryPos = CENHDR;
1698             // Android-changed: don't keep CEN bytes in heap memory after initialization.
1699             //int limit = cen.length - ENDHDR;
1700             int limit = cenlen - ENDHDR;
1701             manifestNum = 0;
1702             // Android-added: duplicate entries are not allowed. See CVE-2013-4787 and b/8219321
1703             Set<String> entriesNames = new HashSet<>();
1704             while (entryPos <= limit) {
1705                 if (idx >= entriesLength) {
1706                     // This will only happen if the zip file has an incorrect
1707                     // ENDTOT field, which usually means it contains more than
1708                     // 65535 entries.
1709                     initCEN(cen, countCENHeaders(cen, limit));
1710                     return;
1711                 }
1712 
1713                 // Checks the entry and adds values to entries[idx ... idx+2]
1714                 int nlen = checkAndAddEntry(cen, pos, idx);
1715 
1716                 // BEGIN Android-added: duplicate entries are not allowed. See CVE-2013-4787
1717                 // and b/8219321.
1718                 // zipCoderForPos takes USE_UTF8 flag into account.
1719                 ZipCoder zcp = zipCoderForPos(cen, entryPos);
1720                 String name = zcp.toString(cen, pos + CENHDR, nlen);
1721                 if (!entriesNames.add(name)) {
1722                     zerror("Duplicate entry name: " + name);
1723                 }
1724                 // END Android-added: duplicate entries are not allowed. See CVE-2013-4787
1725                 // and b/8219321
1726                 // BEGIN Android-added: don't allow NUL in entry names. We can handle it in Java fine,
1727                 // but it is of questionable utility as a valid pathname can't contain NUL.
1728                 for (int nameIdx = 0; nameIdx < nlen; ++nameIdx) {
1729                     byte b = cen[pos + CENHDR + nameIdx];
1730 
1731                     if (b == 0) {
1732                         zerror("Filename contains NUL byte: " + name);
1733                     }
1734                 }
1735                 // END Android-added: don't allow NUL in entry names.
1736                 // BEGIN Android-changed: validation of zip entry names.
1737                 if (key.isZipFilePathValidatorEnabled && !ZipPathValidator.isClear()) {
1738                     ZipPathValidator.getInstance().onZipEntryAccess(name);
1739                 }
1740                 // END Android-changed: validation of zip entry names.
1741                 idx += 3;
1742 
1743                 // Adds name to metanames.
1744                 if (isMetaName(cen, entryPos, nlen)) {
1745                     // nlen is at least META_INF_LENGTH
1746                     if (isManifestName(cen, entryPos + META_INF_LEN, nlen - META_INF_LEN)) {
1747                         manifestPos = pos;
1748                         manifestNum++;
1749                     } else {
1750                         if (isSignatureRelated(cen, entryPos, nlen)) {
1751                             if (signatureNames == null)
1752                                 signatureNames = new ArrayList<>(4);
1753                             signatureNames.add(pos);
1754                         }
1755 
1756                         // If this is a versioned entry, parse the version
1757                         // and store it for later. This optimizes lookup
1758                         // performance in multi-release jar files
1759                         int version = getMetaVersion(cen, entryPos + META_INF_LEN, nlen - META_INF_LEN);
1760                         if (version > 0) {
1761                             if (metaVersionsSet == null)
1762                                 metaVersionsSet = new TreeSet<>();
1763                             metaVersionsSet.add(version);
1764                         }
1765                     }
1766                 }
1767                 // skip to the start of the next entry
1768                 pos = nextEntryPos(cen, pos, entryPos, nlen);
1769                 entryPos = pos + CENHDR;
1770             }
1771 
1772             // Adjust the total entries
1773             this.total = idx / 3;
1774 
1775             if (signatureNames != null) {
1776                 int len = signatureNames.size();
1777                 signatureMetaNames = new int[len];
1778                 for (int j = 0; j < len; j++) {
1779                     signatureMetaNames[j] = signatureNames.get(j);
1780                 }
1781             }
1782             if (metaVersionsSet != null) {
1783                 metaVersions = new int[metaVersionsSet.size()];
1784                 int c = 0;
1785                 for (Integer version : metaVersionsSet) {
1786                     metaVersions[c++] = version;
1787                 }
1788             } else {
1789                 metaVersions = EMPTY_META_VERSIONS;
1790             }
1791             if (pos + ENDHDR != cen.length) {
1792                 zerror("invalid CEN header (bad header size)");
1793             }
1794         }
1795 
1796         // Android-changed: don't keep CEN bytes in heap memory after initialization.
1797         //private int nextEntryPos(byte[] cen, int pos, int entryPos, int nlen) {
nextEntryPos(byte[] cen, int pos, int entryPos, int nlen)1798         private int nextEntryPos(byte[] cen, int pos, int entryPos, int nlen) {
1799             return entryPos + nlen + CENCOM(cen, pos) + CENEXT(cen, pos);
1800         }
1801 
zerror(String msg)1802         private static void zerror(String msg) throws ZipException {
1803             throw new ZipException(msg);
1804         }
1805 
1806         /*
1807          * Returns the {@code pos} of the zip cen entry corresponding to the
1808          * specified entry name, or -1 if not found.
1809          */
getEntryPos(String name, boolean addSlash)1810         private int getEntryPos(String name, boolean addSlash) {
1811             if (total == 0) {
1812                 return -1;
1813             }
1814 
1815             int hsh = ZipCoder.hash(name);
1816             int idx = table[(hsh & 0x7fffffff) % tablelen];
1817 
1818             // Search down the target hash chain for a entry whose
1819             // 32 bit hash matches the hashed name.
1820             while (idx != ZIP_ENDCHAIN) {
1821                 if (getEntryHash(idx) == hsh) {
1822                     // The CEN name must match the specfied one
1823                     int pos = getEntryPos(idx);
1824 
1825                     try {
1826                         ZipCoder zc = zipCoderForPos(pos);
1827                         String entry = zc.toString(cen, pos + CENHDR, CENNAM(cen, pos));
1828 
1829                         // If addSlash is true we'll test for name+/ in addition to
1830                         // name, unless name is the empty string or already ends with a
1831                         // slash
1832                         int entryLen = entry.length();
1833                         int nameLen = name.length();
1834                         if ((entryLen == nameLen && entry.equals(name)) ||
1835                                 (addSlash &&
1836                                 nameLen + 1 == entryLen &&
1837                                 entry.startsWith(name) &&
1838                                 entry.charAt(entryLen - 1) == '/')) {
1839                             return pos;
1840                         }
1841                     } catch (IllegalArgumentException iae) {
1842                         // Ignore
1843                     }
1844                 }
1845                 idx = getEntryNext(idx);
1846             }
1847             return -1;
1848         }
1849 
zipCoderForPos(int pos)1850         private ZipCoder zipCoderForPos(int pos) {
1851             if (zc.isUTF8()) {
1852                 return zc;
1853             }
1854             if ((CENFLG(cen, pos) & USE_UTF8) != 0) {
1855                 return ZipCoder.UTF8;
1856             }
1857             return zc;
1858         }
1859 
1860         // Android-changed: don't keep CEN bytes in heap memory after initialization.
zipCoderForPos(byte[] cen, int pos)1861         private ZipCoder zipCoderForPos(byte[] cen, int pos) {
1862             if (zc.isUTF8()) {
1863                 return zc;
1864             }
1865             if ((CENFLG(cen, pos) & USE_UTF8) != 0) {
1866                 return ZipCoder.UTF8;
1867             }
1868             return zc;
1869         }
1870 
1871         /**
1872          * Returns true if the bytes represent a non-directory name
1873          * beginning with "META-INF/", disregarding ASCII case.
1874          */
isMetaName(byte[] name, int off, int len)1875         private static boolean isMetaName(byte[] name, int off, int len) {
1876             // Use the "oldest ASCII trick in the book":
1877             // ch | 0x20 == Character.toLowerCase(ch)
1878             return len > META_INF_LEN       // "META-INF/".length()
1879                 && name[off + len - 1] != '/'  // non-directory
1880                 && (name[off++] | 0x20) == 'm'
1881                 && (name[off++] | 0x20) == 'e'
1882                 && (name[off++] | 0x20) == 't'
1883                 && (name[off++] | 0x20) == 'a'
1884                 && (name[off++]       ) == '-'
1885                 && (name[off++] | 0x20) == 'i'
1886                 && (name[off++] | 0x20) == 'n'
1887                 && (name[off++] | 0x20) == 'f'
1888                 && (name[off]         ) == '/';
1889         }
1890 
1891         /*
1892          * Check if the bytes represents a name equals to MANIFEST.MF
1893          */
1894         // Android-changed: don't keep CEN bytes in heap memory after initialization.
1895         //private boolean isManifestName(int off, int len) {
isManifestName(byte[] name, int off, int len)1896         private boolean isManifestName(byte[] name, int off, int len) {
1897             return (len == 11 // "MANIFEST.MF".length()
1898                     && (name[off++] | 0x20) == 'm'
1899                     && (name[off++] | 0x20) == 'a'
1900                     && (name[off++] | 0x20) == 'n'
1901                     && (name[off++] | 0x20) == 'i'
1902                     && (name[off++] | 0x20) == 'f'
1903                     && (name[off++] | 0x20) == 'e'
1904                     && (name[off++] | 0x20) == 's'
1905                     && (name[off++] | 0x20) == 't'
1906                     && (name[off++]       ) == '.'
1907                     && (name[off++] | 0x20) == 'm'
1908                     && (name[off]   | 0x20) == 'f');
1909         }
1910 
1911         // Android-changed: don't keep CEN bytes in heap memory after initialization.
1912         //private boolean isSignatureRelated(int off, int len) {
isSignatureRelated(byte[] name, int off, int len)1913         private boolean isSignatureRelated(byte[] name, int off, int len) {
1914             // Only called when isMetaName(name, off, len) is true, which means
1915             // len is at least META_INF_LENGTH
1916             // assert isMetaName(name, off, len)
1917             boolean signatureRelated = false;
1918             // Android-changed: don't keep CEN bytes in heap memory after initialization.
1919             //byte[] name = cen;
1920             if (name[off + len - 3] == '.') {
1921                 // Check if entry ends with .EC and .SF
1922                 int b1 = name[off + len - 2] | 0x20;
1923                 int b2 = name[off + len - 1] | 0x20;
1924                 if ((b1 == 'e' && b2 == 'c') || (b1 == 's' && b2 == 'f')) {
1925                     signatureRelated = true;
1926                 }
1927             } else if (name[off + len - 4] == '.') {
1928                 // Check if entry ends with .DSA and .RSA
1929                 int b1 = name[off + len - 3] | 0x20;
1930                 int b2 = name[off + len - 2] | 0x20;
1931                 int b3 = name[off + len - 1] | 0x20;
1932                 if ((b1 == 'r' || b1 == 'd') && b2 == 's' && b3 == 'a') {
1933                     signatureRelated = true;
1934                 }
1935             }
1936             // Above logic must match SignatureFileVerifier.isBlockOrSF
1937             assert(signatureRelated == SignatureFileVerifier
1938                 // Android-changed: use StandardCharsets.
1939                 // .isBlockOrSF(new String(name, off, len, UTF_8.INSTANCE)
1940                 .isBlockOrSF(new String(name, off, len, StandardCharsets.UTF_8)
1941                     .toUpperCase(Locale.ENGLISH)));
1942             return signatureRelated;
1943         }
1944 
1945         /*
1946          * If the bytes represents a non-directory name beginning
1947          * with "versions/", continuing with a positive integer,
1948          * followed by a '/', then return that integer value.
1949          * Otherwise, return 0
1950          */
1951         // BEGIN Android-changed: don't keep CEN bytes in heap memory after initialization.
1952         //private int getMetaVersion(int off, int len) {
getMetaVersion(byte[] name, int off, int len)1953         private int getMetaVersion(byte[] name, int off, int len) {
1954             //byte[] name = cen;
1955             // END Android-changed: don't keep CEN bytes in heap memory after initialization.
1956             int nend = off + len;
1957             if (!(len > 10                         // "versions//".length()
1958                     && name[off + len - 1] != '/'  // non-directory
1959                     && (name[off++] | 0x20) == 'v'
1960                     && (name[off++] | 0x20) == 'e'
1961                     && (name[off++] | 0x20) == 'r'
1962                     && (name[off++] | 0x20) == 's'
1963                     && (name[off++] | 0x20) == 'i'
1964                     && (name[off++] | 0x20) == 'o'
1965                     && (name[off++] | 0x20) == 'n'
1966                     && (name[off++] | 0x20) == 's'
1967                     && (name[off++]       ) == '/')) {
1968                 return 0;
1969             }
1970             int version = 0;
1971             while (off < nend) {
1972                 final byte c = name[off++];
1973                 if (c == '/') {
1974                     return version;
1975                 }
1976                 if (c < '0' || c > '9') {
1977                     return 0;
1978                 }
1979                 version = version * 10 + c - '0';
1980                 // Check for overflow and leading zeros
1981                 if (version <= 0) {
1982                     return 0;
1983                 }
1984             }
1985             return 0;
1986         }
1987 
1988         /**
1989          * Returns the number of CEN headers in a central directory.
1990          * Will not throw, even if the zip file is corrupt.
1991          *
1992          * @param cen copy of the bytes in a zip file's central directory
1993          * @param size number of bytes in central directory
1994          */
countCENHeaders(byte[] cen, int size)1995         private static int countCENHeaders(byte[] cen, int size) {
1996             int count = 0;
1997             for (int p = 0;
1998                  p + CENHDR <= size;
1999                  p += CENHDR + CENNAM(cen, p) + CENEXT(cen, p) + CENCOM(cen, p))
2000                 count++;
2001             return count;
2002         }
2003     }
2004 }
2005