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