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