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