001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.commons.compress.archivers.zip;
020
021 import java.io.ByteArrayInputStream;
022 import java.io.ByteArrayOutputStream;
023 import java.io.EOFException;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.PushbackInputStream;
027 import java.util.zip.CRC32;
028 import java.util.zip.DataFormatException;
029 import java.util.zip.Inflater;
030 import java.util.zip.ZipEntry;
031 import java.util.zip.ZipException;
032
033 import org.apache.commons.compress.archivers.ArchiveEntry;
034 import org.apache.commons.compress.archivers.ArchiveInputStream;
035
036 import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
037 import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
038 import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
039 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
040
041 /**
042 * Implements an input stream that can read Zip archives.
043 *
044 * <p>Note that {@link ZipArchiveEntry#getSize()} may return -1 if the
045 * DEFLATE algorithm is used, as the size information is not available
046 * from the header.</p>
047 *
048 * <p>The {@link ZipFile} class is preferred when reading from files.</p>
049 *
050 * <p>As of Apache Commons Compress it transparently supports Zip64
051 * extensions and thus individual entries and archives larger than 4
052 * GB or with more than 65536 entries.</p>
053 *
054 * @see ZipFile
055 * @NotThreadSafe
056 */
057 public class ZipArchiveInputStream extends ArchiveInputStream {
058
059 /**
060 * The zip encoding to use for filenames and the file comment.
061 */
062 private final ZipEncoding zipEncoding;
063
064 /**
065 * Whether to look for and use Unicode extra fields.
066 */
067 private final boolean useUnicodeExtraFields;
068
069 /**
070 * Wrapped stream, will always be a PushbackInputStream.
071 */
072 private final InputStream in;
073
074 /**
075 * Inflater used for all deflated entries.
076 */
077 private final Inflater inf = new Inflater(true);
078
079 /**
080 * Calculates checkusms for all entries.
081 */
082 private final CRC32 crc = new CRC32();
083
084 /**
085 * Buffer used to read from the wrapped stream.
086 */
087 private final Buffer buf = new Buffer();
088 /**
089 * The entry that is currently being read.
090 */
091 private CurrentEntry current = null;
092 /**
093 * Whether the stream has been closed.
094 */
095 private boolean closed = false;
096 /**
097 * Whether the stream has reached the central directory - and thus
098 * found all entries.
099 */
100 private boolean hitCentralDirectory = false;
101 /**
102 * When reading a stored entry that uses the data descriptor this
103 * stream has to read the full entry and caches it. This is the
104 * cache.
105 */
106 private ByteArrayInputStream lastStoredEntry = null;
107
108 /**
109 * Whether the stream will try to read STORED entries that use a
110 * data descriptor.
111 */
112 private boolean allowStoredEntriesWithDataDescriptor = false;
113
114 private static final int LFH_LEN = 30;
115 /*
116 local file header signature 4 bytes (0x04034b50)
117 version needed to extract 2 bytes
118 general purpose bit flag 2 bytes
119 compression method 2 bytes
120 last mod file time 2 bytes
121 last mod file date 2 bytes
122 crc-32 4 bytes
123 compressed size 4 bytes
124 uncompressed size 4 bytes
125 file name length 2 bytes
126 extra field length 2 bytes
127 */
128
129 private static final long TWO_EXP_32 = ZIP64_MAGIC + 1;
130
131 public ZipArchiveInputStream(InputStream inputStream) {
132 this(inputStream, ZipEncodingHelper.UTF8, true);
133 }
134
135 /**
136 * @param encoding the encoding to use for file names, use null
137 * for the platform's default encoding
138 * @param useUnicodeExtraFields whether to use InfoZIP Unicode
139 * Extra Fields (if present) to set the file names.
140 */
141 public ZipArchiveInputStream(InputStream inputStream,
142 String encoding,
143 boolean useUnicodeExtraFields) {
144 this(inputStream, encoding, useUnicodeExtraFields, false);
145 }
146
147 /**
148 * @param encoding the encoding to use for file names, use null
149 * for the platform's default encoding
150 * @param useUnicodeExtraFields whether to use InfoZIP Unicode
151 * Extra Fields (if present) to set the file names.
152 * @param allowStoredEntriesWithDataDescriptor whether the stream
153 * will try to read STORED entries that use a data descriptor
154 * @since 1.1
155 */
156 public ZipArchiveInputStream(InputStream inputStream,
157 String encoding,
158 boolean useUnicodeExtraFields,
159 boolean allowStoredEntriesWithDataDescriptor) {
160 zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
161 this.useUnicodeExtraFields = useUnicodeExtraFields;
162 in = new PushbackInputStream(inputStream, buf.buf.length);
163 this.allowStoredEntriesWithDataDescriptor =
164 allowStoredEntriesWithDataDescriptor;
165 }
166
167 public ZipArchiveEntry getNextZipEntry() throws IOException {
168 if (closed || hitCentralDirectory) {
169 return null;
170 }
171 if (current != null) {
172 closeEntry();
173 }
174 byte[] lfh = new byte[LFH_LEN];
175 try {
176 readFully(lfh);
177 } catch (EOFException e) {
178 return null;
179 }
180 ZipLong sig = new ZipLong(lfh);
181 if (sig.equals(ZipLong.CFH_SIG)) {
182 hitCentralDirectory = true;
183 return null;
184 }
185 if (!sig.equals(ZipLong.LFH_SIG)) {
186 return null;
187 }
188
189 int off = WORD;
190 current = new CurrentEntry();
191
192 int versionMadeBy = ZipShort.getValue(lfh, off);
193 off += SHORT;
194 current.entry.setPlatform((versionMadeBy >> ZipFile.BYTE_SHIFT)
195 & ZipFile.NIBLET_MASK);
196
197 final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfh, off);
198 final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
199 final ZipEncoding entryEncoding =
200 hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
201 current.hasDataDescriptor = gpFlag.usesDataDescriptor();
202 current.entry.setGeneralPurposeBit(gpFlag);
203
204 off += SHORT;
205
206 current.entry.setMethod(ZipShort.getValue(lfh, off));
207 off += SHORT;
208
209 long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfh, off));
210 current.entry.setTime(time);
211 off += WORD;
212
213 ZipLong size = null, cSize = null;
214 if (!current.hasDataDescriptor) {
215 current.entry.setCrc(ZipLong.getValue(lfh, off));
216 off += WORD;
217
218 cSize = new ZipLong(lfh, off);
219 off += WORD;
220
221 size = new ZipLong(lfh, off);
222 off += WORD;
223 } else {
224 off += 3 * WORD;
225 }
226
227 int fileNameLen = ZipShort.getValue(lfh, off);
228
229 off += SHORT;
230
231 int extraLen = ZipShort.getValue(lfh, off);
232 off += SHORT;
233
234 byte[] fileName = new byte[fileNameLen];
235 readFully(fileName);
236 current.entry.setName(entryEncoding.decode(fileName), fileName);
237
238 byte[] extraData = new byte[extraLen];
239 readFully(extraData);
240 current.entry.setExtra(extraData);
241
242 if (!hasUTF8Flag && useUnicodeExtraFields) {
243 ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName,
244 null);
245 }
246
247 processZip64Extra(size, cSize);
248 return current.entry;
249 }
250
251 /**
252 * Records whether a Zip64 extra is present and sets the size
253 * information from it if sizes are 0xFFFFFFFF and the entry
254 * doesn't use a data descriptor.
255 */
256 private void processZip64Extra(ZipLong size, ZipLong cSize) {
257 Zip64ExtendedInformationExtraField z64 =
258 (Zip64ExtendedInformationExtraField)
259 current.entry.getExtraField(Zip64ExtendedInformationExtraField
260 .HEADER_ID);
261 current.usesZip64 = z64 != null;
262 if (!current.hasDataDescriptor) {
263 if (current.usesZip64 && (cSize.equals(ZipLong.ZIP64_MAGIC)
264 || size.equals(ZipLong.ZIP64_MAGIC))
265 ) {
266 current.entry.setCompressedSize(z64.getCompressedSize() // z64 cannot be null here
267 .getLongValue());
268 current.entry.setSize(z64.getSize().getLongValue());
269 } else {
270 current.entry.setCompressedSize(cSize.getValue());
271 current.entry.setSize(size.getValue());
272 }
273 }
274 }
275
276 /** {@inheritDoc} */
277 @Override
278 public ArchiveEntry getNextEntry() throws IOException {
279 return getNextZipEntry();
280 }
281
282 /**
283 * Whether this class is able to read the given entry.
284 *
285 * <p>May return false if it is set up to use encryption or a
286 * compression method that hasn't been implemented yet.</p>
287 * @since 1.1
288 */
289 @Override
290 public boolean canReadEntryData(ArchiveEntry ae) {
291 if (ae instanceof ZipArchiveEntry) {
292 ZipArchiveEntry ze = (ZipArchiveEntry) ae;
293 return ZipUtil.canHandleEntryData(ze)
294 && supportsDataDescriptorFor(ze);
295
296 }
297 return false;
298 }
299
300 @Override
301 public int read(byte[] buffer, int start, int length) throws IOException {
302 if (closed) {
303 throw new IOException("The stream is closed");
304 }
305 if (inf.finished() || current == null) {
306 return -1;
307 }
308
309 // avoid int overflow, check null buffer
310 if (start <= buffer.length && length >= 0 && start >= 0
311 && buffer.length - start >= length) {
312 ZipUtil.checkRequestedFeatures(current.entry);
313 if (!supportsDataDescriptorFor(current.entry)) {
314 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException
315 .Feature
316 .DATA_DESCRIPTOR,
317 current.entry);
318 }
319
320 if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) {
321 return readStored(buffer, start, length);
322 }
323 return readDeflated(buffer, start, length);
324 }
325 throw new ArrayIndexOutOfBoundsException();
326 }
327
328 /**
329 * Implementation of read for STORED entries.
330 */
331 private int readStored(byte[] buffer, int start, int length)
332 throws IOException {
333
334 if (current.hasDataDescriptor) {
335 if (lastStoredEntry == null) {
336 readStoredEntry();
337 }
338 return lastStoredEntry.read(buffer, start, length);
339 }
340
341 long csize = current.entry.getSize();
342 if (current.bytesRead >= csize) {
343 return -1;
344 }
345
346 if (buf.offsetInBuffer >= buf.lengthOfLastRead) {
347 buf.offsetInBuffer = 0;
348 if ((buf.lengthOfLastRead = in.read(buf.buf)) == -1) {
349 return -1;
350 }
351 count(buf.lengthOfLastRead);
352 current.bytesReadFromStream += buf.lengthOfLastRead;
353 }
354
355 int toRead = length > buf.lengthOfLastRead
356 ? buf.lengthOfLastRead - buf.offsetInBuffer
357 : length;
358 if ((csize - current.bytesRead) < toRead) {
359 // if it is smaller than toRead then it fits into an int
360 toRead = (int) (csize - current.bytesRead);
361 }
362 System.arraycopy(buf.buf, buf.offsetInBuffer, buffer, start, toRead);
363 buf.offsetInBuffer += toRead;
364 current.bytesRead += toRead;
365 crc.update(buffer, start, toRead);
366 return toRead;
367 }
368
369 /**
370 * Implementation of read for DEFLATED entries.
371 */
372 private int readDeflated(byte[] buffer, int start, int length)
373 throws IOException {
374 if (inf.needsInput()) {
375 fill();
376 if (buf.lengthOfLastRead > 0) {
377 current.bytesReadFromStream += buf.lengthOfLastRead;
378 }
379 }
380 int read = 0;
381 try {
382 read = inf.inflate(buffer, start, length);
383 } catch (DataFormatException e) {
384 throw new ZipException(e.getMessage());
385 }
386 if (read == 0) {
387 if (inf.finished()) {
388 return -1;
389 } else if (buf.lengthOfLastRead == -1) {
390 throw new IOException("Truncated ZIP file");
391 }
392 }
393 crc.update(buffer, start, read);
394 return read;
395 }
396
397 @Override
398 public void close() throws IOException {
399 if (!closed) {
400 closed = true;
401 in.close();
402 inf.end();
403 }
404 }
405
406 /**
407 * Skips over and discards value bytes of data from this input
408 * stream.
409 *
410 * <p>This implementation may end up skipping over some smaller
411 * number of bytes, possibly 0, if and only if it reaches the end
412 * of the underlying stream.</p>
413 *
414 * <p>The actual number of bytes skipped is returned.</p>
415 *
416 * @param value the number of bytes to be skipped.
417 * @return the actual number of bytes skipped.
418 * @throws IOException - if an I/O error occurs.
419 * @throws IllegalArgumentException - if value is negative.
420 */
421 @Override
422 public long skip(long value) throws IOException {
423 if (value >= 0) {
424 long skipped = 0;
425 byte[] b = new byte[1024];
426 while (skipped < value) {
427 long rem = value - skipped;
428 int x = read(b, 0, (int) (b.length > rem ? rem : b.length));
429 if (x == -1) {
430 return skipped;
431 }
432 skipped += x;
433 }
434 return skipped;
435 }
436 throw new IllegalArgumentException();
437 }
438
439 /**
440 * Checks if the signature matches what is expected for a zip file.
441 * Does not currently handle self-extracting zips which may have arbitrary
442 * leading content.
443 *
444 * @param signature
445 * the bytes to check
446 * @param length
447 * the number of bytes to check
448 * @return true, if this stream is a zip archive stream, false otherwise
449 */
450 public static boolean matches(byte[] signature, int length) {
451 if (length < ZipArchiveOutputStream.LFH_SIG.length) {
452 return false;
453 }
454
455 return checksig(signature, ZipArchiveOutputStream.LFH_SIG) // normal file
456 || checksig(signature, ZipArchiveOutputStream.EOCD_SIG); // empty zip
457 }
458
459 private static boolean checksig(byte[] signature, byte[] expected){
460 for (int i = 0; i < expected.length; i++) {
461 if (signature[i] != expected[i]) {
462 return false;
463 }
464 }
465 return true;
466 }
467
468 /**
469 * Closes the current ZIP archive entry and positions the underlying
470 * stream to the beginning of the next entry. All per-entry variables
471 * and data structures are cleared.
472 * <p>
473 * If the compressed size of this entry is included in the entry header,
474 * then any outstanding bytes are simply skipped from the underlying
475 * stream without uncompressing them. This allows an entry to be safely
476 * closed even if the compression method is unsupported.
477 * <p>
478 * In case we don't know the compressed size of this entry or have
479 * already buffered too much data from the underlying stream to support
480 * uncompression, then the uncompression process is completed and the
481 * end position of the stream is adjusted based on the result of that
482 * process.
483 *
484 * @throws IOException if an error occurs
485 */
486 private void closeEntry() throws IOException {
487 if (closed) {
488 throw new IOException("The stream is closed");
489 }
490 if (current == null) {
491 return;
492 }
493
494 // Ensure all entry bytes are read
495 if (current.bytesReadFromStream <= current.entry.getCompressedSize()
496 && !current.hasDataDescriptor) {
497 drainCurrentEntryData();
498 } else {
499 skip(Long.MAX_VALUE);
500
501 long inB =
502 current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED
503 ? getBytesInflated() : current.bytesRead;
504
505 // this is at most a single read() operation and can't
506 // exceed the range of int
507 int diff = (int) (current.bytesReadFromStream - inB);
508
509 // Pushback any required bytes
510 if (diff > 0) {
511 pushback(buf.buf, buf.lengthOfLastRead - diff, diff);
512 }
513 }
514
515 if (lastStoredEntry == null && current.hasDataDescriptor) {
516 readDataDescriptor();
517 }
518
519 inf.reset();
520 buf.reset();
521 crc.reset();
522 current = null;
523 lastStoredEntry = null;
524 }
525
526 /**
527 * Read all data of the current entry from the underlying stream
528 * that hasn't been read, yet.
529 */
530 private void drainCurrentEntryData() throws IOException {
531 long remaining = current.entry.getCompressedSize()
532 - current.bytesReadFromStream;
533 while (remaining > 0) {
534 long n = in.read(buf.buf, 0, (int) Math.min(buf.buf.length,
535 remaining));
536 if (n < 0) {
537 throw new EOFException(
538 "Truncated ZIP entry: " + current.entry.getName());
539 } else {
540 count(n);
541 remaining -= n;
542 }
543 }
544 }
545
546 /**
547 * Get the number of bytes Inflater has actually processed.
548 *
549 * <p>for Java < Java7 the getBytes* methods in
550 * Inflater/Deflater seem to return unsigned ints rather than
551 * longs that start over with 0 at 2^32.</p>
552 *
553 * <p>The stream knows how many bytes it has read, but not how
554 * many the Inflater actually consumed - it should be between the
555 * total number of bytes read for the entry and the total number
556 * minus the last read operation. Here we just try to make the
557 * value close enough to the bytes we've read by assuming the
558 * number of bytes consumed must be smaller than (or equal to) the
559 * number of bytes read but not smaller by more than 2^32.</p>
560 */
561 private long getBytesInflated() {
562 long inB = inf.getBytesRead();
563 if (current.bytesReadFromStream >= TWO_EXP_32) {
564 while (inB + TWO_EXP_32 <= current.bytesReadFromStream) {
565 inB += TWO_EXP_32;
566 }
567 }
568 return inB;
569 }
570
571 private void fill() throws IOException {
572 if (closed) {
573 throw new IOException("The stream is closed");
574 }
575 if ((buf.lengthOfLastRead = in.read(buf.buf)) > 0) {
576 count(buf.lengthOfLastRead);
577 inf.setInput(buf.buf, 0, buf.lengthOfLastRead);
578 }
579 }
580
581 private void readFully(byte[] b) throws IOException {
582 int count = 0, x = 0;
583 while (count != b.length) {
584 count += x = in.read(b, count, b.length - count);
585 if (x == -1) {
586 throw new EOFException();
587 }
588 count(x);
589 }
590 }
591
592 private void readDataDescriptor() throws IOException {
593 byte[] b = new byte[WORD];
594 readFully(b);
595 ZipLong val = new ZipLong(b);
596 if (ZipLong.DD_SIG.equals(val)) {
597 // data descriptor with signature, skip sig
598 readFully(b);
599 val = new ZipLong(b);
600 }
601 current.entry.setCrc(val.getValue());
602
603 // if there is a ZIP64 extra field, sizes are eight bytes
604 // each, otherwise four bytes each. Unfortunately some
605 // implementations - namely Java7 - use eight bytes without
606 // using a ZIP64 extra field -
607 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588
608
609 // just read 16 bytes and check whether bytes nine to twelve
610 // look like one of the signatures of what could follow a data
611 // descriptor (ignoring archive decryption headers for now).
612 // If so, push back eight bytes and assume sizes are four
613 // bytes, otherwise sizes are eight bytes each.
614 b = new byte[2 * DWORD];
615 readFully(b);
616 ZipLong potentialSig = new ZipLong(b, DWORD);
617 if (potentialSig.equals(ZipLong.CFH_SIG)
618 || potentialSig.equals(ZipLong.LFH_SIG)) {
619 pushback(b, DWORD, DWORD);
620 current.entry.setCompressedSize(ZipLong.getValue(b));
621 current.entry.setSize(ZipLong.getValue(b, WORD));
622 } else {
623 current.entry
624 .setCompressedSize(ZipEightByteInteger.getLongValue(b));
625 current.entry.setSize(ZipEightByteInteger.getLongValue(b, DWORD));
626 }
627 }
628
629 /**
630 * Whether this entry requires a data descriptor this library can work with.
631 *
632 * @return true if allowStoredEntriesWithDataDescriptor is true,
633 * the entry doesn't require any data descriptor or the method is
634 * DEFLATED.
635 */
636 private boolean supportsDataDescriptorFor(ZipArchiveEntry entry) {
637 return allowStoredEntriesWithDataDescriptor ||
638 !entry.getGeneralPurposeBit().usesDataDescriptor()
639 || entry.getMethod() == ZipEntry.DEFLATED;
640 }
641
642 /**
643 * Caches a stored entry that uses the data descriptor.
644 *
645 * <ul>
646 * <li>Reads a stored entry until the signature of a local file
647 * header, central directory header or data descriptor has been
648 * found.</li>
649 * <li>Stores all entry data in lastStoredEntry.</p>
650 * <li>Rewinds the stream to position at the data
651 * descriptor.</li>
652 * <li>reads the data descriptor</li>
653 * </ul>
654 *
655 * <p>After calling this method the entry should know its size,
656 * the entry's data is cached and the stream is positioned at the
657 * next local file or central directory header.</p>
658 */
659 private void readStoredEntry() throws IOException {
660 ByteArrayOutputStream bos = new ByteArrayOutputStream();
661 int off = 0;
662 boolean done = false;
663
664 // length of DD without signature
665 int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD;
666
667 while (!done) {
668 int r = in.read(buf.buf, off,
669 ZipArchiveOutputStream.BUFFER_SIZE - off);
670 if (r <= 0) {
671 // read the whole archive without ever finding a
672 // central directory
673 throw new IOException("Truncated ZIP file");
674 }
675 if (r + off < 4) {
676 // buf is too small to check for a signature, loop
677 off += r;
678 continue;
679 }
680
681 done = bufferContainsSignature(bos, off, r, ddLen);
682 if (!done) {
683 off = cacheBytesRead(bos, off, r, ddLen);
684 }
685 }
686
687 byte[] b = bos.toByteArray();
688 lastStoredEntry = new ByteArrayInputStream(b);
689 }
690
691 private static final byte[] LFH = ZipLong.LFH_SIG.getBytes();
692 private static final byte[] CFH = ZipLong.CFH_SIG.getBytes();
693 private static final byte[] DD = ZipLong.DD_SIG.getBytes();
694
695 /**
696 * Checks whether the current buffer contains the signature of a
697 * "data decsriptor", "local file header" or
698 * "central directory entry".
699 *
700 * <p>If it contains such a signature, reads the data descriptor
701 * and positions the stream right after the data descriptor.</p>
702 */
703 private boolean bufferContainsSignature(ByteArrayOutputStream bos,
704 int offset, int lastRead,
705 int expectedDDLen)
706 throws IOException {
707 boolean done = false;
708 int readTooMuch = 0;
709 for (int i = 0; !done && i < lastRead - 4; i++) {
710 if (buf.buf[i] == LFH[0] && buf.buf[i + 1] == LFH[1]) {
711 if ((buf.buf[i + 2] == LFH[2] && buf.buf[i + 3] == LFH[3])
712 || (buf.buf[i] == CFH[2] && buf.buf[i + 3] == CFH[3])) {
713 // found a LFH or CFH:
714 readTooMuch = offset + lastRead - i - expectedDDLen;
715 done = true;
716 }
717 else if (buf.buf[i + 2] == DD[2] && buf.buf[i + 3] == DD[3]) {
718 // found DD:
719 readTooMuch = offset + lastRead - i;
720 done = true;
721 }
722 if (done) {
723 // * push back bytes read in excess as well as the data
724 // descriptor
725 // * copy the remaining bytes to cache
726 // * read data descriptor
727 pushback(buf.buf, offset + lastRead - readTooMuch,
728 readTooMuch);
729 bos.write(buf.buf, 0, i);
730 readDataDescriptor();
731 }
732 }
733 }
734 return done;
735 }
736
737 /**
738 * If the last read bytes could hold a data descriptor and an
739 * incomplete signature then save the last bytes to the front of
740 * the buffer and cache everything in front of the potential data
741 * descriptor into the given ByteArrayOutputStream.
742 *
743 * <p>Data descriptor plus incomplete signature (3 bytes in the
744 * worst case) can be 20 bytes max.</p>
745 */
746 private int cacheBytesRead(ByteArrayOutputStream bos, int offset,
747 int lastRead, int expecteDDLen) {
748 final int cacheable = offset + lastRead - expecteDDLen - 3;
749 if (cacheable > 0) {
750 bos.write(buf.buf, 0, cacheable);
751 System.arraycopy(buf.buf, cacheable, buf.buf, 0,
752 expecteDDLen + 3);
753 offset = expecteDDLen + 3;
754 } else {
755 offset += lastRead;
756 }
757 return offset;
758 }
759
760 private void pushback(byte[] buf, int offset, int length)
761 throws IOException {
762 ((PushbackInputStream) in).unread(buf, offset, length);
763 pushedBackBytes(length);
764 }
765
766 /**
767 * Structure collecting information for the entry that is
768 * currently being read.
769 */
770 private static final class CurrentEntry {
771 /**
772 * Current ZIP entry.
773 */
774 private final ZipArchiveEntry entry = new ZipArchiveEntry();
775 /**
776 * Does the entry use a data descriptor?
777 */
778 private boolean hasDataDescriptor;
779 /**
780 * Does the entry have a ZIP64 extended information extra field.
781 */
782 private boolean usesZip64;
783 /**
784 * Number of bytes of entry content read by the client if the
785 * entry is STORED.
786 */
787 private long bytesRead;
788 /**
789 * Number of bytes of entry content read so from the stream.
790 *
791 * <p>This may be more than the actual entry's length as some
792 * stuff gets buffered up and needs to be pushed back when the
793 * end of the entry has been reached.</p>
794 */
795 private long bytesReadFromStream;
796 }
797
798 /**
799 * Contains a temporary buffer used to read from the wrapped
800 * stream together with some information needed for internal
801 * housekeeping.
802 */
803 private static final class Buffer {
804 /**
805 * Buffer used as temporary buffer when reading from the stream.
806 */
807 private final byte[] buf = new byte[ZipArchiveOutputStream.BUFFER_SIZE];
808 /**
809 * {@link #buf buf} may contain data the client hasnt read, yet,
810 * this is the first byte that hasn't been read so far.
811 */
812 private int offsetInBuffer = 0;
813 /**
814 * Number of bytes read from the wrapped stream into {@link #buf
815 * buf} with the last read operation.
816 */
817 private int lengthOfLastRead = 0;
818 /**
819 * Reset internal housekeeping.
820 */
821 private void reset() {
822 offsetInBuffer = lengthOfLastRead = 0;
823 }
824 }
825 }