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