blob: ee23e551b6beed2f5b4a795754c974b08936203d [file] [log] [blame]
Aurimas Liutikasdc3f8852024-07-11 10:07:48 -07001/*
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
27package java.util.zip;
28
29import java.io.Closeable;
30import java.io.InputStream;
31import java.io.IOException;
32import java.io.EOFException;
33import java.io.File;
34import java.io.FileNotFoundException;
35import java.io.RandomAccessFile;
36import java.io.UncheckedIOException;
37import java.lang.ref.Cleaner.Cleanable;
38import java.nio.charset.CharacterCodingException;
39import java.nio.charset.Charset;
40import java.nio.charset.StandardCharsets;
41import java.nio.file.InvalidPathException;
42import java.nio.file.attribute.BasicFileAttributes;
43import java.nio.file.Files;
44import java.util.ArrayDeque;
45import java.util.ArrayList;
46import java.util.Arrays;
47import java.util.Collections;
48import java.util.Deque;
49import java.util.Enumeration;
50import java.util.HashMap;
51import java.util.HashSet;
52import java.util.Iterator;
53import java.util.List;
54import java.util.Locale;
55import java.util.Objects;
56import java.util.NoSuchElementException;
57import java.util.Set;
58import java.util.Spliterator;
59import java.util.Spliterators;
60import java.util.TreeSet;
61import java.util.WeakHashMap;
62import java.util.function.Consumer;
63import java.util.function.IntFunction;
64import java.util.jar.JarEntry;
65import java.util.jar.JarFile;
66import java.util.stream.Stream;
67import java.util.stream.StreamSupport;
68import jdk.internal.access.SharedSecrets;
69import jdk.internal.misc.VM;
70import jdk.internal.ref.CleanerFactory;
71import jdk.internal.vm.annotation.Stable;
72import sun.security.util.SignatureFileVerifier;
73
74import dalvik.system.CloseGuard;
75import dalvik.system.ZipPathValidator;
76
77import static java.util.zip.ZipConstants64.*;
78import 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 */
98public 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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
445 InflaterCleanupAction(Inflater inf, CleanableResource res) {
446 this.inf = inf;
447 this.res = res;
448 }
449
450 @Override
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
461 ZipFileInflaterInputStream(ZipFileInputStream zfin,
462 CleanableResource res, int size) {
463 this(zfin, res, res.getInflater(), size);
464 }
465
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
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.
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
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 */
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
527 public ZipEntryIterator(int entryCount) {
528 this.entryCount = entryCount;
529 }
530
531 @Override
532 public boolean hasMoreElements() {
533 return hasNext();
534 }
535
536 @Override
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
547 public T nextElement() {
548 return next();
549 }
550
551 @Override
552 @SuppressWarnings("unchecked")
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
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 */
575 public Enumeration<? extends ZipEntry> entries() {
576 synchronized (this) {
577 ensureOpen();
578 return new ZipEntryIterator<ZipEntry>(res.zsrc.total);
579 }
580 }
581
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
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
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 */
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
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 */
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 */
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 */
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 */
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
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.
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
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 */
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 */
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
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 */
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
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
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
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
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 */
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
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
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
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
1052 public int available() {
1053 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1054 }
1055
1056 public long size() {
1057 return size;
1058 }
1059
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
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 */
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 */
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 */
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 */
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.
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
1297 private int getEntryHash(int index) { return entries[index]; }
1298 private int getEntryNext(int index) { return entries[index + 1]; }
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
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.
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
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
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 {
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
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
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
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;
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
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 */
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.
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
1753 private int nextEntryPos(int pos, int entryPos, int nlen) {
1754 return entryPos + nlen + CENCOM(cen, pos) + CENEXT(cen, pos);
1755 }
1756
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 */
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
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 */
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 */
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
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 */
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 */
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}