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 &lt; 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         * &quot;data decsriptor&quot;, &quot;local file header&quot; or
697         * &quot;central directory entry&quot;.
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    }