1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1995, 2015, 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.nio.charset.Charset; 36 import java.nio.charset.StandardCharsets; 37 import java.util.ArrayDeque; 38 import java.util.Deque; 39 import java.util.Enumeration; 40 import java.util.HashMap; 41 import java.util.Iterator; 42 import java.util.Map; 43 import java.util.NoSuchElementException; 44 import java.util.Spliterator; 45 import java.util.Spliterators; 46 import java.util.WeakHashMap; 47 import java.util.stream.Stream; 48 import java.util.stream.StreamSupport; 49 50 import dalvik.system.CloseGuard; 51 52 import static java.util.zip.ZipConstants64.*; 53 54 /** 55 * This class is used to read entries from a zip file. 56 * 57 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor 58 * or method in this class will cause a {@link NullPointerException} to be 59 * thrown. 60 * 61 * @author David Connelly 62 */ 63 public 64 class ZipFile implements ZipConstants, Closeable { 65 // Android-note: jzfile does not require @ReachabilitySensitive annotation. 66 // The @ReachabilitySensitive annotation is usually added to instance fields that references 67 // native data that is cleaned up when the instance becomes unreachable. Its presence ensures 68 // that the instance object is not finalized until the field is no longer used. Without it an 69 // instance could be finalized during execution of an instance method iff that method's this 70 // variable holds the last reference to the instance and the method had copied all the fields 71 // it needs out of the instance. That would release the native data, invalidating its reference 72 // and would cause serious problems if the method had taken a copy of that field and 73 // then called a native method that would try to use it. 74 // 75 // This field does not require the annotation because all usages of this field are enclosed 76 // within a synchronized(this) block and finalizing of the object referenced in a synchronized 77 // block is not allowed as that would release its monitor that is currently in use. 78 private long jzfile; // address of jzfile data 79 private final String name; // zip file name 80 private final int total; // total number of entries 81 private final boolean locsig; // if zip file starts with LOCSIG (usually true) 82 private volatile boolean closeRequested = false; 83 84 // Android-added: CloseGuard support. 85 private final CloseGuard guard = CloseGuard.get(); 86 87 // Android-added: Do not use unlink() to implement OPEN_DELETE. 88 // Upstream uses unlink() to cause the file name to be removed from the filesystem after it is 89 // opened but that does not work on fuse fs as it causes problems with lseek. Android simply 90 // keeps a reference to the File so that it can explicitly delete it during close. 91 // 92 // OpenJDK 9+181 has a pure Java implementation of ZipFile that does not use unlink() and 93 // instead does something very similar to what Android does. If Android adopts it then this 94 // patch can be dropped. 95 // See http://b/28950284 and http://b/28901232 for more details. 96 private final File fileToRemoveOnClose; 97 98 private static final int STORED = ZipEntry.STORED; 99 private static final int DEFLATED = ZipEntry.DEFLATED; 100 101 /** 102 * Mode flag to open a zip file for reading. 103 */ 104 public static final int OPEN_READ = 0x1; 105 106 /** 107 * Mode flag to open a zip file and mark it for deletion. The file will be 108 * deleted some time between the moment that it is opened and the moment 109 * that it is closed, but its contents will remain accessible via the 110 * <tt>ZipFile</tt> object until either the close method is invoked or the 111 * virtual machine exits. 112 */ 113 public static final int OPEN_DELETE = 0x4; 114 115 // Android-removed: initIDs() not used on Android. 116 /* 117 static { 118 /* Zip library is loaded from System.initializeSystemClass * 119 initIDs(); 120 } 121 122 private static native void initIDs(); 123 */ 124 125 private static final boolean usemmap; 126 127 static { 128 // Android-changed: Always use mmap. 129 /* 130 // A system prpperty to disable mmap use to avoid vm crash when 131 // in-use zip file is accidently overwritten by others. 132 String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping"); 133 usemmap = (prop == null || 134 !(prop.length() == 0 || prop.equalsIgnoreCase("true"))); 135 */ 136 usemmap = true; 137 } 138 139 /** 140 * Opens a zip file for reading. 141 * 142 * <p>First, if there is a security manager, its <code>checkRead</code> 143 * method is called with the <code>name</code> argument as its argument 144 * to ensure the read is allowed. 145 * 146 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 147 * decode the entry names and comments. 148 * 149 * @param name the name of the zip file 150 * @throws ZipException if a ZIP format error has occurred 151 * @throws IOException if an I/O error has occurred 152 * @throws SecurityException if a security manager exists and its 153 * <code>checkRead</code> 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</code> to read from the specified 163 * <code>File</code> object in the specified mode. The mode argument 164 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 165 * 166 * <p>First, if there is a security manager, its <code>checkRead</code> 167 * method is called with the <code>name</code> 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</code> method 179 * doesn't allow read access to the file, 180 * or its <code>checkDelete</code> method doesn't allow deleting 181 * the file when the <tt>OPEN_DELETE</tt> flag is set. 182 * @throws IllegalArgumentException if the <tt>mode</tt> 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 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 private ZipCoder zc; 205 206 /** 207 * Opens a new <code>ZipFile</code> to read from the specified 208 * <code>File</code> object in the specified mode. The mode argument 209 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 210 * 211 * <p>First, if there is a security manager, its <code>checkRead</code> 212 * method is called with the <code>name</code> argument as its argument to 213 * ensure the read is allowed. 214 * 215 * @param file the ZIP file to be opened for reading 216 * @param mode the mode in which the file is to be opened 217 * @param charset 218 * the {@linkplain java.nio.charset.Charset charset} to 219 * be used to decode the ZIP entry name and comment that are not 220 * encoded by using UTF-8 encoding (indicated by entry's general 221 * purpose flag). 222 * 223 * @throws ZipException if a ZIP format error has occurred 224 * @throws IOException if an I/O error has occurred 225 * 226 * @throws SecurityException 227 * if a security manager exists and its <code>checkRead</code> 228 * method doesn't allow read access to the file,or its 229 * <code>checkDelete</code> method doesn't allow deleting the 230 * file when the <tt>OPEN_DELETE</tt> flag is set 231 * 232 * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid 233 * 234 * @see SecurityManager#checkRead(java.lang.String) 235 * 236 * @since 1.7 237 */ ZipFile(File file, int mode, Charset charset)238 public ZipFile(File file, int mode, Charset charset) throws IOException 239 { 240 if (((mode & OPEN_READ) == 0) || 241 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { 242 throw new IllegalArgumentException("Illegal mode: 0x"+ 243 Integer.toHexString(mode)); 244 } 245 String name = file.getPath(); 246 // Android-removed: SecurityManager is always null. 247 /* 248 SecurityManager sm = System.getSecurityManager(); 249 if (sm != null) { 250 sm.checkRead(name); 251 if ((mode & OPEN_DELETE) != 0) { 252 sm.checkDelete(name); 253 } 254 } 255 */ 256 257 // Android-added: Do not use unlink() to implement OPEN_DELETE. 258 fileToRemoveOnClose = ((mode & OPEN_DELETE) != 0) ? file : null; 259 260 if (charset == null) 261 throw new NullPointerException("charset is null"); 262 this.zc = ZipCoder.get(charset); 263 // Android-removed: Skip perf counters. 264 // long t0 = System.nanoTime(); 265 jzfile = open(name, mode, file.lastModified(), usemmap); 266 // Android-removed: Skip perf counters. 267 // sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); 268 // sun.misc.PerfCounter.getZipFileCount().increment(); 269 this.name = name; 270 this.total = getTotal(jzfile); 271 this.locsig = startsWithLOC(jzfile); 272 // Android-added: CloseGuard support. 273 guard.open("close"); 274 } 275 276 /** 277 * Opens a zip file for reading. 278 * 279 * <p>First, if there is a security manager, its <code>checkRead</code> 280 * method is called with the <code>name</code> argument as its argument 281 * to ensure the read is allowed. 282 * 283 * @param name the name of the zip file 284 * @param charset 285 * the {@linkplain java.nio.charset.Charset charset} to 286 * be used to decode the ZIP entry name and comment that are not 287 * encoded by using UTF-8 encoding (indicated by entry's general 288 * purpose flag). 289 * 290 * @throws ZipException if a ZIP format error has occurred 291 * @throws IOException if an I/O error has occurred 292 * @throws SecurityException 293 * if a security manager exists and its <code>checkRead</code> 294 * method doesn't allow read access to the file 295 * 296 * @see SecurityManager#checkRead(java.lang.String) 297 * 298 * @since 1.7 299 */ ZipFile(String name, Charset charset)300 public ZipFile(String name, Charset charset) throws IOException 301 { 302 this(new File(name), OPEN_READ, charset); 303 } 304 305 /** 306 * Opens a ZIP file for reading given the specified File object. 307 * @param file the ZIP file to be opened for reading 308 * @param charset 309 * The {@linkplain java.nio.charset.Charset charset} to be 310 * used to decode the ZIP entry name and comment (ignored if 311 * the <a href="package-summary.html#lang_encoding"> language 312 * encoding bit</a> of the ZIP entry's general purpose bit 313 * flag is set). 314 * 315 * @throws ZipException if a ZIP format error has occurred 316 * @throws IOException if an I/O error has occurred 317 * 318 * @since 1.7 319 */ ZipFile(File file, Charset charset)320 public ZipFile(File file, Charset charset) throws IOException 321 { 322 this(file, OPEN_READ, charset); 323 } 324 325 /** 326 * Returns the zip file comment, or null if none. 327 * 328 * @return the comment string for the zip file, or null if none 329 * 330 * @throws IllegalStateException if the zip file has been closed 331 * 332 * Since 1.7 333 */ getComment()334 public String getComment() { 335 synchronized (this) { 336 ensureOpen(); 337 byte[] bcomm = getCommentBytes(jzfile); 338 if (bcomm == null) 339 return null; 340 return zc.toString(bcomm, bcomm.length); 341 } 342 } 343 344 /** 345 * Returns the zip file entry for the specified name, or null 346 * if not found. 347 * 348 * @param name the name of the entry 349 * @return the zip file entry, or null if not found 350 * @throws IllegalStateException if the zip file has been closed 351 */ getEntry(String name)352 public ZipEntry getEntry(String name) { 353 if (name == null) { 354 throw new NullPointerException("name"); 355 } 356 long jzentry = 0; 357 synchronized (this) { 358 ensureOpen(); 359 jzentry = getEntry(jzfile, zc.getBytes(name), true); 360 if (jzentry != 0) { 361 ZipEntry ze = getZipEntry(name, jzentry); 362 freeEntry(jzfile, jzentry); 363 return ze; 364 } 365 } 366 return null; 367 } 368 getEntry(long jzfile, byte[] name, boolean addSlash)369 private static native long getEntry(long jzfile, byte[] name, 370 boolean addSlash); 371 372 // freeEntry releases the C jzentry struct. freeEntry(long jzfile, long jzentry)373 private static native void freeEntry(long jzfile, long jzentry); 374 375 // the outstanding inputstreams that need to be closed, 376 // mapped to the inflater objects they use. 377 private final Map<InputStream, Inflater> streams = new WeakHashMap<>(); 378 379 /** 380 * Returns an input stream for reading the contents of the specified 381 * zip file entry. 382 * 383 * <p> Closing this ZIP file will, in turn, close all input 384 * streams that have been returned by invocations of this method. 385 * 386 * @param entry the zip file entry 387 * @return the input stream for reading the contents of the specified 388 * zip file entry. 389 * @throws ZipException if a ZIP format error has occurred 390 * @throws IOException if an I/O error has occurred 391 * @throws IllegalStateException if the zip file has been closed 392 */ getInputStream(ZipEntry entry)393 public InputStream getInputStream(ZipEntry entry) throws IOException { 394 if (entry == null) { 395 throw new NullPointerException("entry"); 396 } 397 long jzentry = 0; 398 ZipFileInputStream in = null; 399 synchronized (this) { 400 ensureOpen(); 401 if (!zc.isUTF8() && (entry.flag & EFS) != 0) { 402 // Android-changed: Find entry by name, falling back to name/ if cannot be found. 403 // Needed for ClassPathURLStreamHandler handling of URLs without trailing slashes. 404 // This was added as part of the work to move StrictJarFile from libcore to 405 // framework, see http://b/111293098 for more details. 406 // It should be possible to revert this after upgrading to OpenJDK 8u144 or above. 407 // jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false); 408 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), true); 409 } else { 410 // Android-changed: Find entry by name, falling back to name/ if cannot be found. 411 // jzentry = getEntry(jzfile, zc.getBytes(entry.name), false); 412 jzentry = getEntry(jzfile, zc.getBytes(entry.name), true); 413 } 414 if (jzentry == 0) { 415 return null; 416 } 417 in = new ZipFileInputStream(jzentry); 418 419 switch (getEntryMethod(jzentry)) { 420 case STORED: 421 synchronized (streams) { 422 streams.put(in, null); 423 } 424 return in; 425 case DEFLATED: 426 // MORE: Compute good size for inflater stream: 427 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack 428 // Android-changed: Use 64k buffer size, performs better than 8k. 429 // See http://b/65491407. 430 // if (size > 65536) size = 8192; 431 if (size > 65536) size = 65536; 432 if (size <= 0) size = 4096; 433 Inflater inf = getInflater(); 434 InputStream is = 435 new ZipFileInflaterInputStream(in, inf, (int)size); 436 synchronized (streams) { 437 streams.put(is, inf); 438 } 439 return is; 440 default: 441 throw new ZipException("invalid compression method"); 442 } 443 } 444 } 445 446 private class ZipFileInflaterInputStream extends InflaterInputStream { 447 private volatile boolean closeRequested = false; 448 private boolean eof = false; 449 private final ZipFileInputStream zfin; 450 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, int size)451 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, 452 int size) { 453 super(zfin, inf, size); 454 this.zfin = zfin; 455 } 456 close()457 public void close() throws IOException { 458 if (closeRequested) 459 return; 460 closeRequested = true; 461 462 super.close(); 463 Inflater inf; 464 synchronized (streams) { 465 inf = streams.remove(this); 466 } 467 if (inf != null) { 468 releaseInflater(inf); 469 } 470 } 471 472 // Override fill() method to provide an extra "dummy" byte 473 // at the end of the input stream. This is required when 474 // using the "nowrap" Inflater option. fill()475 protected void fill() throws IOException { 476 if (eof) { 477 throw new EOFException("Unexpected end of ZLIB input stream"); 478 } 479 len = in.read(buf, 0, buf.length); 480 if (len == -1) { 481 buf[0] = 0; 482 len = 1; 483 eof = true; 484 } 485 inf.setInput(buf, 0, len); 486 } 487 available()488 public int available() throws IOException { 489 if (closeRequested) 490 return 0; 491 long avail = zfin.size() - inf.getBytesWritten(); 492 return (avail > (long) Integer.MAX_VALUE ? 493 Integer.MAX_VALUE : (int) avail); 494 } 495 finalize()496 protected void finalize() throws Throwable { 497 close(); 498 } 499 } 500 501 /* 502 * Gets an inflater from the list of available inflaters or allocates 503 * a new one. 504 */ getInflater()505 private Inflater getInflater() { 506 Inflater inf; 507 synchronized (inflaterCache) { 508 while (null != (inf = inflaterCache.poll())) { 509 if (false == inf.ended()) { 510 return inf; 511 } 512 } 513 } 514 return new Inflater(true); 515 } 516 517 /* 518 * Releases the specified inflater to the list of available inflaters. 519 */ releaseInflater(Inflater inf)520 private void releaseInflater(Inflater inf) { 521 if (false == inf.ended()) { 522 inf.reset(); 523 synchronized (inflaterCache) { 524 inflaterCache.add(inf); 525 } 526 } 527 } 528 529 // List of available Inflater objects for decompression 530 private Deque<Inflater> inflaterCache = new ArrayDeque<>(); 531 532 /** 533 * Returns the path name of the ZIP file. 534 * @return the path name of the ZIP file 535 */ getName()536 public String getName() { 537 return name; 538 } 539 540 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { 541 private int i = 0; 542 ZipEntryIterator()543 public ZipEntryIterator() { 544 ensureOpen(); 545 } 546 hasMoreElements()547 public boolean hasMoreElements() { 548 return hasNext(); 549 } 550 hasNext()551 public boolean hasNext() { 552 synchronized (ZipFile.this) { 553 ensureOpen(); 554 return i < total; 555 } 556 } 557 nextElement()558 public ZipEntry nextElement() { 559 return next(); 560 } 561 next()562 public ZipEntry next() { 563 synchronized (ZipFile.this) { 564 ensureOpen(); 565 if (i >= total) { 566 throw new NoSuchElementException(); 567 } 568 long jzentry = getNextEntry(jzfile, i++); 569 if (jzentry == 0) { 570 String message; 571 if (closeRequested) { 572 message = "ZipFile concurrently closed"; 573 } else { 574 message = getZipMessage(ZipFile.this.jzfile); 575 } 576 throw new ZipError("jzentry == 0" + 577 ",\n jzfile = " + ZipFile.this.jzfile + 578 ",\n total = " + ZipFile.this.total + 579 ",\n name = " + ZipFile.this.name + 580 ",\n i = " + i + 581 ",\n message = " + message 582 ); 583 } 584 ZipEntry ze = getZipEntry(null, jzentry); 585 freeEntry(jzfile, jzentry); 586 return ze; 587 } 588 } 589 } 590 591 /** 592 * Returns an enumeration of the ZIP file entries. 593 * @return an enumeration of the ZIP file entries 594 * @throws IllegalStateException if the zip file has been closed 595 */ entries()596 public Enumeration<? extends ZipEntry> entries() { 597 return new ZipEntryIterator(); 598 } 599 600 /** 601 * Return an ordered {@code Stream} over the ZIP file entries. 602 * Entries appear in the {@code Stream} in the order they appear in 603 * the central directory of the ZIP file. 604 * 605 * @return an ordered {@code Stream} of entries in this ZIP file 606 * @throws IllegalStateException if the zip file has been closed 607 * @since 1.8 608 */ stream()609 public Stream<? extends ZipEntry> stream() { 610 return StreamSupport.stream(Spliterators.spliterator( 611 new ZipEntryIterator(), size(), 612 Spliterator.ORDERED | Spliterator.DISTINCT | 613 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 614 } 615 getZipEntry(String name, long jzentry)616 private ZipEntry getZipEntry(String name, long jzentry) { 617 ZipEntry e = new ZipEntry(); 618 e.flag = getEntryFlag(jzentry); // get the flag first 619 if (name != null) { 620 e.name = name; 621 } else { 622 byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME); 623 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 624 e.name = zc.toStringUTF8(bname, bname.length); 625 } else { 626 e.name = zc.toString(bname, bname.length); 627 } 628 } 629 e.xdostime = getEntryTime(jzentry); 630 e.crc = getEntryCrc(jzentry); 631 e.size = getEntrySize(jzentry); 632 e.csize = getEntryCSize(jzentry); 633 e.method = getEntryMethod(jzentry); 634 e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false); 635 byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); 636 if (bcomm == null) { 637 e.comment = null; 638 } else { 639 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 640 e.comment = zc.toStringUTF8(bcomm, bcomm.length); 641 } else { 642 e.comment = zc.toString(bcomm, bcomm.length); 643 } 644 } 645 return e; 646 } 647 getNextEntry(long jzfile, int i)648 private static native long getNextEntry(long jzfile, int i); 649 650 /** 651 * Returns the number of entries in the ZIP file. 652 * @return the number of entries in the ZIP file 653 * @throws IllegalStateException if the zip file has been closed 654 */ size()655 public int size() { 656 ensureOpen(); 657 return total; 658 } 659 660 /** 661 * Closes the ZIP file. 662 * <p> Closing this ZIP file will close all of the input streams 663 * previously returned by invocations of the {@link #getInputStream 664 * getInputStream} method. 665 * 666 * @throws IOException if an I/O error has occurred 667 */ close()668 public void close() throws IOException { 669 if (closeRequested) 670 return; 671 // Android-added: CloseGuard support. 672 if (guard != null) { 673 guard.close(); 674 } 675 closeRequested = true; 676 677 synchronized (this) { 678 // Close streams, release their inflaters 679 // BEGIN Android-added: null field check to avoid NullPointerException during finalize. 680 // If the constructor threw an exception then the streams / inflaterCache fields can 681 // be null and close() can be called by the finalizer. 682 if (streams != null) { 683 // END Android-added: null field check to avoid NullPointerException during finalize. 684 synchronized (streams) { 685 if (false == streams.isEmpty()) { 686 Map<InputStream, Inflater> copy = new HashMap<>(streams); 687 streams.clear(); 688 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { 689 e.getKey().close(); 690 Inflater inf = e.getValue(); 691 if (inf != null) { 692 inf.end(); 693 } 694 } 695 } 696 } 697 // BEGIN Android-added: null field check to avoid NullPointerException during finalize. 698 } 699 700 if (inflaterCache != null) { 701 // END Android-added: null field check to avoid NullPointerException during finalize. 702 // Release cached inflaters 703 Inflater inf; 704 synchronized (inflaterCache) { 705 while (null != (inf = inflaterCache.poll())) { 706 inf.end(); 707 } 708 } 709 // BEGIN Android-added: null field check to avoid NullPointerException during finalize. 710 } 711 // END Android-added: null field check to avoid NullPointerException during finalize. 712 713 if (jzfile != 0) { 714 // Close the zip file 715 long zf = this.jzfile; 716 jzfile = 0; 717 718 close(zf); 719 } 720 // Android-added: Do not use unlink() to implement OPEN_DELETE. 721 if (fileToRemoveOnClose != null) { 722 fileToRemoveOnClose.delete(); 723 } 724 } 725 } 726 727 /** 728 * Ensures that the system resources held by this ZipFile object are 729 * released when there are no more references to it. 730 * 731 * <p> 732 * Since the time when GC would invoke this method is undetermined, 733 * it is strongly recommended that applications invoke the <code>close</code> 734 * method as soon they have finished accessing this <code>ZipFile</code>. 735 * This will prevent holding up system resources for an undetermined 736 * length of time. 737 * 738 * @throws IOException if an I/O error has occurred 739 * @see java.util.zip.ZipFile#close() 740 */ finalize()741 protected void finalize() throws IOException { 742 // Android-added: CloseGuard support. 743 if (guard != null) { 744 guard.warnIfOpen(); 745 } 746 close(); 747 } 748 close(long jzfile)749 private static native void close(long jzfile); 750 ensureOpen()751 private void ensureOpen() { 752 if (closeRequested) { 753 throw new IllegalStateException("zip file closed"); 754 } 755 756 if (jzfile == 0) { 757 throw new IllegalStateException("The object is not initialized."); 758 } 759 } 760 ensureOpenOrZipException()761 private void ensureOpenOrZipException() throws IOException { 762 if (closeRequested) { 763 throw new ZipException("ZipFile closed"); 764 } 765 } 766 767 /* 768 * Inner class implementing the input stream used to read a 769 * (possibly compressed) zip file entry. 770 */ 771 private class ZipFileInputStream extends InputStream { 772 private volatile boolean zfisCloseRequested = false; 773 protected long jzentry; // address of jzentry data 774 private long pos; // current position within entry data 775 protected long rem; // number of remaining bytes within entry 776 protected long size; // uncompressed size of this entry 777 ZipFileInputStream(long jzentry)778 ZipFileInputStream(long jzentry) { 779 pos = 0; 780 rem = getEntryCSize(jzentry); 781 size = getEntrySize(jzentry); 782 this.jzentry = jzentry; 783 } 784 read(byte b[], int off, int len)785 public int read(byte b[], int off, int len) throws IOException { 786 // Android-added: Always throw an exception when reading from closed zipfile. 787 // Required by the JavaDoc for InputStream.read(byte[], int, int). Upstream version 788 // 8u121-b13 is not compliant but that bug has been fixed in upstream version 9+181 789 // as part of a major change to switch to a pure Java implementation. 790 // See https://bugs.openjdk.java.net/browse/JDK-8145260 and 791 // https://bugs.openjdk.java.net/browse/JDK-8142508. 792 ensureOpenOrZipException(); 793 794 synchronized (ZipFile.this) { 795 long rem = this.rem; 796 long pos = this.pos; 797 if (rem == 0) { 798 return -1; 799 } 800 if (len <= 0) { 801 return 0; 802 } 803 if (len > rem) { 804 len = (int) rem; 805 } 806 807 // Android-removed: Always throw an exception when reading from closed zipfile. 808 // Moved to the start of the method. 809 //ensureOpenOrZipException(); 810 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b, 811 off, len); 812 if (len > 0) { 813 this.pos = (pos + len); 814 this.rem = (rem - len); 815 } 816 } 817 if (rem == 0) { 818 close(); 819 } 820 return len; 821 } 822 read()823 public int read() throws IOException { 824 byte[] b = new byte[1]; 825 if (read(b, 0, 1) == 1) { 826 return b[0] & 0xff; 827 } else { 828 return -1; 829 } 830 } 831 skip(long n)832 public long skip(long n) { 833 if (n > rem) 834 n = rem; 835 pos += n; 836 rem -= n; 837 if (rem == 0) { 838 close(); 839 } 840 return n; 841 } 842 available()843 public int available() { 844 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 845 } 846 size()847 public long size() { 848 return size; 849 } 850 close()851 public void close() { 852 if (zfisCloseRequested) 853 return; 854 zfisCloseRequested = true; 855 856 rem = 0; 857 synchronized (ZipFile.this) { 858 if (jzentry != 0 && ZipFile.this.jzfile != 0) { 859 freeEntry(ZipFile.this.jzfile, jzentry); 860 jzentry = 0; 861 } 862 } 863 synchronized (streams) { 864 streams.remove(this); 865 } 866 } 867 finalize()868 protected void finalize() { 869 close(); 870 } 871 } 872 873 // Android-removed: Access startsWithLocHeader() directly. 874 /* 875 static { 876 sun.misc.SharedSecrets.setJavaUtilZipFileAccess( 877 new sun.misc.JavaUtilZipFileAccess() { 878 public boolean startsWithLocHeader(ZipFile zip) { 879 return zip.startsWithLocHeader(); 880 } 881 } 882 ); 883 } 884 */ 885 886 /** 887 * Returns {@code true} if, and only if, the zip file begins with {@code 888 * LOCSIG}. 889 * @hide 890 */ 891 // Android-changed: Access startsWithLocHeader() directly. 892 // Make hidden public for use by sun.misc.URLClassPath 893 // private boolean startsWithLocHeader() { startsWithLocHeader()894 public boolean startsWithLocHeader() { 895 return locsig; 896 } 897 898 // BEGIN Android-added: Provide access to underlying file descriptor for testing. 899 // See http://b/111148957 for background information. 900 /** @hide */ 901 // @VisibleForTesting getFileDescriptor()902 public int getFileDescriptor() { 903 return getFileDescriptor(jzfile); 904 } 905 getFileDescriptor(long jzfile)906 private static native int getFileDescriptor(long jzfile); 907 // END Android-added: Provide access to underlying file descriptor for testing. 908 open(String name, int mode, long lastModified, boolean usemmap)909 private static native long open(String name, int mode, long lastModified, 910 boolean usemmap) throws IOException; getTotal(long jzfile)911 private static native int getTotal(long jzfile); startsWithLOC(long jzfile)912 private static native boolean startsWithLOC(long jzfile); read(long jzfile, long jzentry, long pos, byte[] b, int off, int len)913 private static native int read(long jzfile, long jzentry, 914 long pos, byte[] b, int off, int len); 915 916 // access to the native zentry object getEntryTime(long jzentry)917 private static native long getEntryTime(long jzentry); getEntryCrc(long jzentry)918 private static native long getEntryCrc(long jzentry); getEntryCSize(long jzentry)919 private static native long getEntryCSize(long jzentry); getEntrySize(long jzentry)920 private static native long getEntrySize(long jzentry); getEntryMethod(long jzentry)921 private static native int getEntryMethod(long jzentry); getEntryFlag(long jzentry)922 private static native int getEntryFlag(long jzentry); getCommentBytes(long jzfile)923 private static native byte[] getCommentBytes(long jzfile); 924 925 private static final int JZENTRY_NAME = 0; 926 private static final int JZENTRY_EXTRA = 1; 927 private static final int JZENTRY_COMMENT = 2; getEntryBytes(long jzentry, int type)928 private static native byte[] getEntryBytes(long jzentry, int type); 929 getZipMessage(long jzfile)930 private static native String getZipMessage(long jzfile); 931 } 932