    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    }