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