1 /* 2 * Copyright (c) 1995, 2010, 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.charset.Charset; 34 import java.util.Vector; 35 import java.util.Enumeration; 36 import java.util.Set; 37 import java.util.HashSet; 38 import java.util.NoSuchElementException; 39 import java.security.AccessController; 40 import sun.security.action.GetPropertyAction; 41 import static java.util.zip.ZipConstants64.*; 42 43 /** 44 * This class is used to read entries from a zip file. 45 * 46 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor 47 * or method in this class will cause a {@link NullPointerException} to be 48 * thrown. 49 * 50 * @author David Connelly 51 */ 52 public 53 class ZipFile implements ZipConstants, Closeable { 54 private long jzfile; // address of jzfile data 55 private String name; // zip file name 56 private int total; // total number of entries 57 private boolean closeRequested; 58 59 private static final int STORED = ZipEntry.STORED; 60 private static final int DEFLATED = ZipEntry.DEFLATED; 61 62 /** 63 * Mode flag to open a zip file for reading. 64 */ 65 public static final int OPEN_READ = 0x1; 66 67 /** 68 * Mode flag to open a zip file and mark it for deletion. The file will be 69 * deleted some time between the moment that it is opened and the moment 70 * that it is closed, but its contents will remain accessible via the 71 * <tt>ZipFile</tt> object until either the close method is invoked or the 72 * virtual machine exits. 73 */ 74 public static final int OPEN_DELETE = 0x4; 75 76 static { 77 /* Zip library is loaded from System.initializeSystemClass */ 78 initIDs(); 79 } 80 81 private static native void initIDs(); 82 83 private static final boolean usemmap; 84 85 static { 86 // A system prpperty to disable mmap use to avoid vm crash when 87 // in-use zip file is accidently overwritten by others. 88 String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping"); 89 usemmap = (prop == null || 90 !(prop.length() == 0 || prop.equalsIgnoreCase("true"))); 91 } 92 93 /** 94 * Opens a zip file for reading. 95 * 96 * <p>First, if there is a security manager, its <code>checkRead</code> 97 * method is called with the <code>name</code> argument as its argument 98 * to ensure the read is allowed. 99 * 100 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 101 * decode the entry names and comments. 102 * 103 * @param name the name of the zip file 104 * @throws ZipException if a ZIP format error has occurred 105 * @throws IOException if an I/O error has occurred 106 * @throws SecurityException if a security manager exists and its 107 * <code>checkRead</code> method doesn't allow read access to the file. 108 * 109 * @see SecurityManager#checkRead(java.lang.String) 110 */ 111 public ZipFile(String name) throws IOException { 112 this(new File(name), OPEN_READ); 113 } 114 115 /** 116 * Opens a new <code>ZipFile</code> to read from the specified 117 * <code>File</code> object in the specified mode. The mode argument 118 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 119 * 120 * <p>First, if there is a security manager, its <code>checkRead</code> 121 * method is called with the <code>name</code> argument as its argument to 122 * ensure the read is allowed. 123 * 124 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 125 * decode the entry names and comments 126 * 127 * @param file the ZIP file to be opened for reading 128 * @param mode the mode in which the file is to be opened 129 * @throws ZipException if a ZIP format error has occurred 130 * @throws IOException if an I/O error has occurred 131 * @throws SecurityException if a security manager exists and 132 * its <code>checkRead</code> method 133 * doesn't allow read access to the file, 134 * or its <code>checkDelete</code> method doesn't allow deleting 135 * the file when the <tt>OPEN_DELETE</tt> flag is set. 136 * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid 137 * @see SecurityManager#checkRead(java.lang.String) 138 * @since 1.3 139 */ 140 public ZipFile(File file, int mode) throws IOException { 141 this(file, mode, Charset.forName("UTF-8")); 142 } 143 144 /** 145 * Opens a ZIP file for reading given the specified File object. 146 * 147 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to 148 * decode the entry names and comments. 149 * 150 * @param file the ZIP file to be opened for reading 151 * @throws ZipException if a ZIP format error has occurred 152 * @throws IOException if an I/O error has occurred 153 */ 154 public ZipFile(File file) throws ZipException, IOException { 155 this(file, OPEN_READ); 156 } 157 158 private ZipCoder zc; 159 160 /** 161 * Opens a new <code>ZipFile</code> to read from the specified 162 * <code>File</code> object in the specified mode. The mode argument 163 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 164 * 165 * <p>First, if there is a security manager, its <code>checkRead</code> 166 * method is called with the <code>name</code> argument as its argument to 167 * ensure the read is allowed. 168 * 169 * @param file the ZIP file to be opened for reading 170 * @param mode the mode in which the file is to be opened 171 * @param charset 172 * the {@linkplain java.nio.charset.Charset charset} to 173 * be used to decode the ZIP entry name and comment that are not 174 * encoded by using UTF-8 encoding (indicated by entry's general 175 * purpose flag). 176 * 177 * @throws ZipException if a ZIP format error has occurred 178 * @throws IOException if an I/O error has occurred 179 * 180 * @throws SecurityException 181 * if a security manager exists and its <code>checkRead</code> 182 * method doesn't allow read access to the file,or its 183 * <code>checkDelete</code> method doesn't allow deleting the 184 * file when the <tt>OPEN_DELETE</tt> flag is set 185 * 186 * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid 187 * 188 * @see SecurityManager#checkRead(java.lang.String) 189 * 190 * @since 1.7 191 */ 192 public ZipFile(File file, int mode, Charset charset) throws IOException 193 { 194 if (((mode & OPEN_READ) == 0) || 195 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { 196 throw new IllegalArgumentException("Illegal mode: 0x"+ 197 Integer.toHexString(mode)); 198 } 199 String name = file.getPath(); 200 SecurityManager sm = System.getSecurityManager(); 201 if (sm != null) { 202 sm.checkRead(name); 203 if ((mode & OPEN_DELETE) != 0) { 204 sm.checkDelete(name); 205 } 206 } 207 if (charset == null) 208 throw new NullPointerException("charset is null"); 209 this.zc = ZipCoder.get(charset); 210 long t0 = System.nanoTime(); 211 jzfile = open(name, mode, file.lastModified(), usemmap); 212 sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); 213 sun.misc.PerfCounter.getZipFileCount().increment(); 214 this.name = name; 215 this.total = getTotal(jzfile); 216 } 217 218 /** 219 * Opens a zip file for reading. 220 * 221 * <p>First, if there is a security manager, its <code>checkRead</code> 222 * method is called with the <code>name</code> argument as its argument 223 * to ensure the read is allowed. 224 * 225 * @param name the name of the zip file 226 * @param charset 227 * the {@linkplain java.nio.charset.Charset charset} to 228 * be used to decode the ZIP entry name and comment that are not 229 * encoded by using UTF-8 encoding (indicated by entry's general 230 * purpose flag). 231 * 232 * @throws ZipException if a ZIP format error has occurred 233 * @throws IOException if an I/O error has occurred 234 * @throws SecurityException 235 * if a security manager exists and its <code>checkRead</code> 236 * method doesn't allow read access to the file 237 * 238 * @see SecurityManager#checkRead(java.lang.String) 239 * 240 * @since 1.7 241 */ 242 public ZipFile(String name, Charset charset) throws IOException 243 { 244 this(new File(name), OPEN_READ, charset); 245 } 246 247 /** 248 * Opens a ZIP file for reading given the specified File object. 249 * @param file the ZIP file to be opened for reading 250 * @param charset 251 * The {@linkplain java.nio.charset.Charset charset} to be 252 * used to decode the ZIP entry name and comment (ignored if 253 * the <a href="package-summary.html#lang_encoding"> language 254 * encoding bit</a> of the ZIP entry's general purpose bit 255 * flag is set). 256 * 257 * @throws ZipException if a ZIP format error has occurred 258 * @throws IOException if an I/O error has occurred 259 * 260 * @since 1.7 261 */ 262 public ZipFile(File file, Charset charset) throws IOException 263 { 264 this(file, OPEN_READ, charset); 265 } 266 267 /** 268 * Returns the zip file comment, or null if none. 269 * 270 * @return the comment string for the zip file, or null if none 271 * 272 * @throws IllegalStateException if the zip file has been closed 273 * 274 * Since 1.7 275 */ 276 public String getComment() { 277 synchronized (this) { 278 ensureOpen(); 279 byte[] bcomm = getCommentBytes(jzfile); 280 if (bcomm == null) 281 return null; 282 return zc.toString(bcomm, bcomm.length); 283 } 284 } 285 286 /** 287 * Returns the zip file entry for the specified name, or null 288 * if not found. 289 * 290 * @param name the name of the entry 291 * @return the zip file entry, or null if not found 292 * @throws IllegalStateException if the zip file has been closed 293 */ 294 public ZipEntry getEntry(String name) { 295 if (name == null) { 296 throw new NullPointerException("name"); 297 } 298 long jzentry = 0; 299 synchronized (this) { 300 ensureOpen(); 301 jzentry = getEntry(jzfile, zc.getBytes(name), true); 302 if (jzentry != 0) { 303 ZipEntry ze = getZipEntry(name, jzentry); 304 freeEntry(jzfile, jzentry); 305 return ze; 306 } 307 } 308 return null; 309 } 310 311 private static native long getEntry(long jzfile, byte[] name, 312 boolean addSlash); 313 314 // freeEntry releases the C jzentry struct. 315 private static native void freeEntry(long jzfile, long jzentry); 316 317 // the outstanding inputstreams that need to be closed. 318 private Set<InputStream> streams = new HashSet<>(); 319 320 /** 321 * Returns an input stream for reading the contents of the specified 322 * zip file entry. 323 * 324 * <p> Closing this ZIP file will, in turn, close all input 325 * streams that have been returned by invocations of this method. 326 * 327 * @param entry the zip file entry 328 * @return the input stream for reading the contents of the specified 329 * zip file entry. 330 * @throws ZipException if a ZIP format error has occurred 331 * @throws IOException if an I/O error has occurred 332 * @throws IllegalStateException if the zip file has been closed 333 */ 334 public InputStream getInputStream(ZipEntry entry) throws IOException { 335 if (entry == null) { 336 throw new NullPointerException("entry"); 337 } 338 long jzentry = 0; 339 ZipFileInputStream in = null; 340 synchronized (this) { 341 ensureOpen(); 342 if (!zc.isUTF8() && (entry.flag & EFS) != 0) { 343 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false); 344 } else { 345 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false); 346 } 347 if (jzentry == 0) { 348 return null; 349 } 350 in = new ZipFileInputStream(jzentry); 351 352 switch (getEntryMethod(jzentry)) { 353 case STORED: 354 streams.add(in); 355 return in; 356 case DEFLATED: 357 final ZipFileInputStream zfin = in; 358 // MORE: Compute good size for inflater stream: 359 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack 360 if (size > 65536) size = 8192; 361 if (size <= 0) size = 4096; 362 InputStream is = new InflaterInputStream(zfin, getInflater(), (int)size) { 363 private boolean isClosed = false; 364 365 public void close() throws IOException { 366 if (!isClosed) { 367 super.close(); 368 releaseInflater(inf); 369 isClosed = true; 370 } 371 } 372 // Override fill() method to provide an extra "dummy" byte 373 // at the end of the input stream. This is required when 374 // using the "nowrap" Inflater option. 375 protected void fill() throws IOException { 376 if (eof) { 377 throw new EOFException( 378 "Unexpected end of ZLIB input stream"); 379 } 380 len = this.in.read(buf, 0, buf.length); 381 if (len == -1) { 382 buf[0] = 0; 383 len = 1; 384 eof = true; 385 } 386 inf.setInput(buf, 0, len); 387 } 388 private boolean eof; 389 390 public int available() throws IOException { 391 if (isClosed) 392 return 0; 393 long avail = zfin.size() - inf.getBytesWritten(); 394 return avail > (long) Integer.MAX_VALUE ? 395 Integer.MAX_VALUE : (int) avail; 396 } 397 }; 398 streams.add(is); 399 return is; 400 default: 401 throw new ZipException("invalid compression method"); 402 } 403 } 404 } 405 406 /* 407 * Gets an inflater from the list of available inflaters or allocates 408 * a new one. 409 */ 410 private Inflater getInflater() { 411 synchronized (inflaters) { 412 int size = inflaters.size(); 413 if (size > 0) { 414 Inflater inf = (Inflater)inflaters.remove(size - 1); 415 return inf; 416 } else { 417 return new Inflater(true); 418 } 419 } 420 } 421 422 /* 423 * Releases the specified inflater to the list of available inflaters. 424 */ 425 private void releaseInflater(Inflater inf) { 426 synchronized (inflaters) { 427 if (inf.ended()) 428 return; 429 inf.reset(); 430 inflaters.add(inf); 431 } 432 } 433 434 // List of available Inflater objects for decompression 435 private Vector inflaters = new Vector(); 436 437 /** 438 * Returns the path name of the ZIP file. 439 * @return the path name of the ZIP file 440 */ 441 public String getName() { 442 return name; 443 } 444 445 /** 446 * Returns an enumeration of the ZIP file entries. 447 * @return an enumeration of the ZIP file entries 448 * @throws IllegalStateException if the zip file has been closed 449 */ 450 public Enumeration<? extends ZipEntry> entries() { 451 ensureOpen(); 452 return new Enumeration<ZipEntry>() { 453 private int i = 0; 454 public boolean hasMoreElements() { 455 synchronized (ZipFile.this) { 456 ensureOpen(); 457 return i < total; 458 } 459 } 460 public ZipEntry nextElement() throws NoSuchElementException { 461 synchronized (ZipFile.this) { 462 ensureOpen(); 463 if (i >= total) { 464 throw new NoSuchElementException(); 465 } 466 long jzentry = getNextEntry(jzfile, i++); 467 if (jzentry == 0) { 468 String message; 469 if (closeRequested) { 470 message = "ZipFile concurrently closed"; 471 } else { 472 message = getZipMessage(ZipFile.this.jzfile); 473 } 474 throw new ZipError("jzentry == 0" + 475 ",\n jzfile = " + ZipFile.this.jzfile + 476 ",\n total = " + ZipFile.this.total + 477 ",\n name = " + ZipFile.this.name + 478 ",\n i = " + i + 479 ",\n message = " + message 480 ); 481 } 482 ZipEntry ze = getZipEntry(null, jzentry); 483 freeEntry(jzfile, jzentry); 484 return ze; 485 } 486 } 487 }; 488 } 489 490 private ZipEntry getZipEntry(String name, long jzentry) { 491 ZipEntry e = new ZipEntry(); 492 e.flag = getEntryFlag(jzentry); // get the flag first 493 if (name != null) { 494 e.name = name; 495 } else { 496 byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME); 497 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 498 e.name = zc.toStringUTF8(bname, bname.length); 499 } else { 500 e.name = zc.toString(bname, bname.length); 501 } 502 } 503 e.time = getEntryTime(jzentry); 504 e.crc = getEntryCrc(jzentry); 505 e.size = getEntrySize(jzentry); 506 e. csize = getEntryCSize(jzentry); 507 e.method = getEntryMethod(jzentry); 508 e.extra = getEntryBytes(jzentry, JZENTRY_EXTRA); 509 byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); 510 if (bcomm == null) { 511 e.comment = null; 512 } else { 513 if (!zc.isUTF8() && (e.flag & EFS) != 0) { 514 e.comment = zc.toStringUTF8(bcomm, bcomm.length); 515 } else { 516 e.comment = zc.toString(bcomm, bcomm.length); 517 } 518 } 519 return e; 520 } 521 522 private static native long getNextEntry(long jzfile, int i); 523 524 /** 525 * Returns the number of entries in the ZIP file. 526 * @return the number of entries in the ZIP file 527 * @throws IllegalStateException if the zip file has been closed 528 */ 529 public int size() { 530 ensureOpen(); 531 return total; 532 } 533 534 /** 535 * Closes the ZIP file. 536 * <p> Closing this ZIP file will close all of the input streams 537 * previously returned by invocations of the {@link #getInputStream 538 * getInputStream} method. 539 * 540 * @throws IOException if an I/O error has occurred 541 */ 542 public void close() throws IOException { 543 synchronized (this) { 544 closeRequested = true; 545 546 if (streams.size() !=0) { 547 Set<InputStream> copy = streams; 548 streams = new HashSet<>(); 549 for (InputStream is: copy) 550 is.close(); 551 } 552 553 if (jzfile != 0) { 554 // Close the zip file 555 long zf = this.jzfile; 556 jzfile = 0; 557 558 close(zf); 559 560 // Release inflaters 561 synchronized (inflaters) { 562 int size = inflaters.size(); 563 for (int i = 0; i < size; i++) { 564 Inflater inf = (Inflater)inflaters.get(i); 565 inf.end(); 566 } 567 } 568 } 569 } 570 } 571 572 573 /** 574 * Ensures that the <code>close</code> method of this ZIP file is 575 * called when there are no more references to it. 576 * 577 * <p> 578 * Since the time when GC would invoke this method is undetermined, 579 * it is strongly recommended that applications invoke the <code>close</code> 580 * method as soon they have finished accessing this <code>ZipFile</code>. 581 * This will prevent holding up system resources for an undetermined 582 * length of time. 583 * 584 * @throws IOException if an I/O error has occurred 585 * @see java.util.zip.ZipFile#close() 586 */ 587 protected void finalize() throws IOException { 588 close(); 589 } 590 591 private static native void close(long jzfile); 592 593 private void ensureOpen() { 594 if (closeRequested) { 595 throw new IllegalStateException("zip file closed"); 596 } 597 598 if (jzfile == 0) { 599 throw new IllegalStateException("The object is not initialized."); 600 } 601 } 602 603 private void ensureOpenOrZipException() throws IOException { 604 if (closeRequested) { 605 throw new ZipException("ZipFile closed"); 606 } 607 } 608 609 /* 610 * Inner class implementing the input stream used to read a 611 * (possibly compressed) zip file entry. 612 */ 613 private class ZipFileInputStream extends InputStream { 614 protected long jzentry; // address of jzentry data 615 private long pos; // current position within entry data 616 protected long rem; // number of remaining bytes within entry 617 protected long size; // uncompressed size of this entry 618 619 ZipFileInputStream(long jzentry) { 620 pos = 0; 621 rem = getEntryCSize(jzentry); 622 size = getEntrySize(jzentry); 623 this.jzentry = jzentry; 624 } 625 626 public int read(byte b[], int off, int len) throws IOException { 627 if (rem == 0) { 628 return -1; 629 } 630 if (len <= 0) { 631 return 0; 632 } 633 if (len > rem) { 634 len = (int) rem; 635 } 636 synchronized (ZipFile.this) { 637 ensureOpenOrZipException(); 638 639 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b, 640 off, len); 641 } 642 if (len > 0) { 643 pos += len; 644 rem -= len; 645 } 646 if (rem == 0) { 647 close(); 648 } 649 return len; 650 } 651 652 public int read() throws IOException { 653 byte[] b = new byte[1]; 654 if (read(b, 0, 1) == 1) { 655 return b[0] & 0xff; 656 } else { 657 return -1; 658 } 659 } 660 661 public long skip(long n) { 662 if (n > rem) 663 n = rem; 664 pos += n; 665 rem -= n; 666 if (rem == 0) { 667 close(); 668 } 669 return n; 670 } 671 672 public int available() { 673 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; 674 } 675 676 public long size() { 677 return size; 678 } 679 680 public void close() { 681 rem = 0; 682 synchronized (ZipFile.this) { 683 if (jzentry != 0 && ZipFile.this.jzfile != 0) { 684 freeEntry(ZipFile.this.jzfile, jzentry); 685 jzentry = 0; 686 } 687 streams.remove(this); 688 } 689 } 690 } 691 692 693 private static native long open(String name, int mode, long lastModified, 694 boolean usemmap) throws IOException; 695 private static native int getTotal(long jzfile); 696 private static native int read(long jzfile, long jzentry, 697 long pos, byte[] b, int off, int len); 698 699 // access to the native zentry object 700 private static native long getEntryTime(long jzentry); 701 private static native long getEntryCrc(long jzentry); 702 private static native long getEntryCSize(long jzentry); 703 private static native long getEntrySize(long jzentry); 704 private static native int getEntryMethod(long jzentry); 705 private static native int getEntryFlag(long jzentry); 706 private static native byte[] getCommentBytes(long jzfile); 707 708 private static final int JZENTRY_NAME = 0; 709 private static final int JZENTRY_EXTRA = 1; 710 private static final int JZENTRY_COMMENT = 2; 711 private static native byte[] getEntryBytes(long jzentry, int type); 712 713 private static native String getZipMessage(long jzfile); 714 }