001: /*
002: * @(#)ZipInputStream.java 1.37 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.util.zip;
029:
030: import java.io.InputStream;
031: import java.io.IOException;
032: import java.io.EOFException;
033: import java.io.PushbackInputStream;
034:
035: /**
036: * This class implements an input stream filter for reading files in the
037: * ZIP file format. Includes support for both compressed and uncompressed
038: * entries.
039: *
040: * @author David Connelly
041: * @version 1.28, 02/02/00
042: */
043: public class ZipInputStream extends InflaterInputStream implements
044: ZipConstants {
045: private ZipEntry entry;
046: private CRC32 crc = new CRC32();
047: private long remaining;
048: private byte[] tmpbuf = new byte[512];
049:
050: private static final int STORED = ZipEntry.STORED;
051: private static final int DEFLATED = ZipEntry.DEFLATED;
052:
053: private boolean closed = false;
054: // this flag is set to true after EOF has reached for
055: // one entry
056: private boolean entryEOF = false;
057:
058: /**
059: * Check to make sure that this stream has not been closed
060: */
061: private void ensureOpen() throws IOException {
062: if (closed) {
063: throw new IOException("Stream closed");
064: }
065: }
066:
067: /**
068: * Creates a new ZIP input stream.
069: * @param in the actual input stream
070: */
071: public ZipInputStream(InputStream in) {
072: super (new PushbackInputStream(in, 512), new Inflater(true), 512);
073: usesDefaultInflater = true;
074: if (in == null) {
075: throw new NullPointerException("in is null");
076: }
077: }
078:
079: /**
080: * Reads the next ZIP file entry and positions stream at the beginning
081: * of the entry data.
082: * @return the ZipEntry just read
083: * @exception ZipException if a ZIP file error has occurred
084: * @exception IOException if an I/O error has occurred
085: */
086: public ZipEntry getNextEntry() throws IOException {
087: ensureOpen();
088: if (entry != null) {
089: closeEntry();
090: }
091: crc.reset();
092: inf.reset();
093: if ((entry = readLOC()) == null) {
094: return null;
095: }
096: if (entry.method == STORED) {
097: remaining = entry.size;
098: }
099: entryEOF = false;
100: return entry;
101: }
102:
103: /**
104: * Closes the current ZIP entry and positions the stream for reading the
105: * next entry.
106: * @exception ZipException if a ZIP file error has occurred
107: * @exception IOException if an I/O error has occurred
108: */
109: public void closeEntry() throws IOException {
110: ensureOpen();
111: while (read(tmpbuf, 0, tmpbuf.length) != -1)
112: ;
113: entryEOF = true;
114: }
115:
116: /**
117: * Returns 0 after EOF has reached for the current entry data,
118: * otherwise always return 1.
119: * <p>
120: * Programs should not count on this method to return the actual number
121: * of bytes that could be read without blocking.
122: *
123: * @return 1 before EOF and 0 after EOF has reached for current entry.
124: * @exception IOException if an I/O error occurs.
125: *
126: */
127: public int available() throws IOException {
128: ensureOpen();
129: if (entryEOF) {
130: return 0;
131: } else {
132: return 1;
133: }
134: }
135:
136: /**
137: * Reads from the current ZIP entry into an array of bytes. Blocks until
138: * some input is available.
139: * @param b the buffer into which the data is read
140: * @param off the start offset of the data
141: * @param len the maximum number of bytes read
142: * @return the actual number of bytes read, or -1 if the end of the
143: * entry is reached
144: * @exception ZipException if a ZIP file error has occurred
145: * @exception IOException if an I/O error has occurred
146: */
147: public int read(byte[] b, int off, int len) throws IOException {
148: ensureOpen();
149: if (off < 0 || len < 0 || off > b.length - len) {
150: throw new IndexOutOfBoundsException();
151: } else if (len == 0) {
152: return 0;
153: }
154:
155: if (entry == null) {
156: return -1;
157: }
158: switch (entry.method) {
159: case DEFLATED:
160: len = super .read(b, off, len);
161: if (len == -1) {
162: readEnd(entry);
163: entryEOF = true;
164: entry = null;
165: } else {
166: crc.update(b, off, len);
167: }
168: return len;
169: case STORED:
170: if (remaining <= 0) {
171: entryEOF = true;
172: entry = null;
173: return -1;
174: }
175: if (len > remaining) {
176: len = (int) remaining;
177: }
178: len = in.read(b, off, len);
179: if (len == -1) {
180: throw new ZipException("unexpected EOF");
181: }
182: crc.update(b, off, len);
183: remaining -= len;
184: return len;
185: default:
186: throw new InternalError("invalid compression method");
187: }
188: }
189:
190: /**
191: * Skips specified number of bytes in the current ZIP entry.
192: * @param n the number of bytes to skip
193: * @return the actual number of bytes skipped
194: * @exception ZipException if a ZIP file error has occurred
195: * @exception IOException if an I/O error has occurred
196: * @exception IllegalArgumentException if n < 0
197: */
198: public long skip(long n) throws IOException {
199: if (n < 0) {
200: throw new IllegalArgumentException("negative skip length");
201: }
202: ensureOpen();
203: int max = (int) Math.min(n, Integer.MAX_VALUE);
204: int total = 0;
205: while (total < max) {
206: int len = max - total;
207: if (len > tmpbuf.length) {
208: len = tmpbuf.length;
209: }
210: len = read(tmpbuf, 0, len);
211: if (len == -1) {
212: entryEOF = true;
213: break;
214: }
215: total += len;
216: }
217: return total;
218: }
219:
220: /**
221: * Closes the ZIP input stream.
222: * @exception IOException if an I/O error has occurred
223: */
224: public void close() throws IOException {
225: if (!closed) {
226: super .close();
227: closed = true;
228: }
229: }
230:
231: private byte[] b = new byte[256];
232:
233: /*
234: * Reads local file (LOC) header for next entry.
235: */
236: private ZipEntry readLOC() throws IOException {
237: try {
238: readFully(tmpbuf, 0, LOCHDR);
239: } catch (EOFException e) {
240: return null;
241: }
242: if (get32(tmpbuf, 0) != LOCSIG) {
243: return null;
244: }
245: // get the entry name and create the ZipEntry first
246: int len = get16(tmpbuf, LOCNAM);
247: if (len == 0) {
248: throw new ZipException("missing entry name");
249: }
250: int blen = b.length;
251: if (len > blen) {
252: do
253: blen = blen * 2;
254: while (len > blen);
255: b = new byte[blen];
256: }
257: readFully(b, 0, len);
258: ZipEntry e = createZipEntry(getUTF8String(b, 0, len));
259: // now get the remaining fields for the entry
260: e.version = get16(tmpbuf, LOCVER);
261: e.flag = get16(tmpbuf, LOCFLG);
262: if ((e.flag & 1) == 1) {
263: throw new ZipException("encrypted ZIP entry not supported");
264: }
265: e.method = get16(tmpbuf, LOCHOW);
266: e.time = get32(tmpbuf, LOCTIM);
267: if ((e.flag & 8) == 8) {
268: /* EXT descriptor present */
269: if (e.method != DEFLATED) {
270: throw new ZipException(
271: "only DEFLATED entries can have EXT descriptor");
272: }
273: } else {
274: e.crc = get32(tmpbuf, LOCCRC);
275: e.csize = get32(tmpbuf, LOCSIZ);
276: e.size = get32(tmpbuf, LOCLEN);
277: }
278: len = get16(tmpbuf, LOCEXT);
279: if (len > 0) {
280: byte[] bb = new byte[len];
281: readFully(bb, 0, len);
282: e.extra = bb;
283: }
284: return e;
285: }
286:
287: /*
288: * Fetches a UTF8-encoded String from the specified byte array.
289: */
290: private static String getUTF8String(byte[] b, int off, int len) {
291: // First, count the number of characters in the sequence
292: int count = 0;
293: int max = off + len;
294: int i = off;
295: while (i < max) {
296: int c = b[i++] & 0xff;
297: switch (c >> 4) {
298: case 0:
299: case 1:
300: case 2:
301: case 3:
302: case 4:
303: case 5:
304: case 6:
305: case 7:
306: // 0xxxxxxx
307: count++;
308: break;
309: case 12:
310: case 13:
311: // 110xxxxx 10xxxxxx
312: if ((int) (b[i++] & 0xc0) != 0x80) {
313: throw new IllegalArgumentException();
314: }
315: count++;
316: break;
317: case 14:
318: // 1110xxxx 10xxxxxx 10xxxxxx
319: if (((int) (b[i++] & 0xc0) != 0x80)
320: || ((int) (b[i++] & 0xc0) != 0x80)) {
321: throw new IllegalArgumentException();
322: }
323: count++;
324: break;
325: default:
326: // 10xxxxxx, 1111xxxx
327: throw new IllegalArgumentException();
328: }
329: }
330: if (i != max) {
331: throw new IllegalArgumentException();
332: }
333: // Now decode the characters...
334: char[] cs = new char[count];
335: i = 0;
336: while (off < max) {
337: int c = b[off++] & 0xff;
338: switch (c >> 4) {
339: case 0:
340: case 1:
341: case 2:
342: case 3:
343: case 4:
344: case 5:
345: case 6:
346: case 7:
347: // 0xxxxxxx
348: cs[i++] = (char) c;
349: break;
350: case 12:
351: case 13:
352: // 110xxxxx 10xxxxxx
353: cs[i++] = (char) (((c & 0x1f) << 6) | (b[off++] & 0x3f));
354: break;
355: case 14:
356: // 1110xxxx 10xxxxxx 10xxxxxx
357: int t = (b[off++] & 0x3f) << 6;
358: cs[i++] = (char) (((c & 0x0f) << 12) | t | (b[off++] & 0x3f));
359: break;
360: default:
361: // 10xxxxxx, 1111xxxx
362: throw new IllegalArgumentException();
363: }
364: }
365: return new String(cs, 0, count);
366: }
367:
368: /**
369: * Creates a new <code>ZipEntry</code> object for the specified
370: * entry name.
371: *
372: * @param name the ZIP file entry name
373: * @return the ZipEntry just created
374: */
375: protected ZipEntry createZipEntry(String name) {
376: return new ZipEntry(name);
377: }
378:
379: /*
380: * Reads end of deflated entry as well as EXT descriptor if present.
381: */
382: private void readEnd(ZipEntry e) throws IOException {
383: int n = inf.getRemaining();
384: if (n > 0) {
385: ((PushbackInputStream) in).unread(buf, len - n, n);
386: }
387: if ((e.flag & 8) == 8) {
388: /* EXT descriptor present */
389: readFully(tmpbuf, 0, EXTHDR);
390: long sig = get32(tmpbuf, 0);
391: if (sig != EXTSIG) { // no EXTSIG present
392: e.crc = sig;
393: e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
394: e.size = get32(tmpbuf, EXTLEN - EXTCRC);
395: ((PushbackInputStream) in).unread(tmpbuf, EXTHDR
396: - EXTCRC - 1, EXTCRC);
397: } else {
398: e.crc = get32(tmpbuf, EXTCRC);
399: e.csize = get32(tmpbuf, EXTSIZ);
400: e.size = get32(tmpbuf, EXTLEN);
401: }
402: }
403: if (e.size != inf.getTotalOut()) {
404: throw new ZipException("invalid entry size (expected "
405: + e.size + " but got " + inf.getTotalOut()
406: + " bytes)");
407: }
408: if (e.csize != inf.getTotalIn()) {
409: throw new ZipException(
410: "invalid entry compressed size (expected "
411: + e.csize + " but got " + inf.getTotalIn()
412: + " bytes)");
413: }
414: if (e.crc != crc.getValue()) {
415: throw new ZipException("invalid entry CRC (expected 0x"
416: + Long.toHexString(e.crc) + " but got 0x"
417: + Long.toHexString(crc.getValue()) + ")");
418: }
419: }
420:
421: /*
422: * Reads bytes, blocking until all bytes are read.
423: */
424: private void readFully(byte[] b, int off, int len)
425: throws IOException {
426: while (len > 0) {
427: int n = in.read(b, off, len);
428: if (n == -1) {
429: throw new EOFException();
430: }
431: off += n;
432: len -= n;
433: }
434: }
435:
436: /*
437: * Fetches unsigned 16-bit value from byte array at specified offset.
438: * The bytes are assumed to be in little-endian byte order.
439: */
440: private static final int get16(byte b[], int off) {
441: return (b[off] & 0xff) | ((b[off + 1] & 0xff) << 8);
442: }
443:
444: /*
445: * Fetches unsigned 32-bit value from byte array at specified offset.
446: * The bytes are assumed to be in little-endian byte order.
447: */
448: private static final long get32(byte b[], int off) {
449: return get16(b, off) | ((long) get16(b, off + 2) << 16);
450: }
451: }
|