1 /* java.util.zip.ZipFile 2 Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 19 02111-1307 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 package java.util.zip; 39 40 import java.io.BufferedInputStream; 41 import java.io.DataInput; 42 import java.io.File; 43 import java.io.InputStream; 44 import java.io.IOException; 45 import java.io.EOFException; 46 import java.io.RandomAccessFile; 47 import java.util.Enumeration; 48 import java.util.HashMap; 49 import java.util.Iterator; 50 import java.util.NoSuchElementException; 51 52 /** 53 * This class represents a Zip archive. You can ask for the contained 54 * entries, or get an input stream for a file entry. The entry is 55 * automatically decompressed. 56 * 57 * This class is thread safe: You can open input streams for arbitrary 58 * entries in different threads. 59 * 60 * @author Jochen Hoenicke 61 * @author Artur Biesiadowski 62 */ 63 public class ZipFile implements ZipConstants 64 { 65 66 /** 67 * Mode flag to open a zip file for reading. 68 */ 69 public static final int OPEN_READ = 0x1; 70 71 /** 72 * Mode flag to delete a zip file after reading. 73 */ 74 public static final int OPEN_DELETE = 0x4; 75 76 // Name of this zip file. 77 private final String name; 78 79 // File from which zip entries are read. 80 private final RandomAccessFile raf; 81 82 // The entries of this zip file when initialized and not yet closed. 83 private HashMap entries; 84 85 private boolean closed = false; 86 87 /** 88 * Opens a Zip file with the given name for reading. 89 * @exception IOException if a i/o error occured. 90 * @exception ZipException if the file doesn't contain a valid zip 91 * archive. 92 */ ZipFile(String name)93 public ZipFile(String name) throws ZipException, IOException 94 { 95 this.raf = new RandomAccessFile(name, "r"); 96 this.name = name; 97 } 98 99 /** 100 * Opens a Zip file reading the given File. 101 * @exception IOException if a i/o error occured. 102 * @exception ZipException if the file doesn't contain a valid zip 103 * archive. 104 */ ZipFile(File file)105 public ZipFile(File file) throws ZipException, IOException 106 { 107 this.raf = new RandomAccessFile(file, "r"); 108 this.name = file.getPath(); 109 } 110 111 /** 112 * Opens a Zip file reading the given File in the given mode. 113 * 114 * If the OPEN_DELETE mode is specified, the zip file will be deleted at 115 * some time moment after it is opened. It will be deleted before the zip 116 * file is closed or the Virtual Machine exits. 117 * 118 * The contents of the zip file will be accessible until it is closed. 119 * 120 * The OPEN_DELETE mode is currently unimplemented in this library 121 * 122 * @since JDK1.3 123 * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE 124 * 125 * @exception IOException if a i/o error occured. 126 * @exception ZipException if the file doesn't contain a valid zip 127 * archive. 128 */ ZipFile(File file, int mode)129 public ZipFile(File file, int mode) throws ZipException, IOException 130 { 131 if ((mode & OPEN_DELETE) != 0) 132 { 133 throw new IllegalArgumentException 134 ("OPEN_DELETE mode not supported yet in java.util.zip.ZipFile"); 135 } 136 this.raf = new RandomAccessFile(file, "r"); 137 this.name = file.getPath(); 138 } 139 140 /** 141 * Read an unsigned short in little endian byte order from the given 142 * DataInput stream using the given byte buffer. 143 * 144 * @param di DataInput stream to read from. 145 * @param b the byte buffer to read in (must be at least 2 bytes long). 146 * @return The value read. 147 * 148 * @exception IOException if a i/o error occured. 149 * @exception EOFException if the file ends prematurely 150 */ readLeShort(DataInput di, byte[] b)151 private final int readLeShort(DataInput di, byte[] b) throws IOException 152 { 153 di.readFully(b, 0, 2); 154 return (b[0] & 0xff) | (b[1] & 0xff) << 8; 155 } 156 157 /** 158 * Read an int in little endian byte order from the given 159 * DataInput stream using the given byte buffer. 160 * 161 * @param di DataInput stream to read from. 162 * @param b the byte buffer to read in (must be at least 4 bytes long). 163 * @return The value read. 164 * 165 * @exception IOException if a i/o error occured. 166 * @exception EOFException if the file ends prematurely 167 */ readLeInt(DataInput di, byte[] b)168 private final int readLeInt(DataInput di, byte[] b) throws IOException 169 { 170 di.readFully(b, 0, 4); 171 return ((b[0] & 0xff) | (b[1] & 0xff) << 8) 172 | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16; 173 } 174 175 176 /** 177 * Read an unsigned short in little endian byte order from the given 178 * byte buffer at the given offset. 179 * 180 * @param b the byte array to read from. 181 * @param off the offset to read from. 182 * @return The value read. 183 */ readLeShort(byte[] b, int off)184 private final int readLeShort(byte[] b, int off) 185 { 186 return (b[off] & 0xff) | (b[off+1] & 0xff) << 8; 187 } 188 189 /** 190 * Read an int in little endian byte order from the given 191 * byte buffer at the given offset. 192 * 193 * @param b the byte array to read from. 194 * @param off the offset to read from. 195 * @return The value read. 196 */ readLeInt(byte[] b, int off)197 private final int readLeInt(byte[] b, int off) 198 { 199 return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8) 200 | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16; 201 } 202 203 204 /** 205 * Read the central directory of a zip file and fill the entries 206 * array. This is called exactly once when first needed. It is called 207 * while holding the lock on <code>raf</code>. 208 * 209 * @exception IOException if a i/o error occured. 210 * @exception ZipException if the central directory is malformed 211 */ readEntries()212 private void readEntries() throws ZipException, IOException 213 { 214 /* Search for the End Of Central Directory. When a zip comment is 215 * present the directory may start earlier. 216 * FIXME: This searches the whole file in a very slow manner if the 217 * file isn't a zip file. 218 */ 219 long pos = raf.length() - ENDHDR; 220 byte[] ebs = new byte[CENHDR]; 221 222 do 223 { 224 if (pos < 0) 225 throw new ZipException 226 ("central directory not found, probably not a zip file: " + name); 227 raf.seek(pos--); 228 } 229 while (readLeInt(raf, ebs) != ENDSIG); 230 231 if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD) 232 throw new EOFException(name); 233 int count = readLeShort(raf, ebs); 234 if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ) 235 throw new EOFException(name); 236 int centralOffset = readLeInt(raf, ebs); 237 238 entries = new HashMap(count+count/2); 239 raf.seek(centralOffset); 240 241 byte[] buffer = new byte[16]; 242 for (int i = 0; i < count; i++) 243 { 244 raf.readFully(ebs); 245 if (readLeInt(ebs, 0) != CENSIG) 246 throw new ZipException("Wrong Central Directory signature: " + name); 247 248 int method = readLeShort(ebs, CENHOW); 249 int dostime = readLeInt(ebs, CENTIM); 250 int crc = readLeInt(ebs, CENCRC); 251 int csize = readLeInt(ebs, CENSIZ); 252 int size = readLeInt(ebs, CENLEN); 253 int nameLen = readLeShort(ebs, CENNAM); 254 int extraLen = readLeShort(ebs, CENEXT); 255 int commentLen = readLeShort(ebs, CENCOM); 256 257 int offset = readLeInt(ebs, CENOFF); 258 259 int needBuffer = Math.max(nameLen, commentLen); 260 if (buffer.length < needBuffer) 261 buffer = new byte[needBuffer]; 262 263 raf.readFully(buffer, 0, nameLen); 264 String name = new String(buffer, 0, 0, nameLen); 265 266 ZipEntry entry = new ZipEntry(name); 267 entry.setMethod(method); 268 entry.setCrc(crc & 0xffffffffL); 269 entry.setSize(size & 0xffffffffL); 270 entry.setCompressedSize(csize & 0xffffffffL); 271 entry.setDOSTime(dostime); 272 if (extraLen > 0) 273 { 274 byte[] extra = new byte[extraLen]; 275 raf.readFully(extra); 276 entry.setExtra(extra); 277 } 278 if (commentLen > 0) 279 { 280 raf.readFully(buffer, 0, commentLen); 281 entry.setComment(new String(buffer, 0, commentLen)); 282 } 283 entry.offset = offset; 284 entries.put(name, entry); 285 } 286 } 287 288 /** 289 * Closes the ZipFile. This also closes all input streams given by 290 * this class. After this is called, no further method should be 291 * called. 292 * 293 * @exception IOException if a i/o error occured. 294 */ close()295 public void close() throws IOException 296 { 297 synchronized (raf) 298 { 299 closed = true; 300 entries = null; 301 raf.close(); 302 } 303 } 304 305 /** 306 * Calls the <code>close()</code> method when this ZipFile has not yet 307 * been explicitly closed. 308 */ finalize()309 protected void finalize() throws IOException 310 { 311 if (!closed && raf != null) close(); 312 } 313 314 /** 315 * Returns an enumeration of all Zip entries in this Zip file. 316 */ entries()317 public Enumeration entries() 318 { 319 try 320 { 321 return new ZipEntryEnumeration(getEntries().values().iterator()); 322 } 323 catch (IOException ioe) 324 { 325 return null; 326 } 327 } 328 329 /** 330 * Checks that the ZipFile is still open and reads entries when necessary. 331 * 332 * @exception IllegalStateException when the ZipFile has already been closed. 333 * @exception IOEexception when the entries could not be read. 334 */ getEntries()335 private HashMap getEntries() throws IOException 336 { 337 synchronized(raf) 338 { 339 if (closed) 340 throw new IllegalStateException("ZipFile has closed: " + name); 341 342 if (entries == null) 343 readEntries(); 344 345 return entries; 346 } 347 } 348 349 /** 350 * Searches for a zip entry in this archive with the given name. 351 * 352 * @param the name. May contain directory components separated by 353 * slashes ('/'). 354 * @return the zip entry, or null if no entry with that name exists. 355 */ getEntry(String name)356 public ZipEntry getEntry(String name) 357 { 358 try 359 { 360 HashMap entries = getEntries(); 361 ZipEntry entry = (ZipEntry) entries.get(name); 362 return entry != null ? (ZipEntry) entry.clone() : null; 363 } 364 catch (IOException ioe) 365 { 366 return null; 367 } 368 } 369 370 371 //access should be protected by synchronized(raf) 372 private byte[] locBuf = new byte[LOCHDR]; 373 374 /** 375 * Checks, if the local header of the entry at index i matches the 376 * central directory, and returns the offset to the data. 377 * 378 * @param entry to check. 379 * @return the start offset of the (compressed) data. 380 * 381 * @exception IOException if a i/o error occured. 382 * @exception ZipException if the local header doesn't match the 383 * central directory header 384 */ checkLocalHeader(ZipEntry entry)385 private long checkLocalHeader(ZipEntry entry) throws IOException 386 { 387 synchronized (raf) 388 { 389 raf.seek(entry.offset); 390 raf.readFully(locBuf); 391 392 if (readLeInt(locBuf, 0) != LOCSIG) 393 throw new ZipException("Wrong Local header signature: " + name); 394 395 if (entry.getMethod() != readLeShort(locBuf, LOCHOW)) 396 throw new ZipException("Compression method mismatch: " + name); 397 398 if (entry.getName().length() != readLeShort(locBuf, LOCNAM)) 399 throw new ZipException("file name length mismatch: " + name); 400 401 int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT); 402 return entry.offset + LOCHDR + extraLen; 403 } 404 } 405 406 /** 407 * Creates an input stream reading the given zip entry as 408 * uncompressed data. Normally zip entry should be an entry 409 * returned by getEntry() or entries(). 410 * 411 * @param entry the entry to create an InputStream for. 412 * @return the input stream. 413 * 414 * @exception IOException if a i/o error occured. 415 * @exception ZipException if the Zip archive is malformed. 416 */ getInputStream(ZipEntry entry)417 public InputStream getInputStream(ZipEntry entry) throws IOException 418 { 419 HashMap entries = getEntries(); 420 String name = entry.getName(); 421 ZipEntry zipEntry = (ZipEntry) entries.get(name); 422 if (zipEntry == null) 423 throw new NoSuchElementException(name); 424 425 long start = checkLocalHeader(zipEntry); 426 int method = zipEntry.getMethod(); 427 InputStream is = new BufferedInputStream(new PartialInputStream 428 (raf, start, zipEntry.getCompressedSize())); 429 switch (method) 430 { 431 case ZipOutputStream.STORED: 432 return is; 433 case ZipOutputStream.DEFLATED: 434 return new InflaterInputStream(is, new Inflater(true)); 435 default: 436 throw new ZipException("Unknown compression method " + method); 437 } 438 } 439 440 /** 441 * Returns the (path) name of this zip file. 442 */ getName()443 public String getName() 444 { 445 return name; 446 } 447 448 /** 449 * Returns the number of entries in this zip file. 450 */ size()451 public int size() 452 { 453 try 454 { 455 return getEntries().size(); 456 } 457 catch (IOException ioe) 458 { 459 return 0; 460 } 461 } 462 463 private static class ZipEntryEnumeration implements Enumeration 464 { 465 private final Iterator elements; 466 ZipEntryEnumeration(Iterator elements)467 public ZipEntryEnumeration(Iterator elements) 468 { 469 this.elements = elements; 470 } 471 hasMoreElements()472 public boolean hasMoreElements() 473 { 474 return elements.hasNext(); 475 } 476 nextElement()477 public Object nextElement() 478 { 479 /* We return a clone, just to be safe that the user doesn't 480 * change the entry. 481 */ 482 return ((ZipEntry)elements.next()).clone(); 483 } 484 } 485 486 private static class PartialInputStream extends InputStream 487 { 488 private final RandomAccessFile raf; 489 long filepos, end; 490 PartialInputStream(RandomAccessFile raf, long start, long len)491 public PartialInputStream(RandomAccessFile raf, long start, long len) 492 { 493 this.raf = raf; 494 filepos = start; 495 end = start + len; 496 } 497 available()498 public int available() 499 { 500 long amount = end - filepos; 501 if (amount > Integer.MAX_VALUE) 502 return Integer.MAX_VALUE; 503 return (int) amount; 504 } 505 read()506 public int read() throws IOException 507 { 508 if (filepos == end) 509 return -1; 510 synchronized (raf) 511 { 512 raf.seek(filepos++); 513 return raf.read(); 514 } 515 } 516 read(byte[] b, int off, int len)517 public int read(byte[] b, int off, int len) throws IOException 518 { 519 if (len > end - filepos) 520 { 521 len = (int) (end - filepos); 522 if (len == 0) 523 return -1; 524 } 525 synchronized (raf) 526 { 527 raf.seek(filepos); 528 int count = raf.read(b, off, len); 529 if (count > 0) 530 filepos += len; 531 return count; 532 } 533 } 534 skip(long amount)535 public long skip(long amount) 536 { 537 if (amount < 0) 538 throw new IllegalArgumentException(); 539 if (amount > end - filepos) 540 amount = end - filepos; 541 filepos += amount; 542 return amount; 543 } 544 } 545 } 546