ZipInputStream.java 9.17 KB
Newer Older
1 2
/* ZipInputStream.java --
   Copyright (C) 2001, 2002, 2003, 2004  Free Software Foundation, Inc.
Per Bothner committed
3

4
This file is part of GNU Classpath.
Per Bothner committed
5

6 7 8 9
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
10

11 12 13 14 15 16 17 18 19 20
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */
Per Bothner committed
37

38

Per Bothner committed
39
package java.util.zip;
40

41 42
import java.io.EOFException;
import java.io.IOException;
43
import java.io.InputStream;
Per Bothner committed
44 45

/**
46 47 48 49 50 51 52 53
 * This is a FilterInputStream that reads the files in an zip archive
 * one after another.  It has a special method to get the zip entry of
 * the next file.  The zip entry contains information about the file name
 * size, compressed size, CRC, etc.
 *
 * It includes support for STORED and DEFLATED entries.
 *
 * @author Jochen Hoenicke
Per Bothner committed
54
 */
55 56 57 58
public class ZipInputStream extends InflaterInputStream implements ZipConstants
{
  private CRC32 crc = new CRC32();
  private ZipEntry entry = null;
Per Bothner committed
59

60 61 62 63 64
  private int csize;
  private int size;
  private int method;
  private int flags;
  private int avail;
65
  private boolean entryAtEOF;
Per Bothner committed
66

67 68 69 70 71 72 73
  /**
   * Creates a new Zip input stream, reading a zip archive.
   */
  public ZipInputStream(InputStream in)
  {
    super(in, new Inflater(true));
  }
Per Bothner committed
74

75
  private void fillBuf() throws IOException
Per Bothner committed
76
  {
77
    avail = len = in.read(buf, 0, buf.length);
Per Bothner committed
78 79
  }

80
  private int readBuf(byte[] out, int offset, int length) throws IOException
Per Bothner committed
81
  {
82
    if (avail <= 0)
Per Bothner committed
83
      {
84 85 86
	fillBuf();
	if (avail <= 0)
	  return -1;
Per Bothner committed
87
      }
88 89 90 91 92 93 94 95 96 97 98 99
    if (length > avail)
      length = avail;
    System.arraycopy(buf, len - avail, out, offset, length);
    avail -= length;
    return length;
  }
  
