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 }
94 *
95 * @since 1.7
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 }
96 *
97 * @since 1.7
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 * @since 1.9
117 */
118 public ZipEntry getNextEntry() throws IOException {
119 return getNextEntry(null);
120 }
121
122 /**
123 * Reads the next ZIP file entry and positions the stream at the
124 * beginning of the entry data.
125 * @param zipCryption ZIP encrypt/decrypt engine. zip decryption will not
126 * work if this value set to null.
127 * @return the next ZIP file entry, or null if there are no more entries
128 * @exception ZipException if a ZIP file error has occurred
129 * @exception IOException if an I/O error has occurred
130 */
131 public ZipEntry getNextEntry(ZipCryption zipCryption) throws IOException {
132 ensureOpen();
133 if (entry != null) {
134 closeEntry();
135 }
136 crc.reset();
137 inf.reset();
138 if ((entry = readLOC(zipCryption)) == null) {
139 return null;
140 }
141 if (entry.method == STORED) {
142 remaining = entry.size;
143 }
144 entryEOF = false;
145 return entry;
146 }
147
148 /**
149 * Closes the current ZIP entry and positions the stream for reading the
150 * next entry.
151 * @exception ZipException if a ZIP file error has occurred
152 * @exception IOException if an I/O error has occurred
153 */
154 public void closeEntry() throws IOException {
155 ensureOpen();
156 while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
157 entryEOF = true;
158 }
212 readEnd(entry);
213 entryEOF = true;
214 entry = null;
215 } else {
216 crc.update(b, off, len);
217 }
218 return len;
219 case STORED:
220 if (remaining <= 0) {
221 entryEOF = true;
222 entry = null;
223 return -1;
224 }
225 if (len > remaining) {
226 len = (int)remaining;
227 }
228 len = in.read(b, off, len);
229 if (len == -1) {
230 throw new ZipException("unexpected EOF");
231 }
232 if (zipCryption != null) {
233 zipCryption.decryptBytes(b, off, len);
234 }
235 crc.update(b, off, len);
236 remaining -= len;
237 if (remaining == 0 && entry.crc != crc.getValue()) {
238 throw new ZipException(
239 "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
240 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
241 }
242 return len;
243 default:
244 throw new ZipException("invalid compression method");
245 }
246 }
247
248 /**
249 * Skips specified number of bytes in the current ZIP entry.
250 * @param n the number of bytes to skip
251 * @return the actual number of bytes skipped
252 * @exception ZipException if a ZIP file error has occurred
253 * @exception IOException if an I/O error has occurred
254 * @exception IllegalArgumentException if {@code n < 0}
275 return total;
276 }
277
278 /**
279 * Closes this input stream and releases any system resources associated
280 * with the stream.
281 * @exception IOException if an I/O error has occurred
282 */
283 public void close() throws IOException {
284 if (!closed) {
285 super.close();
286 closed = true;
287 }
288 }
289
290 private byte[] b = new byte[256];
291
292 /*
293 * Reads local file (LOC) header for next entry.
294 */
295 private ZipEntry readLOC(ZipCryption zipCryption) throws IOException {
296 this.zipCryption = zipCryption;
297
298 try {
299 readFully(tmpbuf, 0, LOCHDR);
300 } catch (EOFException e) {
301 return null;
302 }
303 if (get32(tmpbuf, 0) != LOCSIG) {
304 return null;
305 }
306 // get flag first, we need check EFS and encryption.
307 flag = get16(tmpbuf, LOCFLG);
308 // get the entry name and create the ZipEntry first
309 int len = get16(tmpbuf, LOCNAM);
310 int blen = b.length;
311 if (len > blen) {
312 do {
313 blen = blen * 2;
314 } while (len > blen);
315 b = new byte[blen];
316 }
317 readFully(b, 0, len);
318 // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8
319 ZipEntry e = createZipEntry(((flag & EFS) != 0)
320 ? zc.toStringUTF8(b, len)
321 : zc.toString(b, len));
322 e.flag = flag;
323 // now get the remaining fields for the entry
324 if (((flag & 1) == 1) && (zipCryption == null)) {
325 throw new ZipException("ZipCryption is required.");
326 }
327 e.method = get16(tmpbuf, LOCHOW);
328 e.xdostime = get32(tmpbuf, LOCTIM);
329 if ((flag & 8) == 8) {
330 /* "Data Descriptor" present */
331 if (e.method != DEFLATED) {
332 throw new ZipException(
333 "only DEFLATED entries can have EXT descriptor");
334 }
335 } else {
336 e.crc = get32(tmpbuf, LOCCRC);
337 e.csize = get32(tmpbuf, LOCSIZ);
338 e.size = get32(tmpbuf, LOCLEN);
339 }
340 len = get16(tmpbuf, LOCEXT);
341 if (len > 0) {
342 byte[] extra = new byte[len];
343 readFully(extra, 0, len);
344 e.setExtra0(extra,
345 e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL);
346 }
347
348 if (zipCryption != null) {
349 zipCryption.reset();
350 super.setZipCryption(zipCryption);
351
352 byte[] encryptionHeader =
353 new byte[zipCryption.getEncryptionHeaderSize()];
354 readFully(encryptionHeader, 0, encryptionHeader.length);
355 zipCryption.decryptBytes(encryptionHeader);
356
357 if (!zipCryption.isValid(e, encryptionHeader)) {
358 throw new ZipException("possibly incorrect passphrase");
359 }
360
361 }
362
363 return e;
364 }
365
366 /**
367 * Creates a new <code>ZipEntry</code> object for the specified
368 * entry name.
369 *
370 * @param name the ZIP file entry name
371 * @return the ZipEntry just created
372 */
373 protected ZipEntry createZipEntry(String name) {
374 return new ZipEntry(name);
375 }
376
377 /**
378 * Reads end of deflated entry as well as EXT descriptor if present.
379 *
380 * Local headers for DEFLATED entries may optionally be followed by a
381 * data descriptor, and that data descriptor may optionally contain a
382 * leading signature (EXTSIG).
383 *
384 * From the zip spec http://www.pkware.com/documents/casestudies/APPNOTE.TXT
385 *
386 * """Although not originally assigned a signature, the value 0x08074b50
387 * has commonly been adopted as a signature value for the data descriptor
388 * record. Implementers should be aware that ZIP files may be
389 * encountered with or without this signature marking data descriptors
390 * and should account for either case when reading ZIP files to ensure
391 * compatibility."""
392 */
393 private void readEnd(ZipEntry e) throws IOException {
394 int n = inf.getRemaining();
395 if (n > 0) {
396 ((PushbackInputStream)in).unread(
397 (zipCryption == null) ? buf : originBuf, len - n, n);
398 }
399 if ((flag & 8) == 8) {
400 /* "Data Descriptor" present */
401 if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
402 inf.getBytesRead() > ZIP64_MAGICVAL) {
403 // ZIP64 format
404 readFully(tmpbuf, 0, ZIP64_EXTHDR);
405 long sig = get32(tmpbuf, 0);
406 if (sig != EXTSIG) { // no EXTSIG present
407 e.crc = sig;
408 e.csize = get64(tmpbuf, ZIP64_EXTSIZ - ZIP64_EXTCRC);
409 e.size = get64(tmpbuf, ZIP64_EXTLEN - ZIP64_EXTCRC);
410 ((PushbackInputStream)in).unread(
411 tmpbuf, ZIP64_EXTHDR - ZIP64_EXTCRC, ZIP64_EXTCRC);
412 } else {
413 e.crc = get32(tmpbuf, ZIP64_EXTCRC);
414 e.csize = get64(tmpbuf, ZIP64_EXTSIZ);
415 e.size = get64(tmpbuf, ZIP64_EXTLEN);
416 }
417 } else {
418 readFully(tmpbuf, 0, EXTHDR);
419 long sig = get32(tmpbuf, 0);
420 if (sig != EXTSIG) { // no EXTSIG present
421 e.crc = sig;
422 e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
423 e.size = get32(tmpbuf, EXTLEN - EXTCRC);
424 ((PushbackInputStream)in).unread(
425 tmpbuf, EXTHDR - EXTCRC, EXTCRC);
426 } else {
427 e.crc = get32(tmpbuf, EXTCRC);
428 e.csize = get32(tmpbuf, EXTSIZ);
429 e.size = get32(tmpbuf, EXTLEN);
430 }
431 }
432 }
433 if (e.size != inf.getBytesWritten()) {
434 throw new ZipException(
435 "invalid entry size (expected " + e.size +
436 " but got " + inf.getBytesWritten() + " bytes)");
437 }
438 if (zipCryption != null) {
439 e.csize -= zipCryption.getEncryptionHeaderSize();
440 }
441 if (e.csize != inf.getBytesRead()) {
442 throw new ZipException(
443 "invalid entry compressed size (expected " + e.csize +
444 " but got " + inf.getBytesRead() + " bytes)");
445 }
446 if (e.crc != crc.getValue()) {
447 throw new ZipException(
448 "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
449 " but got 0x" + Long.toHexString(crc.getValue()) + ")");
450 }
451 }
452
453 /*
454 * Reads bytes, blocking until all bytes are read.
455 */
456 private void readFully(byte[] b, int off, int len) throws IOException {
457 while (len > 0) {
458 int n = in.read(b, off, len);
459 if (n == -1) {
|