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 private long jzfile; // address of jzfile data 66 private final String name; // zip file name 67 private final int total; // total number of entries 68 private final boolean locsig; // if zip file starts with LOCSIG (usually true) 69 private volatile boolean closeRequested = false; 70 71 private final CloseGuard guard = CloseGuard.get(); 72 73 // Android-changed, needed for alternative OPEN_DELETE implementation 74 // that doesn't use unlink before closing the file. 75 private final File fileToRemoveOnClose; 76 77 private static final int STORED = ZipEntry.STORED; 78 private static final int DEFLATED = ZipEntry.DEFLATED; 79 80 /** 81 * Mode flag to open a zip file for reading. 82 */ 83 public static final int OPEN_READ = 0x1; 84 85 /** 86 * Mode flag to open a zip file and mark it for deletion. The file will be 87 * deleted some time between the moment that it is opened and the moment 88 * that it is closed, but its contents will remain accessible via the 89 * <tt>ZipFile</tt> object until either the close method is invoked or the 90 * virtual machine exits. 91 */ 92 public static final int OPEN_DELETE = 0x4; 93 94 private static final boolean usemmap; 95 96 static { 97 // Android-changed: always use mmap. 98 usemmap = true; 99 } 100 101 /** 102 * Opens a zip file for reading. 103 * 104 * <p>First, if there is a security manager, its <code>checkRead</code> 105 * method is called with the <code>name</code> argument as its argument 106 * to ensure the read is allowed. 107 * 108 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 109 * decode the entry names and comments. 110 * 111 * @param name the name of the zip file 112 * @throws ZipException if a ZIP format error has occurred 113 * @throws IOException if an I/O error has occurred 114 * @throws SecurityException if a security manager exists and its 115 * <code>checkRead</code> method doesn't allow read access to the file. 116 * 117 * @see SecurityManager#checkRead(java.lang.String) 118 */ ZipFile(String name)119 public ZipFile(String name) throws IOException { 120 this(new File(name), OPEN_READ); 121 } 122 123 /** 124 * Opens a new <code>ZipFile</code> to read from the specified 125 * <code>File</code> object in the specified mode. The mode argument 126 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 127 * 128 * <p>First, if there is a security manager, its <code>checkRead</code> 129 * method is called with the <code>name</code> argument as its argument to 130 * ensure the read is allowed. 131 * 132 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 133 * decode the entry names and comments 134 * 135 * @param file the ZIP file to be opened for reading 136 * @param mode the mode in which the file is to be opened 137 * @throws ZipException if a ZIP format error has occurred 138 * @throws IOException if an I/O error has occurred 139 * @throws SecurityException if a security manager exists and 140 * its <code>checkRead</code> method 141 * doesn't allow read access to the file, 142 * or its <code>checkDelete</code> method doesn't allow deleting 143 * the file when the <tt>OPEN_DELETE</tt> flag is set. 144 * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid 145 * @see SecurityManager#checkRead(java.lang.String) 146 * @since 1.3 147 */ ZipFile(File file, int mode)148 public ZipFile(File file, int mode) throws IOException { 149 this(file, mode, StandardCharsets.UTF_8); 150 } 151 152 /** 153 * Opens a ZIP file for reading given the specified File object. 154 * 155 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 156 * decode the entry names and comments. 157 * 158 * @param file the ZIP file to be opened for reading 159 * @throws ZipException if a ZIP format error has occurred 160 * @throws IOException if an I/O error has occurred 161 */ ZipFile(File file)162 public ZipFile(File file) throws ZipException, IOException { 163 this(file, OPEN_READ); 164 } 165 166 private ZipCoder zc; 167 168 /** 169 * Opens a new <code>ZipFile</code> to read from the specified 170 * <code>File</code> object in the specified mode. The mode argument 171 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 172 * 173 * <p>First, if there is a security manager, its <code>checkRead</code> 174 * method is called with the <code>name</code> argument as its argument to 175 * ensure the read is allowed. 176 * 177 * @param file the ZIP file to be opened for reading 178 * @param mode the mode in which the file is to be opened 179 * @param charset 180 * the {@linkplain java.nio.charset.Charset charset} to 181 * be used to decode the ZIP entry name and comment that are not 182 * encoded by using UTF-8 encoding (indicated by entry's general 183 * purpose flag). 184 * 185 * @throws ZipException if a ZIP format error has occurred 186 * @throws IOException if an I/O error has occurred 187 * 188 * @throws SecurityException 189 * if a security manager exists and its <code>checkRead</code> 190 * method doesn't allow read access to the file,or its 191 * <code>checkDelete</code> method doesn't allow deleting the 192 * file when the <tt>OPEN_DELETE</tt> flag is set 193 * 194 * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid 195 * 196 * @see SecurityManager#checkRead(java.lang.String) 197 * 198 * @since 1.7 199 */ ZipFile(File file, int mode, Charset charset)200 public ZipFile(File file, int mode, Charset charset) throws IOException 201 { 202 if (((mode & OPEN_READ) == 0) || 203 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { 204 throw new IllegalArgumentException("Illegal mode: 0x"+ 205 Integer.toHexString(mode)); 206 } 207 208 // Android-changed: Error out early if the file is too short or non-existent. 209 long length = file.length(); 210 if (length < ZipConstants.ENDHDR) { 211 if (length == 0 && !file.exists()) { 212 throw new FileNotFoundException("File doesn't exist: " + file); 213 } else { 214 throw new ZipException("File too short to be a zip file: " + file.length()); 215 } 216 } 217 218 // Android-changed, handle OPEN_DELETE case in #close(). 219 fileToRemoveOnClose = ((mode & OPEN_DELETE) != 0) ? file : null; 220 221 String name = file.getPath(); 222 // Android-changed: SecurityManager is always null 223 // SecurityManager sm = System.getSecurityManager(); 224 // if (sm != null) { 225 // sm.checkRead(name); 226 // if ((mode & OPEN_DELETE) != 0) { 227 // sm.checkDelete(name); 228 // } 229 // } 230 if (charset == null) 231 throw new NullPointerException("charset is null"); 232 this.zc = ZipCoder.get(charset); 233 // Android-changed: Skip perf counters 234 // long t0 = System.nanoTime(); 235 jzfile = open(name, mode, file.lastModified(), usemmap); 236 // Android-changed: Skip perf counters 237 // sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); 238 // sun.misc.PerfCounter.getZipFileCount().increment(); 239 this.name = name; 240 this.total = getTotal(jzfile); 241 this.locsig = startsWithLOC(jzfile); 242 Enumeration<? extends ZipEntry> entries = entries(); 243 244 // Android-changed: Error out early if the zipfile has no entries. 245 if (size() == 0 || !entries.hasMoreElements()) { 246 close(); 247 throw new ZipException("No entries"); 248 } 249 250 guard.open("close"); 251 } 252 253 /** 254 * Opens a zip file for reading. 255 * 256 * <p>First, if there is a security manager, its <code>checkRead</code> 257 * method is called with the <code>name</code> argument as its argument 258 * to ensure the read is allowed. 259 * 260 * @param name the name of the zip file 261 * @param charset 262 * the {@linkplain java.nio.charset.Charset charset} to 263 * be used to decode the ZIP entry name and comment that are not 264 * encoded by using UTF-8 encoding (indicated by entry's general 265 * purpose flag). 266 * 267 * @throws ZipException if a ZIP format error has occurred 268 * @throws IOException if an I/O error has occurred 269 * @throws SecurityException 270 * if a security manager exists and its <code>checkRead</code> 271 * method doesn't allow read access to the file 272 * 273 * @see SecurityManager#checkRead(java.lang.String) 274 * 275 * @since 1.7 276 */ ZipFile(String name, Charset charset)277 public ZipFile(String name, Charset charset) throws IOException 278 { 279 this(new File(name), OPEN_READ, charset); 280 } 281 282 /** 283 * Opens a ZIP file for reading given the specified File object. 284 * @param file the ZIP file to be opened for reading 285 * @param charset 286 * The {@linkplain java.nio.charset.Charset charset} to be 287 * used to decode the ZIP entry name and comment (ignored if 288 * the <a href="package-summary.html#lang_encoding"> language 289 * encoding bit</a> of the ZIP entry's general purpose bit 290 * flag is set). 291 * 292 * @throws ZipException if a ZIP format error has occurred 293 * @throws IOException if an I/O error has occurred 294 * 295 * @since 1.7 296 */ ZipFile(File file, Charset charset)297 public ZipFile(File file, Charset charset) throws IOException 298 { 299 this(file, OPEN_READ, charset); 300 } 301 302 /** 303 * Returns the zip file comment, or null if none. 304 * 305 * @return the comment string for the zip file, or null if none 306 * 307 * @throws IllegalStateException if the zip file has been closed 308 * 309 * Since 1.7 310 */ getComment()311 public String getComment() { 312 synchronized (this) { 313 ensureOpen(); 314 byte[] bcomm = getCommentBytes(jzfile); 315 if (bcomm == null) 316 return null; 317 return zc.toString(bcomm, bcomm.length); 318 } 319 } 320 321 /** 322 * Returns the zip file entry for the specified name, or null 323 * if not found. 324 * 325 * @param name the name of the entry 326 * @return the zip file entry, or null if not found 327 * @throws IllegalStateException if the zip file has been closed 328 */ getEntry(String name)329 public ZipEntry getEntry(String name) { 330 if (name == null) { 331 throw new NullPointerException("name"); 332 } 333 long jzentry = 0; 334 synchronized (this) { 335 ensureOpen(); 336 jzentry = getEntry(jzfile, zc.getBytes(name), true); 337 if (jzentry != 0) { 338 ZipEntry ze = getZipEntry(name, jzentry); 339 freeEntry(jzfile, jzentry); 340 return ze; 341 } 342 } 343 return null; 344 } 345 getEntry(long jzfile, byte[] name, boolean addSlash)346 private static native long getEntry(long jzfile, byte[] name, 347 boolean addSlash); 348 349 // freeEntry releases the C jzentry struct. freeEntry(long jzfile, long jzentry)350 private static native void freeEntry(long jzfile, long jzentry); 351 352 // the outstanding inputstreams that need to be closed, 353 // mapped to the inflater objects they use. 354 private final Map<InputStream, Inflater> streams = new WeakHashMap<>(); 355 356 /** 357 * Returns an input stream for reading the contents of the specified 358 * zip file entry. 359 * 360 * <p> Closing this ZIP file will, in turn, close all input 361 * streams that have been returned by invocations of this method. 362 * 363 * @param entry the zip file entry 364 * @return the input stream for reading the contents of the specified 365 * zip file entry. 366 * @throws ZipException if a ZIP format error has occurred 367 * @throws IOException if an I/O error has occurred 368 * @throws IllegalStateException if the zip file has been closed 369 */ getInputStream(ZipEntry entry)370 public InputStream getInputStream(ZipEntry entry) throws IOException { 371 if (entry == null) { 372 throw new NullPointerException("entry"); 373 } 374 long jzentry = 0; 375 ZipFileInputStream in = null; 376 synchronized (this) { 377 ensureOpen(); 378 if (!zc.isUTF8() && (entry.flag & EFS) != 0) { 379 // Android-changed: addSlash set to true, android is fine with "/" at the end 380 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), true); 381 } else { 382 // Android-changed: addSlash set to true, android is fine with "/" at the end 383 jzentry = getEntry(jzfile, zc.getBytes(entry.name), true); 384 } 385 if (jzentry == 0) { 386 return null; 387 } 388 in = new ZipFileInputStream(jzentry); 389 390 switch (getEntryMethod(jzentry)) { 391 case STORED: 392 synchronized (streams) { 393 streams.put(in, null); 394 } 395 return in; 396 case DEFLATED: 397 // MORE: Compute good size for inflater stream: 398 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack 399 if (size > 65536) size = 8192; 400 if (size <= 0) size = 4096; 401 Inflater inf = getInflater(); 402 InputStream is = 403 new ZipFileInflaterInputStream(in, inf, (int)size); 404 synchronized (streams) { 405 streams.put(is, inf); 406 } 407 return is; 408 default: 409 throw new ZipException("invalid compression method"); 410 } 411 } 412 } 413 414 private class ZipFileInflaterInputStream extends InflaterInputStream { 415 private volatile boolean closeRequested = false; 416 private boolean eof = false; 417 private final ZipFileInputStream zfin; 418 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, int size)419 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, 420 int size) { 421 super(zfin, inf, size); 422 this.zfin = zfin; 423 } 424 close()425 public void close() throws IOException { 426 if (closeRequested) 427 return; 428 closeRequested = true; 429 430 super.close(); 431 Inflater inf; 432 synchronized (streams) { 433 inf = streams.remove(this); 434 } 435 if (inf != null) { 436 releaseInflater(inf); 437 } 438 } 439 440 // Override fill() method to provide an extra "dummy" byte 441 // at the end of the input stream. This is required when 442 // using the "nowrap" Inflater option. fill()443 protected void fill() throws IOException { 444 if (eof) { 445 throw new EOFException("Unexpected end of ZLIB input stream"); 446 } 447 len = in.read(buf, 0, buf.length); 448 if (len == -1) { 449 buf[0] = 0; 450 len = 1; 451 eof = true; 452 } 453 inf.setInput(buf, 0, len); 454 } 455 available()456 public int available() throws IOException { 457 if (closeRequested) 458 return 0; 459 long avail = zfin.size() - inf.getBytesWritten(); 460 return (avail > (long) Integer.MAX_VALUE ? 461 Integer.MAX_VALUE : (int) avail); 462 } 463 finalize()464 protected void finalize() throws Throwable { 465 close(); 466 } 467 } 468 469 /* 470 * Gets an inflater from the list of available inflaters or allocates 471 * a new one. 472 */ getInflater()473 private Inflater getInflater() { 474 Inflater inf; 475 synchronized (inflaterCache) { 476 while (null != (inf = inflaterCache.poll())) { 477 if (false == inf.ended()) { 478 return inf; 479 } 480 } 481 } 482 return new Inflater(true); 483 } 484 485 /* 486 * Releases the specified inflater to the list of available inflaters. 487 */ releaseInflater(Inflater inf)488 private void releaseInflater(Inflater inf) { 489 if (false == inf.ended()) { 490 inf.reset(); 491 synchronized (inflaterCache) { 492 inflaterCache.add(inf); 493 } 494 } 495 } 496 497 // List of available Inflater objects for decompression 498 private Deque<Inflater> inflaterCache = new ArrayDeque<>(); 499 500 /** 501 * Returns the path name of the ZIP file. 502 * @return the path name of the ZIP file 503 */ getName()504 public String getName() { 505 return name; 506 } 507 508 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { 509 private int i = 0; 510 ZipEntryIterator()511 public ZipEntryIterator() { 512 ensureOpen(); 513 } 514 hasMoreElements()515 public boolean hasMoreElements() { 516 return hasNext(); 517 } 518 hasNext()519 public boolean hasNext() { 520 synchronized (ZipFile.this) { 521 ensureOpen(); 522 return i < total; 523 } 524 } 525 nextElement()526 public ZipEntry nextElement() { 527 return next(); 528 } 529 next()530 public ZipEntry next() { 531 synchronized (ZipFile.this) { 532 ensureOpen(); 533 if (i >= total) { 534 throw new NoSuchElementException(); 535 } 536 long jzentry = getNextEntry(jzfile, i++); 537 if (jzentry == 0) { 538 String message; 539 if (closeRequested) { 540 message = "ZipFile concurrently closed"; 541 } else { 542 message = getZipMessage(ZipFile.this.jzfile); 543 } 544 throw new ZipError("jzentry == 0" + 545 ",\n jzfile = " + ZipFile.this.jzfile + 546 ",\n total = " + ZipFile.this.total + 547 ",\n name = " + ZipFile.this.name + 548 ",\n i = " + i + 549 ",\n message = " + message 550 ); 551 } 552 ZipEntry ze = getZipEntry(null, jzentry); 553 freeEntry(jzfile, jzentry); 554 return ze; 555 } 556 } 557 } 558 559 /** 560 * Returns an enumeration of the ZIP file entries. 561 * @return an enumeration of the ZIP file entries 562 * @throws IllegalStateException if the zip file has been closed 563 */ entries()564 public Enumeration<? extends ZipEntry> entries() { 565 return new ZipEntryIterator(); 566 } 567 568 /** 569 * Return an ordered {@code Stream} over the ZIP file entries. 570 * Entries appear in the {@code Stream} in the order they appear in 571 * the central directory of the ZIP file. 572 * 573 * @return an ordered {@code Stream} of entries in this ZIP file 574 * @throws IllegalStateException if the zip file has been closed 575 * @since 1.8 576 */ stream()577 public Stream<? extends ZipEntry> stream() { 578 return StreamSupport.stream(Spliterators.spliterator( 579 new ZipEntryIterator(), size(), 580 Spliterator.ORDERED | Spliterator.DISTINCT | 581 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 582 } 583 getZipEntry(String name, long jzentry)584 private ZipEntry getZipEntry(String name, long jzentry) { 585 ZipEntry e = new ZipEntry(); 586 e.flag = getEntryFlag(jzentry); // get the flag first 587 if (name != null) { 588 e.name = name; 589 } else { 590 byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME); 591 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 592 e.name = zc.toStringUTF8(bname, bname.length); 593 } else { 594 e.name = zc.toString(bname, bname.length); 595 } 596 } 597 e.xdostime = getEntryTime(jzentry); 598 e.crc = getEntryCrc(jzentry); 599 e.size = getEntrySize(jzentry); 600 e.csize = getEntryCSize(jzentry); 601 e.method = getEntryMethod(jzentry); 602 e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false); 603 byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); 604 if (bcomm == null) { 605 e.comment = null; 606 } else { 607 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 608 e.comment = zc.toStringUTF8(bcomm, bcomm.length); 609 } else { 610 e.comment = zc.toString(bcomm, bcomm.length); 611 } 612 } 613 return e; 614 } 615 getNextEntry(long jzfile, int i)616 private static native long getNextEntry(long jzfile, int i); 617 618 /** 619 * Returns the number of entries in the ZIP file. 620 * @return the number of entries in the ZIP file 621 * @throws IllegalStateException if the zip file has been closed 622 */ size()623 public int size() { 624 ensureOpen(); 625 return total; 626 } 627 628 /** 629 * Closes the ZIP file. 630 * <p> Closing this ZIP file will close all of the input streams 631 * previously returned by invocations of the {@link #getInputStream 632 * getInputStream} method. 633 * 634 * @throws IOException if an I/O error has occurred 635 */ close()636 public void close() throws IOException { 637 if (closeRequested) 638 return; 639 guard.close(); 640 closeRequested = true; 641 642 synchronized (this) { 643 // Close streams, release their inflaters 644 synchronized (streams) { 645 if (false == streams.isEmpty()) { 646 Map<InputStream, Inflater> copy = new HashMap<>(streams); 647 streams.clear(); 648 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { 649 e.getKey().close(); 650 Inflater inf = e.getValue(); 651 if (inf != null) { 652 inf.end(); 653 } 654 } 655 } 656 } 657 658 // Release cached inflaters 659 Inflater inf; 660 synchronized (inflaterCache) { 661 while (null != (inf = inflaterCache.poll())) { 662 inf.end(); 663 } 664 } 665 666 if (jzfile != 0) { 667 // Close the zip file 668 long zf = this.jzfile; 669 jzfile = 0; 670 671 close(zf); 672 } 673 // Android-changed, explicit delete for OPEN_DELETE ZipFile. 674 if (fileToRemoveOnClose != null) { 675 fileToRemoveOnClose.delete(); 676 } 677 } 678 } 679 680 /** 681 * Ensures that the system resources held by this ZipFile object are 682 * released when there are no more references to it. 683 * 684 * <p> 685 * Since the time when GC would invoke this method is undetermined, 686 * it is strongly recommended that applications invoke the <code>close</code> 687 * method as soon they have finished accessing this <code>ZipFile</code>. 688 * This will prevent holding up system resources for an undetermined 689 * length of time. 690 * 691 * @throws IOException if an I/O error has occurred 692 * @see java.util.zip.ZipFile#close() 693 */ finalize()694 protected void finalize() throws IOException { 695 if (guard != null) { 696 guard.warnIfOpen(); 697 } 698 699 close(); 700 } 701 close(long jzfile)702 private static native void close(long jzfile); 703 ensureOpen()704 private void ensureOpen() { 705 if (closeRequested) { 706 throw new IllegalStateException("zip file closed"); 707 } 708 709 if (jzfile == 0) { 710 throw new IllegalStateException("The object is not initialized."); 711 } 712 } 713 ensureOpenOrZipException()714 private void ensureOpenOrZipException() throws IOException { 715 if (closeRequested) { 716 throw new ZipException("ZipFile closed"); 717 } 718 } 719 720 /* 721 * Inner class implementing the input stream used to read a 722 * (possibly compressed) zip file entry. 723 */ 724 private class ZipFileInputStream extends InputStream { 725 private volatile boolean zfisCloseRequested = false; 726 protected long jzentry; // address of jzentry data 727 private long pos; // current position within entry data 728 protected long rem; // number of remaining bytes within entry 729 protected long size; // uncompressed size of this entry 730 ZipFileInputStream(long jzentry)731 ZipFileInputStream(long jzentry) { 732 pos = 0; 733 rem = getEntryCSize(jzentry); 734 size = getEntrySize(jzentry); 735 this.jzentry = jzentry; 736 } 737 read(byte b[], int off, int len)738 public int read(byte b[], int off, int len) throws IOException { 739 // Android-changed: Always throw an exception on read if the zipfile 740 // has already been closed. 741 ensureOpenOrZipException(); 742 743 synchronized (ZipFile.this) { 744 long rem = this.rem; 745 long pos = this.pos; 746 if (rem == 0) { 747 return -1; 748 } 749 if (len <= 0) { 750 return 0; 751 } 752 if (len > rem) { 753 len = (int) rem; 754 } 755 756 // Android-changed: Moved 757 //ensureOpenOrZipException(); 758 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b, 759 off, len); 760 if (len > 0) { 761 this.pos = (pos + len); 762 this.rem = (rem - len); 763 } 764 } 765 if (rem == 0) { 766 close(); 767 } 768 return len; 769 } 770 read()771 public int read() throws IOException { 772 byte[] b = new byte[1]; 773 if (read(b, 0, 1) == 1) { 774 return b[0] & 0xff; 775 } else { 776 return -1; 777 } 778 } 779 skip(long n)780 public long skip(long n) { 781 if (n > rem) 782 n = rem; 783 pos += n; 784 rem -= n; 785 if (rem == 0) { 786 close(); 787 } 788 return n; 789 } 790 available()791 public int available() { 792 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 793 } 794 size()795 public long size() { 796 return size; 797 } 798 close()799 public void close() { 800 if (zfisCloseRequested) 801 return; 802 zfisCloseRequested = true; 803 804 rem = 0; 805 synchronized (ZipFile.this) { 806 if (jzentry != 0 && ZipFile.this.jzfile != 0) { 807 freeEntry(ZipFile.this.jzfile, jzentry); 808 jzentry = 0; 809 } 810 } 811 synchronized (streams) { 812 streams.remove(this); 813 } 814 } 815 finalize()816 protected void finalize() { 817 close(); 818 } 819 } 820 821 /** 822 * Returns {@code true} if, and only if, the zip file begins with {@code 823 * LOCSIG}. 824 * @hide 825 */ startsWithLocHeader()826 public boolean startsWithLocHeader() { 827 return locsig; 828 } 829 830 /** @hide */ 831 // @VisibleForTesting getFileDescriptor()832 public int getFileDescriptor() { 833 return getFileDescriptor(jzfile); 834 } 835 getFileDescriptor(long jzfile)836 private static native int getFileDescriptor(long jzfile); 837 open(String name, int mode, long lastModified, boolean usemmap)838 private static native long open(String name, int mode, long lastModified, 839 boolean usemmap) throws IOException; getTotal(long jzfile)840 private static native int getTotal(long jzfile); startsWithLOC(long jzfile)841 private static native boolean startsWithLOC(long jzfile); read(long jzfile, long jzentry, long pos, byte[] b, int off, int len)842 private static native int read(long jzfile, long jzentry, 843 long pos, byte[] b, int off, int len); 844 845 // access to the native zentry object getEntryTime(long jzentry)846 private static native long getEntryTime(long jzentry); getEntryCrc(long jzentry)847 private static native long getEntryCrc(long jzentry); getEntryCSize(long jzentry)848 private static native long getEntryCSize(long jzentry); getEntrySize(long jzentry)849 private static native long getEntrySize(long jzentry); getEntryMethod(long jzentry)850 private static native int getEntryMethod(long jzentry); getEntryFlag(long jzentry)851 private static native int getEntryFlag(long jzentry); getCommentBytes(long jzfile)852 private static native byte[] getCommentBytes(long jzfile); 853 854 private static final int JZENTRY_NAME = 0; 855 private static final int JZENTRY_EXTRA = 1; 856 private static final int JZENTRY_COMMENT = 2; getEntryBytes(long jzentry, int type)857 private static native byte[] getEntryBytes(long jzentry, int type); 858 getZipMessage(long jzfile)859 private static native String getZipMessage(long jzfile); 860 } 861