Source for java.util.zip.ZipFile

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