  private void readFully(byte[] out) throws IOException
  {
    int off = 0;
    int len = out.length;
    while (len > 0)
Per Bothner committed
100
      {
101 102 103 104 105
	int count = readBuf(out, off, len);
	if (count == -1)
	  throw new EOFException();
	off += count;
	len -= count;
Per Bothner committed
106
      }
107 108
  }
  
109
  private int readLeByte() throws IOException
110 111
  {
    if (avail <= 0)
Per Bothner committed
112
      {
113 114 115
	fillBuf();
	if (avail <= 0)
	  throw new ZipException("EOF in header");
Per Bothner committed
116
      }
117
    return buf[len - avail--] & 0xff;
Per Bothner committed
118 119
  }

120 121 122
  /**
   * Read an unsigned short in little endian byte order.
   */
123
  private int readLeShort() throws IOException 
124
  {
125
    return readLeByte() | (readLeByte() << 8);
126 127
  }

128
  /**
129
   * Read an int in little endian byte order.
130
   */
131
  private int readLeInt() throws IOException 
132
  {
133
    return readLeShort() | (readLeShort() << 16);
134
  }
135

136 137 138 139 140
  /**
   * Open the next entry from the zip archive, and return its description.
   * If the previous entry wasn't closed, this method will close it.
   */
  public ZipEntry getNextEntry() throws IOException
Per Bothner committed
141
  {
142
    if (crc == null)
Mark Wielaard committed
143
      throw new IOException("Stream closed.");
144 145 146 147 148
    if (entry != null)
      closeEntry();

    int header = readLeInt();
    if (header == CENSIG)
149
      {
150 151 152
	/* Central Header reached. */
	close();
	return null;
153
      }
154
    if (header != LOCSIG)
155
      throw new ZipException("Wrong Local header signature: "
156
			     + Integer.toHexString(header));
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    /* skip version */
    readLeShort();
    flags = readLeShort();
    method = readLeShort();
    int dostime = readLeInt();
    int crc = readLeInt();
    csize = readLeInt();
    size = readLeInt();
    int nameLen = readLeShort();
    int extraLen = readLeShort();

    if (method == ZipOutputStream.STORED && csize != size)
      throw new ZipException("Stored, but compressed != uncompressed");


    byte[] buffer = new byte[nameLen];
    readFully(buffer);
    String name = new String(buffer);
    
    entry = createZipEntry(name);
177
    entryAtEOF = false;
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
    entry.setMethod(method);
    if ((flags & 8) == 0)
      {
	entry.setCrc(crc & 0xffffffffL);
	entry.setSize(size & 0xffffffffL);
	entry.setCompressedSize(csize & 0xffffffffL);
      }
    entry.setDOSTime(dostime);
    if (extraLen > 0)
      {
	byte[] extra = new byte[extraLen];
	readFully(extra);
	entry.setExtra(extra);
      }

    if (method == ZipOutputStream.DEFLATED && avail > 0)
      {
	System.arraycopy(buf, len - avail, buf, 0, avail);
	len = avail;
	avail = 0;
	inf.setInput(buf, 0, len);
      }
    return entry;
Per Bothner committed
201 202
  }

203
  private void readDataDescr() throws IOException
Per Bothner committed
204
  {
205 206 207 208 209 210 211
    if (readLeInt() != EXTSIG)
      throw new ZipException("Data descriptor signature not found");
    entry.setCrc(readLeInt() & 0xffffffffL);
    csize = readLeInt();
    size = readLeInt();
    entry.setSize(size & 0xffffffffL);
    entry.setCompressedSize(csize & 0xffffffffL);
Per Bothner committed
212 213
  }

214
  /**
215
   * Closes the current zip entry and moves to the next one.
216
   */
217
  public void closeEntry() throws IOException
218
  {
219
    if (crc == null)
Mark Wielaard committed
220
      throw new IOException("Stream closed.");
221 222
    if (entry == null)
      return;
223

224
    if (method == ZipOutputStream.DEFLATED)
Per Bothner committed
225
      {
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
	if ((flags & 8) != 0)
	  {
	    /* We don't know how much we must skip, read until end. */
	    byte[] tmp = new byte[2048];
	    while (read(tmp) > 0)
	      ;
	    /* read will close this entry */
	    return;
	  }
	csize -= inf.getTotalIn();
	avail = inf.getRemaining();
      }

    if (avail > csize && csize >= 0)
      avail -= csize;
    else
      {
	csize -= avail;
	avail = 0;
	while (csize != 0)
	  {
	    long skipped = in.skip(csize & 0xffffffffL);
	    if (skipped <= 0)
	      throw new ZipException("zip archive ends early.");
	    csize -= skipped;
	  }
Per Bothner committed
252
      }
253 254 255 256 257 258

    size = 0;
    crc.reset();
    if (method == ZipOutputStream.DEFLATED)
      inf.reset();
    entry = null;
259
    entryAtEOF = true;
Per Bothner committed
260 261
  }

262
  public int available() throws IOException
Per Bothner committed
263
  {
264
    return entryAtEOF ? 0 : 1;
Per Bothner committed
265 266
  }

267 268 269 270 271 272 273
  /**
   * Reads a byte from the current zip entry.
   * @return the byte or -1 on EOF.
   * @exception IOException if a i/o error occured.
   * @exception ZipException if the deflated stream is corrupted.
   */
  public int read() throws IOException
Per Bothner committed
274
  {
275 276 277 278
    byte[] b = new byte[1];
    if (read(b, 0, 1) <= 0)
      return -1;
    return b[0] & 0xff;
Per Bothner committed
279 280
  }

281 282 283 284 285 286 287 288
  /**
   * Reads a block of bytes from the current zip entry.
   * @return the number of bytes read (may be smaller, even before
   * EOF), or -1 on EOF.
   * @exception IOException if a i/o error occured.
   * @exception ZipException if the deflated stream is corrupted.
   */
  public int read(byte[] b, int off, int len) throws IOException
Per Bothner committed
289
  {
John Leuner committed
290 291
    if (len == 0)
      return 0;
292
    if (crc == null)
Mark Wielaard committed
293
      throw new IOException("Stream closed.");
294 295 296 297
    if (entry == null)
      return -1;
    boolean finished = false;
    switch (method)
Per Bothner committed
298
      {
299 300 301
      case ZipOutputStream.DEFLATED:
	len = super.read(b, off, len);
	if (len < 0)
Per Bothner committed
302
	  {
303 304 305 306 307 308 309 310 311 312 313
	    if (!inf.finished())
	      throw new ZipException("Inflater not finished!?");
	    avail = inf.getRemaining();
	    if ((flags & 8) != 0)
	      readDataDescr();

	    if (inf.getTotalIn() != csize
		|| inf.getTotalOut() != size)
	      throw new ZipException("size mismatch: "+csize+";"+size+" <-> "+inf.getTotalIn()+";"+inf.getTotalOut());
	    inf.reset();
	    finished = true;
Per Bothner committed
314
	  }
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
	break;
	
      case ZipOutputStream.STORED:

	if (len > csize && csize >= 0)
	  len = csize;
	
	len = readBuf(b, off, len);
	if (len > 0)
	  {
	    csize -= len;
	    size -= len;
	  }

	if (csize == 0)
	  finished = true;
	else if (len < 0)
	  throw new ZipException("EOF in stored block");
	break;
      }

    if (len > 0)
      crc.update(b, off, len);

    if (finished)
      {
	if ((crc.getValue() & 0xffffffffL) != entry.getCrc())
	  throw new ZipException("CRC mismatch");
	crc.reset();
	entry = null;
345
	entryAtEOF = true;
Per Bothner committed
346
      }
347
    return len;
Per Bothner committed
348 349
  }

350
  /**
351 352
   * Closes the zip file.
   * @exception IOException if a i/o error occured.
353
   */
354
  public void close() throws IOException
Per Bothner committed
355 356
  {
    super.close();
357 358
    crc = null;
    entry = null;
359
    entryAtEOF = true;
Per Bothner committed
360
  }
361

362 363 364 365 366 367 368 369 370
  /**
   * Creates a new zip entry for the given name.  This is equivalent
   * to new ZipEntry(name).
   * @param name the name of the zip entry.
   */
  protected ZipEntry createZipEntry(String name) 
  {
    return new ZipEntry(name);
  }
Per Bothner committed
371
}