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