42 * @author David Connelly
43 */
44 public
45 class ZipInputStream extends InflaterInputStream implements ZipConstants {
46 private ZipEntry entry;
47 private int flag;
48 private CRC32 crc = new CRC32();
49 private long remaining;
50 private byte[] tmpbuf = new byte[512];
51
52 private static final int STORED = ZipEntry.STORED;
53 private static final int DEFLATED = ZipEntry.DEFLATED;
54
55 private boolean closed = false;
56 // this flag is set to true after EOF has reached for
57 // one entry
58 private boolean entryEOF = false;
59
60 private ZipCoder zc;
61
62 /**
63 * Check to make sure that this stream has not been closed
64 */
65 private void ensureOpen() throws IOException {
66 if (closed) {
67 throw new IOException("Stream closed");
68 }
69 }
70
71 /**
72 * Creates a new ZIP input stream.
73 *
74 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
75 * decode the entry names.
76 *
77 * @param in the actual input stream
78 */
79 public ZipInputStream(InputStream in) {
80 this(in, StandardCharsets.UTF_8);
81 }
96 */
97 public ZipInputStream(InputStream in, Charset charset) {
98 super(new PushbackInputStream(in, 512), new Inflater(true), 512);
99 usesDefaultInflater = true;
100 if(in == null) {
101 throw new NullPointerException("in is null");
102 }
103 if (charset == null)
104 throw new NullPointerException("charset is null");
105 this.zc = ZipCoder.get(charset);
106 }
107
108 /**
109 * Reads the next ZIP file entry and positions the stream at the
110 * beginning of the entry data.
111 * @return the next ZIP file entry, or null if there are no more entries
112 * @exception ZipException if a ZIP file error has occurred
113 * @exception IOException if an I/O error has occurred
114 */
115 public ZipEntry getNextEntry() throws IOException {
116 ensureOpen();
117 if (entry != null) {
118 closeEntry();
119 }
120 crc.reset();
121 inf.reset();
122 if ((entry = readLOC()) == null) {
123 return null;
124 }
125 if (entry.method == STORED) {
126 remaining = entry.size;
127 }
128 entryEOF = false;
129 return entry;
130 }
131
132 /**
133 * Closes the current ZIP entry and positions the stream for reading the
134 * next entry.
135 * @exception ZipException if a ZIP file error has occurred
136 * @exception IOException if an I/O error has occurred
137 */
138 public void closeEntry() throws IOException {
139 ensureOpen();
140 while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
141 entryEOF = true;
142 }
196 readEnd(entry);
197 entryEOF = true;
198 entry = null;
199 } else {
200 crc.update(b, off, len);
201 }
202 return len;
203 case STORED:
204 if (remaining <= 0) {
205 entryEOF = true;
206 entry = null;
207 return -1;
208 }
209 if (len > remaining) {
210 len = (int)remaining;
211 }
212 len = in.read(b, off, len);
213 if (len == -1) {
214 throw new ZipException("unexpected EOF");
215 }
216 crc.update(b, off, len);
217 remaining -= len;
218 if (remaining == 0 && entry.crc != crc.getValue()) {
219 throw new ZipException(
220 "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
221 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
222 }
223 return len;
224 default:
225 throw new ZipException("invalid compression method");
226 }
227 }
228
229 /**
230 * Skips specified number of bytes in the current ZIP entry.
231 * @param n the number of bytes to skip
232 * @return the actual number of bytes skipped
233 * @exception ZipException if a ZIP file error has occurred
234 * @exception IOException if an I/O error has occurred
235 * @exception IllegalArgumentException if {@code n < 0}
256 return total;
257 }
258
259 /**
260 * Closes this input stream and releases any system resources associated
261 * with the stream.
262 * @exception IOException if an I/O error has occurred
263 */
264 public void close() throws IOException {
265 if (!closed) {
266 super.close();
267 closed = true;
268 }
269 }
270
271 private byte[] b = new byte[256];
272
273 /*
274 * Reads local file (LOC) header for next entry.
275 */
276 private ZipEntry readLOC() throws IOException {
277 try {
278 readFully(tmpbuf, 0, LOCHDR);
279 } catch (EOFException e) {
280 return null;
281 }
282 if (get32(tmpbuf, 0) != LOCSIG) {
283 return null;
284 }
285 // get flag first, we need check EFS.
286 flag = get16(tmpbuf, LOCFLG);
287 // get the entry name and create the ZipEntry first
288 int len = get16(tmpbuf, LOCNAM);
289 int blen = b.length;
290 if (len > blen) {
291 do {
292 blen = blen * 2;
293 } while (len > blen);
294 b = new byte[blen];
295 }
296 readFully(b, 0, len);
297 // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
298 ZipEntry e = createZipEntry(((flag & EFS) != 0)
299 ? zc.toStringUTF8(b, len)
300 : zc.toString(b, len));
301 // now get the remaining fields for the entry
302 if ((flag & 1) == 1) {
303 throw new ZipException("encrypted ZIP entry not supported");
304 }
305 e.method = get16(tmpbuf, LOCHOW);
306 e.xdostime = get32(tmpbuf, LOCTIM);
307 if ((flag & 8) == 8) {
308 /* "Data Descriptor" present */
309 if (e.method != DEFLATED) {
310 throw new ZipException(
311 "only DEFLATED entries can have EXT descriptor");
312 }
313 } else {
314 e.crc = get32(tmpbuf, LOCCRC);
315 e.csize = get32(tmpbuf, LOCSIZ);
316 e.size = get32(tmpbuf, LOCLEN);
317 }
318 len = get16(tmpbuf, LOCEXT);
319 if (len > 0) {
320 byte[] extra = new byte[len];
321 readFully(extra, 0, len);
322 e.setExtra0(extra,
323 e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL);
324 }
325 return e;
326 }
327
328 /**
329 * Creates a new <code>ZipEntry</code> object for the specified
330 * entry name.
331 *
332 * @param name the ZIP file entry name
333 * @return the ZipEntry just created
334 */
335 protected ZipEntry createZipEntry(String name) {
336 return new ZipEntry(name);
337 }
338
339 /**
340 * Reads end of deflated entry as well as EXT descriptor if present.
341 *
342 * Local headers for DEFLATED entries may optionally be followed by a
343 * data descriptor, and that data descriptor may optionally contain a
344 * leading signature (EXTSIG).
345 *
346 * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT
347 *
348 * """Although not originally assigned a signature, the value 0x08074b50
349 * has commonly been adopted as a signature value for the data descriptor
350 * record. Implementers should be aware that ZIP files may be
351 * encountered with or without this signature marking data descriptors
352 * and should account for either case when reading ZIP files to ensure
353 * compatibility."""
354 */
355 private void readEnd(ZipEntry e) throws IOException {
356 int n = inf.getRemaining();
357 if (n > 0) {
358 ((PushbackInputStream)in).unread(buf, len - n, n);
359 }
360 if ((flag & 8) == 8) {
361 /* "Data Descriptor" present */
362 if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
363 inf.getBytesRead() > ZIP64_MAGICVAL) {
364 // ZIP64 format
365 readFully(tmpbuf, 0, ZIP64_EXTHDR);
366 long sig = get32(tmpbuf, 0);
367 if (sig != EXTSIG) { // no EXTSIG present
368 e.crc = sig;
369 e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
370 e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
371 ((PushbackInputStream)in).unread(
372 tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC);
373 } else {
374 e.crc = get32(tmpbuf, ZIP64_EXTCRC);
375 e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
376 e.size = get64(tmpbuf, ZIP64_EXTLEN);
377 }
378 } else {
379 readFully(tmpbuf, 0, EXTHDR);
380 long sig = get32(tmpbuf, 0);
381 if (sig != EXTSIG) { // no EXTSIG present
382 e.crc = sig;
383 e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
384 e.size = get32(tmpbuf, EXTLEN - EXTCRC);
385 ((PushbackInputStream)in).unread(
386 tmpbuf, EXTHDR - EXTCRC, EXTCRC);
387 } else {
388 e.crc = get32(tmpbuf, EXTCRC);
389 e.csize = get32(tmpbuf, EXTSIZ);
390 e.size = get32(tmpbuf, EXTLEN);
391 }
392 }
393 }
394 if (e.size != inf.getBytesWritten()) {
395 throw new ZipException(
396 "invalid entry size (expected " + e.size +
397 " but got " + inf.getBytesWritten() + " bytes)");
398 }
399 if (e.csize != inf.getBytesRead()) {
400 throw new ZipException(
401 "invalid entry compressed size (expected " + e.csize +
402 " but got " + inf.getBytesRead() + " bytes)");
403 }
404 if (e.crc != crc.getValue()) {
405 throw new ZipException(
406 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
407 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
408 }
409 }
410
411 /*
412 * Reads bytes, blocking until all bytes are read.
413 */
414 private void readFully(byte[] b, int off, int len) throws IOException {
415 while (len > 0) {
416 int n = in.read(b, off, len);
417 if (n == -1) {
|
42 * @author David Connelly
43 */
44 public
45 class ZipInputStream extends InflaterInputStream implements ZipConstants {
46 private ZipEntry entry;
47 private int flag;
48 private CRC32 crc = new CRC32();
49 private long remaining;
50 private byte[] tmpbuf = new byte[512];
51
52 private static final int STORED = ZipEntry.STORED;
53 private static final int DEFLATED = ZipEntry.DEFLATED;
54
55 private boolean closed = false;
56 // this flag is set to true after EOF has reached for
57 // one entry
58 private boolean entryEOF = false;
59
60 private ZipCoder zc;
61
62 private ZipCryption zipCryption;
63
64 /**
65 * Check to make sure that this stream has not been closed
66 */
67 private void ensureOpen() throws IOException {
68 if (closed) {
69 throw new IOException("Stream closed");
70 }
71 }
72
73 /**
74 * Creates a new ZIP input stream.
75 *
76 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
77 * decode the entry names.
78 *
79 * @param in the actual input stream
80 */
81 public ZipInputStream(InputStream in) {
82 this(in, StandardCharsets.UTF_8);
83 }
98 */
99 public ZipInputStream(InputStream in, Charset charset) {
100 super(new PushbackInputStream(in, 512), new Inflater(true), 512);
101 usesDefaultInflater = true;
102 if(in == null) {
103 throw new NullPointerException("in is null");
104 }
105 if (charset == null)
106 throw new NullPointerException("charset is null");
107 this.zc = ZipCoder.get(charset);
108 }
109
110 /**
111 * Reads the next ZIP file entry and positions the stream at the
112 * beginning of the entry data.
113 * @return the next ZIP file entry, or null if there are no more entries
114 * @exception ZipException if a ZIP file error has occurred
115 * @exception IOException if an I/O error has occurred
116 */
117 public ZipEntry getNextEntry() throws IOException {
118 return getNextEntry(null);
119 }
120
121 /**
122 * Reads the next ZIP file entry and positions the stream at the
123 * beginning of the entry data.
124 * @param zipCryption instance of ZipCryption
125 * @return the next ZIP file entry, or null if there are no more entries
126 * @exception ZipException if a ZIP file error has occurred
127 * @exception IOException if an I/O error has occurred
128 */
129 public ZipEntry getNextEntry(ZipCryption zipCryption) throws IOException {
130 ensureOpen();
131 if (entry != null) {
132 closeEntry();
133 }
134 crc.reset();
135 inf.reset();
136 if ((entry = readLOC(zipCryption)) == null) {
137 return null;
138 }
139 if (entry.method == STORED) {
140 remaining = entry.size;
141 }
142 entryEOF = false;
143 return entry;
144 }
145
146 /**
147 * Closes the current ZIP entry and positions the stream for reading the
148 * next entry.
149 * @exception ZipException if a ZIP file error has occurred
150 * @exception IOException if an I/O error has occurred
151 */
152 public void closeEntry() throws IOException {
153 ensureOpen();
154 while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
155 entryEOF = true;
156 }
210 readEnd(entry);
211 entryEOF = true;
212 entry = null;
213 } else {
214 crc.update(b, off, len);
215 }
216 return len;
217 case STORED:
218 if (remaining <= 0) {
219 entryEOF = true;
220 entry = null;
221 return -1;
222 }
223 if (len > remaining) {
224 len = (int)remaining;
225 }
226 len = in.read(b, off, len);
227 if (len == -1) {
228 throw new ZipException("unexpected EOF");
229 }
230 if (zipCryption != null) {
231 zipCryption.decryptBytes(b, off, len);
232 }
233 crc.update(b, off, len);
234 remaining -= len;
235 if (remaining == 0 && entry.crc != crc.getValue()) {
236 throw new ZipException(
237 "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
238 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
239 }
240 return len;
241 default:
242 throw new ZipException("invalid compression method");
243 }
244 }
245
246 /**
247 * Skips specified number of bytes in the current ZIP entry.
248 * @param n the number of bytes to skip
249 * @return the actual number of bytes skipped
250 * @exception ZipException if a ZIP file error has occurred
251 * @exception IOException if an I/O error has occurred
252 * @exception IllegalArgumentException if {@code n < 0}
273 return total;
274 }
275
276 /**
277 * Closes this input stream and releases any system resources associated
278 * with the stream.
279 * @exception IOException if an I/O error has occurred
280 */
281 public void close() throws IOException {
282 if (!closed) {
283 super.close();
284 closed = true;
285 }
286 }
287
288 private byte[] b = new byte[256];
289
290 /*
291 * Reads local file (LOC) header for next entry.
292 */
293 private ZipEntry readLOC(ZipCryption zipCryption) throws IOException {
294 this.zipCryption = zipCryption;
295
296 try {
297 readFully(tmpbuf, 0, LOCHDR);
298 } catch (EOFException e) {
299 return null;
300 }
301 if (get32(tmpbuf, 0) != LOCSIG) {
302 return null;
303 }
304 // get flag first, we need check EFS and encryption.
305 flag = get16(tmpbuf, LOCFLG);
306 // get the entry name and create the ZipEntry first
307 int len = get16(tmpbuf, LOCNAM);
308 int blen = b.length;
309 if (len > blen) {
310 do {
311 blen = blen * 2;
312 } while (len > blen);
313 b = new byte[blen];
314 }
315 readFully(b, 0, len);
316 // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
317 ZipEntry e = createZipEntry(((flag & EFS) != 0)
318 ? zc.toStringUTF8(b, len)
319 : zc.toString(b, len));
320 e.flag = flag;
321 // now get the remaining fields for the entry
322 if (((flag & 1) == 1) && (zipCryption == null)) {
323 throw new ZipException("ZipCryption is required.");
324 }
325 e.method = get16(tmpbuf, LOCHOW);
326 e.xdostime = get32(tmpbuf, LOCTIM);
327 if ((flag & 8) == 8) {
328 /* "Data Descriptor" present */
329 if (e.method != DEFLATED) {
330 throw new ZipException(
331 "only DEFLATED entries can have EXT descriptor");
332 }
333 } else {
334 e.crc = get32(tmpbuf, LOCCRC);
335 e.csize = get32(tmpbuf, LOCSIZ);
336 e.size = get32(tmpbuf, LOCLEN);
337 }
338 len = get16(tmpbuf, LOCEXT);
339 if (len > 0) {
340 byte[] extra = new byte[len];
341 readFully(extra, 0, len);
342 e.setExtra0(extra,
343 e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL);
344 }
345
346 if (zipCryption != null) {
347 zipCryption.reset();
348 super.setZipCryption(zipCryption);
349
350 byte[] encryptionHeader =
351 new byte[zipCryption.getEncryptionHeaderSize()];
352 readFully(encryptionHeader, 0, encryptionHeader.length);
353 zipCryption.decryptBytes(encryptionHeader);
354
355 if (!zipCryption.isValid(e, encryptionHeader)) {
356 throw new ZipException("possibly incorrect passphrase");
357 }
358
359 }
360
361 return e;
362 }
363
364 /**
365 * Creates a new <code>ZipEntry</code> object for the specified
366 * entry name.
367 *
368 * @param name the ZIP file entry name
369 * @return the ZipEntry just created
370 */
371 protected ZipEntry createZipEntry(String name) {
372 return new ZipEntry(name);
373 }
374
375 /**
376 * Reads end of deflated entry as well as EXT descriptor if present.
377 *
378 * Local headers for DEFLATED entries may optionally be followed by a
379 * data descriptor, and that data descriptor may optionally contain a
380 * leading signature (EXTSIG).
381 *
382 * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT
383 *
384 * """Although not originally assigned a signature, the value 0x08074b50
385 * has commonly been adopted as a signature value for the data descriptor
386 * record. Implementers should be aware that ZIP files may be
387 * encountered with or without this signature marking data descriptors
388 * and should account for either case when reading ZIP files to ensure
389 * compatibility."""
390 */
391 private void readEnd(ZipEntry e) throws IOException {
392 int n = inf.getRemaining();
393 if (n > 0) {
394 ((PushbackInputStream)in).unread(
395 (zipCryption == null) ? buf : originBuf, len - n, n);
396 }
397 if ((flag & 8) == 8) {
398 /* "Data Descriptor" present */
399 if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
400 inf.getBytesRead() > ZIP64_MAGICVAL) {
401 // ZIP64 format
402 readFully(tmpbuf, 0, ZIP64_EXTHDR);
403 long sig = get32(tmpbuf, 0);
404 if (sig != EXTSIG) { // no EXTSIG present
405 e.crc = sig;
406 e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
407 e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
408 ((PushbackInputStream)in).unread(
409 tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC);
410 } else {
411 e.crc = get32(tmpbuf, ZIP64_EXTCRC);
412 e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
413 e.size = get64(tmpbuf, ZIP64_EXTLEN);
414 }
415 } else {
416 readFully(tmpbuf, 0, EXTHDR);
417 long sig = get32(tmpbuf, 0);
418 if (sig != EXTSIG) { // no EXTSIG present
419 e.crc = sig;
420 e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
421 e.size = get32(tmpbuf, EXTLEN - EXTCRC);
422 ((PushbackInputStream)in).unread(
423 tmpbuf, EXTHDR - EXTCRC, EXTCRC);
424 } else {
425 e.crc = get32(tmpbuf, EXTCRC);
426 e.csize = get32(tmpbuf, EXTSIZ);
427 e.size = get32(tmpbuf, EXTLEN);
428 }
429 }
430 }
431 if (e.size != inf.getBytesWritten()) {
432 throw new ZipException(
433 "invalid entry size (expected " + e.size +
434 " but got " + inf.getBytesWritten() + " bytes)");
435 }
436 if (zipCryption != null) {
437 e.csize -= zipCryption.getEncryptionHeaderSize();
438 }
439 if (e.csize != inf.getBytesRead()) {
440 throw new ZipException(
441 "invalid entry compressed size (expected " + e.csize +
442 " but got " + inf.getBytesRead() + " bytes)");
443 }
444 if (e.crc != crc.getValue()) {
445 throw new ZipException(
446 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
447 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
448 }
449 }
450
451 /*
452 * Reads bytes, blocking until all bytes are read.
453 */
454 private void readFully(byte[] b, int off, int len) throws IOException {
455 while (len > 0) {
456 int n = in.read(b, off, len);
457 if (n == -1) {
|