1 /* 2 * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.zip; 27 28 import java.io.Closeable; 29 import java.io.InputStream; 30 import java.io.IOException; 31 import java.io.EOFException; 32 import java.io.File; 33 import java.nio.ByteBuffer; 34 import java.nio.ByteOrder; 35 import java.nio.IntBuffer; 36 import java.nio.charset.Charset; 37 import java.nio.charset.StandardCharsets; 38 import java.util.ArrayDeque; 39 import java.util.Deque; 40 import java.util.Enumeration; 41 import java.util.HashMap; 42 import java.util.Iterator; 43 import java.util.Map; 44 import java.util.NoSuchElementException; 45 import java.util.Spliterator; 46 import java.util.Spliterators; 47 import java.util.WeakHashMap; 48 import java.util.stream.Stream; 49 import java.util.stream.StreamSupport; 50 51 import static java.util.zip.ZipConstants64.*; 52 53 /** 54 * This class is used to read entries from a zip file. 55 * 56 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor 57 * or method in this class will cause a {@link NullPointerException} to be 58 * thrown. 59 * 60 * @author David Connelly 61 */ 62 public 63 class ZipFile implements ZipConstants, Closeable { 64 private long jzfile; // address of jzfile data 65 private IntBuffer table; // direct buffer mapping jzfile->table 66 private ByteBuffer entries; // direct buffer mapping jzfile->entries 67 private int falsePositiveProbes; 68 private final String name; // zip file name 69 private final int total; // total number of entries 70 private final boolean locsig; // if zip file starts with LOCSIG (usually true) 71 private volatile boolean closeRequested = false; 72 73 private static final int STORED = ZipEntry.STORED; 74 private static final int DEFLATED = ZipEntry.DEFLATED; 75 76 /** 77 * Mode flag to open a zip file for reading. 78 */ 79 public static final int OPEN_READ = 0x1; 80 81 /** 82 * Mode flag to open a zip file and mark it for deletion. The file will be 83 * deleted some time between the moment that it is opened and the moment 84 * that it is closed, but its contents will remain accessible via the 85 * {@code ZipFile} object until either the close method is invoked or the 86 * virtual machine exits. 87 */ 88 public static final int OPEN_DELETE = 0x4; 89 90 static { 91 /* Zip library is loaded from System.initializeSystemClass */ 92 initIDs(); 93 } 94 95 private static native void initIDs(); 96 97 private static final boolean usemmap; 98 private static final boolean useNativeTableProbe; 99 100 static { 101 // A system prpperty to disable mmap use to avoid vm crash when 102 // in-use zip file is accidently overwritten by others. 103 String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping"); 104 usemmap = (prop == null || 105 !(prop.length() == 0 || prop.equalsIgnoreCase("true"))); 106 // A system property to enable mapping of native hash table of entries 107 // using direct byte buffers to facilitate a probe that speeds-up lookups 108 // for non-existent entries (important for class loading with large 109 // class-paths where JAR files are tried in order to find a class/resource) 110 prop = System.getProperty("sun.zip.useNativeTableProbe"); 111 useNativeTableProbe = prop != null && prop.equalsIgnoreCase("true"); 112 } 113 114 /** 115 * Opens a zip file for reading. 116 * 117 * <p>First, if there is a security manager, its {@code checkRead} 118 * method is called with the {@code name} argument as its argument 119 * to ensure the read is allowed. 120 * 121 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 122 * decode the entry names and comments. 123 * 124 * @param name the name of the zip file 125 * @throws ZipException if a ZIP format error has occurred 126 * @throws IOException if an I/O error has occurred 127 * @throws SecurityException if a security manager exists and its 128 * {@code checkRead} method doesn't allow read access to the file. 129 * 130 * @see SecurityManager#checkRead(java.lang.String) 131 */ 132 public ZipFile(String name) throws IOException { 133 this(new File(name), OPEN_READ); 134 } 135 136 /** 137 * Opens a new {@code ZipFile} to read from the specified 138 * {@code File} object in the specified mode. The mode argument 139 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 140 * 141 * <p>First, if there is a security manager, its {@code checkRead} 142 * method is called with the {@code name} argument as its argument to 143 * ensure the read is allowed. 144 * 145 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 146 * decode the entry names and comments 147 * 148 * @param file the ZIP file to be opened for reading 149 * @param mode the mode in which the file is to be opened 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 153 * its {@code checkRead} method 154 * doesn't allow read access to the file, 155 * or its {@code checkDelete} method doesn't allow deleting 156 * the file when the {@code OPEN_DELETE} flag is set. 157 * @throws IllegalArgumentException if the {@code mode} argument is invalid 158 * @see SecurityManager#checkRead(java.lang.String) 159 * @since 1.3 160 */ 161 public ZipFile(File file, int mode) throws IOException { 162 this(file, mode, StandardCharsets.UTF_8); 163 } 164 165 /** 166 * Opens a ZIP file for reading given the specified File object. 167 * 168 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 169 * decode the entry names and comments. 170 * 171 * @param file the ZIP file to be opened for reading 172 * @throws ZipException if a ZIP format error has occurred 173 * @throws IOException if an I/O error has occurred 174 */ 175 public ZipFile(File file) throws ZipException, IOException { 176 this(file, OPEN_READ); 177 } 178 179 private ZipCoder zc; 180 181 /** 182 * Opens a new {@code ZipFile} to read from the specified 183 * {@code File} object in the specified mode. The mode argument 184 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 185 * 186 * <p>First, if there is a security manager, its {@code checkRead} 187 * method is called with the {@code name} argument as its argument to 188 * ensure the read is allowed. 189 * 190 * @param file the ZIP file to be opened for reading 191 * @param mode the mode in which the file is to be opened 192 * @param charset 193 * the {@linkplain java.nio.charset.Charset charset} to 194 * be used to decode the ZIP entry name and comment that are not 195 * encoded by using UTF-8 encoding (indicated by entry's general 196 * purpose flag). 197 * 198 * @throws ZipException if a ZIP format error has occurred 199 * @throws IOException if an I/O error has occurred 200 * 201 * @throws SecurityException 202 * if a security manager exists and its {@code checkRead} 203 * method doesn't allow read access to the file,or its 204 * {@code checkDelete} method doesn't allow deleting the 205 * file when the {@code OPEN_DELETE} flag is set 206 * 207 * @throws IllegalArgumentException if the {@code mode} argument is invalid 208 * 209 * @see SecurityManager#checkRead(java.lang.String) 210 * 211 * @since 1.7 212 */ 213 public ZipFile(File file, int mode, Charset charset) throws IOException 214 { 215 if (((mode & OPEN_READ) == 0) || 216 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { 217 throw new IllegalArgumentException("Illegal mode: 0x"+ 218 Integer.toHexString(mode)); 219 } 220 String name = file.getPath(); 221 SecurityManager sm = System.getSecurityManager(); 222 if (sm != null) { 223 sm.checkRead(name); 224 if ((mode & OPEN_DELETE) != 0) { 225 sm.checkDelete(name); 226 } 227 } 228 if (charset == null) 229 throw new NullPointerException("charset is null"); 230 this.zc = ZipCoder.get(charset); 231 long t0 = System.nanoTime(); 232 ByteBuffer[] tableAndEntries = useNativeTableProbe ? new ByteBuffer[2] : null; 233 jzfile = open(name, mode, file.lastModified(), usemmap, tableAndEntries); 234 if (tableAndEntries != null) { 235 table = tableAndEntries[0].order(ByteOrder.nativeOrder()).asIntBuffer(); 236 entries = tableAndEntries[1].order(ByteOrder.nativeOrder()); 237 } 238 sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); 239 sun.misc.PerfCounter.getZipFileCount().increment(); 240 this.name = name; 241 this.total = getTotal(jzfile); 242 this.locsig = startsWithLOC(jzfile); 243 } 244 245 /** 246 * Opens a zip file for reading. 247 * 248 * <p>First, if there is a security manager, its {@code checkRead} 249 * method is called with the {@code name} argument as its argument 250 * to ensure the read is allowed. 251 * 252 * @param name the name of the zip file 253 * @param charset 254 * the {@linkplain java.nio.charset.Charset charset} to 255 * be used to decode the ZIP entry name and comment that are not 256 * encoded by using UTF-8 encoding (indicated by entry's general 257 * purpose flag). 258 * 259 * @throws ZipException if a ZIP format error has occurred 260 * @throws IOException if an I/O error has occurred 261 * @throws SecurityException 262 * if a security manager exists and its {@code checkRead} 263 * method doesn't allow read access to the file 264 * 265 * @see SecurityManager#checkRead(java.lang.String) 266 * 267 * @since 1.7 268 */ 269 public ZipFile(String name, Charset charset) throws IOException 270 { 271 this(new File(name), OPEN_READ, charset); 272 } 273 274 /** 275 * Opens a ZIP file for reading given the specified File object. 276 * @param file the ZIP file to be opened for reading 277 * @param charset 278 * The {@linkplain java.nio.charset.Charset charset} to be 279 * used to decode the ZIP entry name and comment (ignored if 280 * the <a href="package-summary.html#lang_encoding"> language 281 * encoding bit</a> of the ZIP entry's general purpose bit 282 * flag is set). 283 * 284 * @throws ZipException if a ZIP format error has occurred 285 * @throws IOException if an I/O error has occurred 286 * 287 * @since 1.7 288 */ 289 public ZipFile(File file, Charset charset) throws IOException 290 { 291 this(file, OPEN_READ, charset); 292 } 293 294 /** 295 * Returns the zip file comment, or null if none. 296 * 297 * @return the comment string for the zip file, or null if none 298 * 299 * @throws IllegalStateException if the zip file has been closed 300 * 301 * Since 1.7 302 */ 303 public String getComment() { 304 synchronized (this) { 305 ensureOpen(); 306 byte[] bcomm = getCommentBytes(jzfile); 307 if (bcomm == null) 308 return null; 309 return zc.toString(bcomm, bcomm.length); 310 } 311 } 312 313 /** 314 * Returns the zip file entry for the specified name, or null 315 * if not found. 316 * 317 * @param name the name of the entry 318 * @return the zip file entry, or null if not found 319 * @throws IllegalStateException if the zip file has been closed 320 */ 321 public ZipEntry getEntry(String name) { 322 if (name == null) { 323 throw new NullPointerException("name"); 324 } 325 synchronized (this) { 326 ensureOpen(); 327 byte[] nameBytes = zc.getBytes(name); 328 int h = hash(nameBytes); 329 if (mayContainEntry(h) || 330 (name.charAt(name.length()-1) != '/' && mayContainEntry(31 * h + '/'))) { 331 long jzentry = getEntry(jzfile, nameBytes, true); 332 if (jzentry != 0) { 333 ZipEntry ze = getZipEntry(name, jzentry); 334 freeEntry(jzfile, jzentry); 335 return ze; 336 } else { 337 falsePositiveProbes++; 338 } 339 } 340 } 341 return null; 342 } 343 344 /** 345 * Lookup into a hash table maintained in two native arrays (table and entries) 346 * to probe whether there may be an entry present with specified 32 bit hash. 347 */ 348 private boolean mayContainEntry(int h) { 349 // when sun.zip.useNativeTableProbe is not defined, 350 // any entry hash may be valid... 351 if (table == null) return true; 352 353 // use unsigned modulus to calculate index into hash table 354 int i = Integer.remainderUnsigned(h, table.capacity()); 355 // obtain index into entries array which holds a head of the hash table bucket 356 int head = table.get(i); 357 // special -1 value marks end of chain 358 while (head != -1) { 359 // 360 // entries array is composed of the following 16-byte entries (zip_util.h): 361 // 362 // typedef struct jzcell { 363 // unsigned int hash; /* 32 bit hashcode on name */ 364 // unsigned int next; /* hash chain: index into jzfile->entries */ 365 // jlong cenpos; /* Offset of central directory file header */ 366 // } jzcell; 367 // 368 int hi = head << 4; // index -> byte offset 369 if (entries.getInt(hi) == h) { // entries[head].hash 370 // got it 371 // TODO: return jzcell.cenpos to save native code from re-executing the same lookup 372 return true; 373 } 374 // advance to next in chain 375 head = entries.getInt(hi + 4); // entries[head].next 376 } 377 // no luck 378 return false; 379 } 380 381 /** 382 * Equivalent to the following from zip_util.c: 383 * <pre> 384 * static unsigned int 385 * hashN(const char *s, int length) 386 * { 387 * int h = 0; 388 * while (length-- > 0) 389 * h = 31*h + *s++; 390 * return h; 391 * } 392 * </pre> 393 */ 394 private static int hash(byte[] bytes) { 395 int h = 0; 396 for (int i = 0; i < bytes.length; i++) { 397 h = 31 * h + ((int) bytes[i] & 0xFF); 398 } 399 return h; 400 } 401 402 private static native long getEntry(long jzfile, byte[] name, 403 boolean addSlash); 404 405 // freeEntry releases the C jzentry struct. 406 private static native void freeEntry(long jzfile, long jzentry); 407 408 // the outstanding inputstreams that need to be closed, 409 // mapped to the inflater objects they use. 410 private final Map<InputStream, Inflater> streams = new WeakHashMap<>(); 411 412 /** 413 * Returns an input stream for reading the contents of the specified 414 * zip file entry. 415 * 416 * <p> Closing this ZIP file will, in turn, close all input 417 * streams that have been returned by invocations of this method. 418 * 419 * @param entry the zip file entry 420 * @return the input stream for reading the contents of the specified 421 * zip file entry. 422 * @throws ZipException if a ZIP format error has occurred 423 * @throws IOException if an I/O error has occurred 424 * @throws IllegalStateException if the zip file has been closed 425 */ 426 public InputStream getInputStream(ZipEntry entry) throws IOException { 427 if (entry == null) { 428 throw new NullPointerException("entry"); 429 } 430 long jzentry = 0; 431 ZipFileInputStream in = null; 432 synchronized (this) { 433 ensureOpen(); 434 if (!zc.isUTF8() && (entry.flag & EFS) != 0) { 435 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false); 436 } else { 437 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false); 438 } 439 if (jzentry == 0) { 440 return null; 441 } 442 in = new ZipFileInputStream(jzentry); 443 444 switch (getEntryMethod(jzentry)) { 445 case STORED: 446 synchronized (streams) { 447 streams.put(in, null); 448 } 449 return in; 450 case DEFLATED: 451 // MORE: Compute good size for inflater stream: 452 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack 453 if (size > 65536) size = 8192; 454 if (size <= 0) size = 4096; 455 Inflater inf = getInflater(); 456 InputStream is = 457 new ZipFileInflaterInputStream(in, inf, (int)size); 458 synchronized (streams) { 459 streams.put(is, inf); 460 } 461 return is; 462 default: 463 throw new ZipException("invalid compression method"); 464 } 465 } 466 } 467 468 private class ZipFileInflaterInputStream extends InflaterInputStream { 469 private volatile boolean closeRequested = false; 470 private boolean eof = false; 471 private final ZipFileInputStream zfin; 472 473 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, 474 int size) { 475 super(zfin, inf, size); 476 this.zfin = zfin; 477 } 478 479 public void close() throws IOException { 480 if (closeRequested) 481 return; 482 closeRequested = true; 483 484 super.close(); 485 Inflater inf; 486 synchronized (streams) { 487 inf = streams.remove(this); 488 } 489 if (inf != null) { 490 releaseInflater(inf); 491 } 492 } 493 494 // Override fill() method to provide an extra "dummy" byte 495 // at the end of the input stream. This is required when 496 // using the "nowrap" Inflater option. 497 protected void fill() throws IOException { 498 if (eof) { 499 throw new EOFException("Unexpected end of ZLIB input stream"); 500 } 501 len = in.read(buf, 0, buf.length); 502 if (len == -1) { 503 buf[0] = 0; 504 len = 1; 505 eof = true; 506 } 507 inf.setInput(buf, 0, len); 508 } 509 510 public int available() throws IOException { 511 if (closeRequested) 512 return 0; 513 long avail = zfin.size() - inf.getBytesWritten(); 514 return (avail > (long) Integer.MAX_VALUE ? 515 Integer.MAX_VALUE : (int) avail); 516 } 517 518 protected void finalize() throws Throwable { 519 close(); 520 } 521 } 522 523 /* 524 * Gets an inflater from the list of available inflaters or allocates 525 * a new one. 526 */ 527 private Inflater getInflater() { 528 Inflater inf; 529 synchronized (inflaterCache) { 530 while (null != (inf = inflaterCache.poll())) { 531 if (false == inf.ended()) { 532 return inf; 533 } 534 } 535 } 536 return new Inflater(true); 537 } 538 539 /* 540 * Releases the specified inflater to the list of available inflaters. 541 */ 542 private void releaseInflater(Inflater inf) { 543 if (false == inf.ended()) { 544 inf.reset(); 545 synchronized (inflaterCache) { 546 inflaterCache.add(inf); 547 } 548 } 549 } 550 551 // List of available Inflater objects for decompression 552 private Deque<Inflater> inflaterCache = new ArrayDeque<>(); 553 554 /** 555 * Returns the path name of the ZIP file. 556 * @return the path name of the ZIP file 557 */ 558 public String getName() { 559 return name; 560 } 561 562 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { 563 private int i = 0; 564 565 public ZipEntryIterator() { 566 ensureOpen(); 567 } 568 569 public boolean hasMoreElements() { 570 return hasNext(); 571 } 572 573 public boolean hasNext() { 574 synchronized (ZipFile.this) { 575 ensureOpen(); 576 return i < total; 577 } 578 } 579 580 public ZipEntry nextElement() { 581 return next(); 582 } 583 584 public ZipEntry next() { 585 synchronized (ZipFile.this) { 586 ensureOpen(); 587 if (i >= total) { 588 throw new NoSuchElementException(); 589 } 590 long jzentry = getNextEntry(jzfile, i++); 591 if (jzentry == 0) { 592 String message; 593 if (closeRequested) { 594 message = "ZipFile concurrently closed"; 595 } else { 596 message = getZipMessage(ZipFile.this.jzfile); 597 } 598 throw new ZipError("jzentry == 0" + 599 ",\n jzfile = " + ZipFile.this.jzfile + 600 ",\n total = " + ZipFile.this.total + 601 ",\n name = " + ZipFile.this.name + 602 ",\n i = " + i + 603 ",\n message = " + message 604 ); 605 } 606 ZipEntry ze = getZipEntry(null, jzentry); 607 freeEntry(jzfile, jzentry); 608 return ze; 609 } 610 } 611 612 public Iterator<ZipEntry> asIterator() { 613 return this; 614 } 615 } 616 617 /** 618 * Returns an enumeration of the ZIP file entries. 619 * @return an enumeration of the ZIP file entries 620 * @throws IllegalStateException if the zip file has been closed 621 */ 622 public Enumeration<? extends ZipEntry> entries() { 623 return new ZipEntryIterator(); 624 } 625 626 /** 627 * Returns an ordered {@code Stream} over the ZIP file entries. 628 * Entries appear in the {@code Stream} in the order they appear in 629 * the central directory of the ZIP file. 630 * 631 * @return an ordered {@code Stream} of entries in this ZIP file 632 * @throws IllegalStateException if the zip file has been closed 633 * @since 1.8 634 */ 635 public Stream<? extends ZipEntry> stream() { 636 return StreamSupport.stream(Spliterators.spliterator( 637 new ZipEntryIterator(), size(), 638 Spliterator.ORDERED | Spliterator.DISTINCT | 639 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 640 } 641 642 private ZipEntry getZipEntry(String name, long jzentry) { 643 ZipEntry e = new ZipEntry(); 644 e.flag = getEntryFlag(jzentry); // get the flag first 645 if (name != null) { 646 e.name = name; 647 } else { 648 byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME); 649 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 650 e.name = zc.toStringUTF8(bname, bname.length); 651 } else { 652 e.name = zc.toString(bname, bname.length); 653 } 654 } 655 e.xdostime = getEntryTime(jzentry); 656 e.crc = getEntryCrc(jzentry); 657 e.size = getEntrySize(jzentry); 658 e.csize = getEntryCSize(jzentry); 659 e.method = getEntryMethod(jzentry); 660 e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false); 661 byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); 662 if (bcomm == null) { 663 e.comment = null; 664 } else { 665 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 666 e.comment = zc.toStringUTF8(bcomm, bcomm.length); 667 } else { 668 e.comment = zc.toString(bcomm, bcomm.length); 669 } 670 } 671 return e; 672 } 673 674 private static native long getNextEntry(long jzfile, int i); 675 676 /** 677 * Returns the number of entries in the ZIP file. 678 * @return the number of entries in the ZIP file 679 * @throws IllegalStateException if the zip file has been closed 680 */ 681 public int size() { 682 ensureOpen(); 683 return total; 684 } 685 686 /** 687 * Closes the ZIP file. 688 * <p> Closing this ZIP file will close all of the input streams 689 * previously returned by invocations of the {@link #getInputStream 690 * getInputStream} method. 691 * 692 * @throws IOException if an I/O error has occurred 693 */ 694 public void close() throws IOException { 695 if (closeRequested) 696 return; 697 closeRequested = true; 698 699 synchronized (this) { 700 // Close streams, release their inflaters 701 synchronized (streams) { 702 if (false == streams.isEmpty()) { 703 Map<InputStream, Inflater> copy = new HashMap<>(streams); 704 streams.clear(); 705 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { 706 e.getKey().close(); 707 Inflater inf = e.getValue(); 708 if (inf != null) { 709 inf.end(); 710 } 711 } 712 } 713 } 714 715 // Release cached inflaters 716 Inflater inf; 717 synchronized (inflaterCache) { 718 while (null != (inf = inflaterCache.poll())) { 719 inf.end(); 720 } 721 } 722 723 if (jzfile != 0) { 724 // Close the zip file 725 long zf = this.jzfile; 726 jzfile = 0; 727 table = null; 728 entries = null; 729 close(zf); 730 } 731 } 732 } 733 734 /** 735 * Ensures that the system resources held by this ZipFile object are 736 * released when there are no more references to it. 737 * 738 * <p> 739 * Since the time when GC would invoke this method is undetermined, 740 * it is strongly recommended that applications invoke the {@code close} 741 * method as soon they have finished accessing this {@code ZipFile}. 742 * This will prevent holding up system resources for an undetermined 743 * length of time. 744 * 745 * @throws IOException if an I/O error has occurred 746 * @see java.util.zip.ZipFile#close() 747 */ 748 protected void finalize() throws IOException { 749 close(); 750 } 751 752 private static native void close(long jzfile); 753 754 private void ensureOpen() { 755 if (closeRequested) { 756 throw new IllegalStateException("zip file closed"); 757 } 758 759 if (jzfile == 0) { 760 throw new IllegalStateException("The object is not initialized."); 761 } 762 } 763 764 private void ensureOpenOrZipException() throws IOException { 765 if (closeRequested) { 766 throw new ZipException("ZipFile closed"); 767 } 768 } 769 770 /* 771 * Inner class implementing the input stream used to read a 772 * (possibly compressed) zip file entry. 773 */ 774 private class ZipFileInputStream extends InputStream { 775 private volatile boolean closeRequested = false; 776 protected long jzentry; // address of jzentry data 777 private long pos; // current position within entry data 778 protected long rem; // number of remaining bytes within entry 779 protected long size; // uncompressed size of this entry 780 781 ZipFileInputStream(long jzentry) { 782 pos = 0; 783 rem = getEntryCSize(jzentry); 784 size = getEntrySize(jzentry); 785 this.jzentry = jzentry; 786 } 787 788 public int read(byte b[], int off, int len) throws IOException { 789 synchronized (ZipFile.this) { 790 long rem = this.rem; 791 long pos = this.pos; 792 if (rem == 0) { 793 return -1; 794 } 795 if (len <= 0) { 796 return 0; 797 } 798 if (len > rem) { 799 len = (int) rem; 800 } 801 802 ensureOpenOrZipException(); 803 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b, 804 off, len); 805 if (len > 0) { 806 this.pos = (pos + len); 807 this.rem = (rem - len); 808 } 809 } 810 if (rem == 0) { 811 close(); 812 } 813 return len; 814 } 815 816 public int read() throws IOException { 817 byte[] b = new byte[1]; 818 if (read(b, 0, 1) == 1) { 819 return b[0] & 0xff; 820 } else { 821 return -1; 822 } 823 } 824 825 public long skip(long n) { 826 if (n > rem) 827 n = rem; 828 pos += n; 829 rem -= n; 830 if (rem == 0) { 831 close(); 832 } 833 return n; 834 } 835 836 public int available() { 837 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 838 } 839 840 public long size() { 841 return size; 842 } 843 844 public void close() { 845 if (closeRequested) 846 return; 847 closeRequested = true; 848 849 rem = 0; 850 synchronized (ZipFile.this) { 851 if (jzentry != 0 && ZipFile.this.jzfile != 0) { 852 freeEntry(ZipFile.this.jzfile, jzentry); 853 jzentry = 0; 854 } 855 } 856 synchronized (streams) { 857 streams.remove(this); 858 } 859 } 860 861 protected void finalize() { 862 close(); 863 } 864 } 865 866 static { 867 sun.misc.SharedSecrets.setJavaUtilZipFileAccess( 868 new sun.misc.JavaUtilZipFileAccess() { 869 public boolean startsWithLocHeader(ZipFile zip) { 870 return zip.startsWithLocHeader(); 871 } 872 } 873 ); 874 } 875 876 /** 877 * Returns {@code true} if, and only if, the zip file begins with {@code 878 * LOCSIG}. 879 */ 880 private boolean startsWithLocHeader() { 881 return locsig; 882 } 883 884 private static native long open(String name, int mode, long lastModified, 885 boolean usemmap, 886 ByteBuffer[] tableAndEntries) throws IOException; 887 private static native int getTotal(long jzfile); 888 private static native boolean startsWithLOC(long jzfile); 889 private static native int read(long jzfile, long jzentry, 890 long pos, byte[] b, int off, int len); 891 892 // access to the native zentry object 893 private static native long getEntryTime(long jzentry); 894 private static native long getEntryCrc(long jzentry); 895 private static native long getEntryCSize(long jzentry); 896 private static native long getEntrySize(long jzentry); 897 private static native int getEntryMethod(long jzentry); 898 private static native int getEntryFlag(long jzentry); 899 private static native byte[] getCommentBytes(long jzfile); 900 901 private static final int JZENTRY_NAME = 0; 902 private static final int JZENTRY_EXTRA = 1; 903 private static final int JZENTRY_COMMENT = 2; 904 private static native byte[] getEntryBytes(long jzentry, int type); 905 906 private static native String getZipMessage(long jzfile); 907 }