    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 */
    019package org.apache.commons.compress.archivers.zip;
    020
    021import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
    022import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
    023import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
    024import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
    025
    026import java.io.ByteArrayInputStream;
    027import java.io.ByteArrayOutputStream;
    028import java.io.EOFException;
    029import java.io.IOException;
    030import java.io.InputStream;
    031import java.io.PushbackInputStream;
    032import java.math.BigInteger;
    033import java.nio.ByteBuffer;
    034import java.nio.charset.StandardCharsets;
    035import java.util.Arrays;
    036import java.util.Objects;
    037import java.util.function.Function;
    038import java.util.zip.CRC32;
    039import java.util.zip.DataFormatException;
    040import java.util.zip.Inflater;
    041import java.util.zip.ZipEntry;
    042import java.util.zip.ZipException;
    043
    044import org.apache.commons.compress.archivers.ArchiveEntry;
    045import org.apache.commons.compress.archivers.ArchiveInputStream;
    046import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
    047import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
    048import org.apache.commons.compress.utils.ArchiveUtils;
    049import org.apache.commons.compress.utils.IOUtils;
    050import org.apache.commons.compress.utils.InputStreamStatistics;
    051import org.apache.commons.io.input.BoundedInputStream;
    052
    053/**
    054 * Implements an input stream that can read Zip archives.
    055 * <p>
    056 * As of Apache Commons Compress it transparently supports Zip64 extensions and thus individual entries and archives larger than 4 GB or with more than 65,536
    057 * entries.
    058 * </p>
    059 * <p>
    060 * The {@link ZipFile} class is preferred when reading from files as {@link ZipArchiveInputStream} is limited by not being able to read the central directory
    061 * header before returning entries. In particular {@link ZipArchiveInputStream}
    062 * </p>
    063 * <ul>
    064 * <li>may return entries that are not part of the central directory at all and shouldn't be considered part of the archive.</li>
    065 * <li>may return several entries with the same name.</li>
    066 * <li>will not return internal or external attributes.</li>
    067 * <li>may return incomplete extra field data.</li>
    068 * <li>may return unknown sizes and CRC values for entries until the next entry has been reached if the archive uses the data descriptor feature.</li>
    069 * </ul>
    070 *
    071 * @see ZipFile
    072 * @NotThreadSafe
    073 */
    074public class ZipArchiveInputStream extends ArchiveInputStream<ZipArchiveEntry> implements InputStreamStatistics {
    075
    076    /**
    077     * Input stream adapted from commons-io.
    078     */
    079    private final class BoundCountInputStream extends BoundedInputStream {
    080
    081        // TODO Consider how to do this from a final class, an IO class, or basically without the current side-effect implementation.
    082
    083        /**
    084         * Creates a new {@code BoundedInputStream} that wraps the given input stream and limits it to a certain size.
    085         *
    086         * @param in   The wrapped input stream
    087         * @param max The maximum number of bytes to return
    088         */
    089        BoundCountInputStream(final InputStream in, final long max) {
    090            super(in, max);
    091        }
    092
    093        private boolean atMaxLength() {
    094            return getMaxCount() >= 0 && getCount() >= getMaxCount();
    095        }
    096
    097        @Override
    098        public int read() throws IOException {
    099            if (atMaxLength()) {
    100                return -1;
    101            }
    102            final int result = super.read();
    103            if (result != -1) {
    104                readCount(1);
    105            }
    106            return result;
    107        }
    108
    109        @Override
    110        public int read(final byte[] b, final int off, final int len) throws IOException {
    111            if (len == 0) {
    112                return 0;
    113            }
    114            if (atMaxLength()) {
    115                return -1;
    116            }
    117            final long maxRead = getMaxCount() >= 0 ? Math.min(len, getMaxCount() - getCount()) : len;
    118            return readCount(super.read(b, off, (int) maxRead));
    119        }
    120
    121        private int readCount(final int bytesRead) {
    122            if (bytesRead != -1) {
    123                count(bytesRead);
    124                current.bytesReadFromStream += bytesRead;
    125            }
    126            return bytesRead;
    127        }
    128
    129    }
    130
    131    /**
    132     * Structure collecting information for the entry that is currently being read.
    133     */
    134    private static final class CurrentEntry {
    135
    136        /**
    137         * Current ZIP entry.
    138         */
    139        private final ZipArchiveEntry entry = new ZipArchiveEntry();
    140
    141        /**
    142         * Does the entry use a data descriptor?
    143         */
    144        private boolean hasDataDescriptor;
    145
    146        /**
    147         * Does the entry have a ZIP64 extended information extra field.
    148         */
    149        private boolean usesZip64;
    150
    151        /**
    152         * Number of bytes of entry content read by the client if the entry is STORED.
    153         */
    154        private long bytesRead;
    155
    156        /**
    157         * Number of bytes of entry content read from the stream.
    158         * <p>
    159         * This may be more than the actual entry's length as some stuff gets buffered up and needs to be pushed back when the end of the entry has been
    160         * reached.
    161         * </p>
    162         */
    163        private long bytesReadFromStream;
    164
    165        /**
    166         * The checksum calculated as the current entry is read.
    167         */
    168        private final CRC32 crc = new CRC32();
    169
    170        /**
    171         * The input stream decompressing the data for shrunk and imploded entries.
    172         */
    173        private InputStream inputStream;
    174
    175        @SuppressWarnings("unchecked") // Caller beware
    176        private <T extends InputStream> T checkInputStream() {
    177            return (T) Objects.requireNonNull(inputStream, "inputStream");
    178        }
    179    }
    180
    181    public static final int PREAMBLE_GARBAGE_MAX_SIZE = 4096;
    182
    183    private static final int LFH_LEN = 30;
    184
    185    /*
    186     * local file header signature WORD version needed to extract SHORT general purpose bit flag SHORT compression method SHORT last mod file time SHORT last
    187     * mod file date SHORT crc-32 WORD compressed size WORD uncompressed size WORD file name length SHORT extra field length SHORT
    188     */
    189    private static final int CFH_LEN = 46;
    190
    191    /*
    192     * central file header signature WORD version made by SHORT version needed to extract SHORT general purpose bit flag SHORT compression method SHORT last mod
    193     * file time SHORT last mod file date SHORT crc-32 WORD compressed size WORD uncompressed size WORD file name length SHORT extra field length SHORT file
    194     * comment length SHORT disk number start SHORT internal file attributes SHORT external file attributes WORD relative offset of local header WORD
    195     */
    196    private static final long TWO_EXP_32 = ZIP64_MAGIC + 1;
    197
    198    private static final String USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER = " while reading a stored entry using data descriptor. Either the archive is broken"
    199            + " or it can not be read using ZipArchiveInputStream and you must use ZipFile."
    200            + " A common cause for this is a ZIP archive containing a ZIP archive."
    201            + " See https://commons.apache.org/proper/commons-compress/zip.html#ZipArchiveInputStream_vs_ZipFile";
    202
    203    private static final byte[] LFH = ZipLong.LFH_SIG.getBytes();
    204
    205    private static final byte[] CFH = ZipLong.CFH_SIG.getBytes();
    206
    207    private static final byte[] DD = ZipLong.DD_SIG.getBytes();
    208
    209    private static final byte[] APK_SIGNING_BLOCK_MAGIC = { 'A', 'P', 'K', ' ', 'S', 'i', 'g', ' ', 'B', 'l', 'o', 'c', 'k', ' ', '4', '2', };
    210
    211    private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
    212
    213    private static boolean checksig(final byte[] expected, final byte[] signature) {
    214        for (int i = 0; i < expected.length; i++) {
    215            if (signature[i] != expected[i]) {
    216                return false;
    217            }
    218        }
    219        return true;
    220    }
    221
    222    /**
    223     * Checks if the signature matches what is expected for a ZIP file. Does not currently handle self-extracting ZIPs which may have arbitrary leading content.
    224     *
    225     * @param signature the bytes to check
    226     * @param length    the number of bytes to check
    227     * @return true, if this stream is a ZIP archive stream, false otherwise
    228     */
    229    public static boolean matches(final byte[] signature, final int length) {
    230        if (length < ZipArchiveOutputStream.LFH_SIG.length) {
    231            return false;
    232        }
    233
    234        return checksig(ZipArchiveOutputStream.LFH_SIG, signature) // normal file
    235                || checksig(ZipArchiveOutputStream.EOCD_SIG, signature) // empty zip
    236                || checksig(ZipArchiveOutputStream.DD_SIG, signature) // split zip
    237                || checksig(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER.getBytes(), signature);
    238    }
    239
    240    /** The ZIP encoding to use for file names and the file comment. */
    241    private final ZipEncoding zipEncoding;
    242
    243    /** Whether to look for and use Unicode extra fields. */
    244    private final boolean useUnicodeExtraFields;
    245
    246    /** Inflater used for all deflated entries. */
    247    private final Inflater inf = new Inflater(true);
    248
    249    /** Buffer used to read from the wrapped stream. */
    250    private final ByteBuffer buf = ByteBuffer.allocate(ZipArchiveOutputStream.BUFFER_SIZE);
    251
    252    /** The entry that is currently being read. */
    253    private CurrentEntry current;
    254
    255    /** Whether the stream has been closed. */
    256    private boolean closed;
    257
    258    /** Whether the stream has reached the central directory - and thus found all entries. */
    259    private boolean hitCentralDirectory;
    260
    261    /**
    262     * When reading a stored entry that uses the data descriptor this stream has to read the full entry and caches it. This is the cache.
    263     */
    264    private ByteArrayInputStream lastStoredEntry;
    265
    266    /**
    267     * Whether the stream will try to read STORED entries that use a data descriptor. Setting it to true means we will not stop reading an entry with the
    268     * compressed size, instead we will stop reading an entry when a data descriptor is met (by finding the Data Descriptor Signature). This will completely
    269     * break down in some cases - like JARs in WARs.
    270     * <p>
    271     * See also : https://issues.apache.org/jira/projects/COMPRESS/issues/COMPRESS-555
    272     * https://github.com/apache/commons-compress/pull/137#issuecomment-690835644
    273     * </p>
    274     */
    275    private final boolean allowStoredEntriesWithDataDescriptor;
    276
    277    /** Count decompressed bytes for current entry */
    278    private long uncompressedCount;
    279
    280    /** Whether the stream will try to skip the ZIP split signature(08074B50) at the beginning **/
    281    private final boolean skipSplitSig;
    282
    283    /** Cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection). */
    284    private final byte[] lfhBuf = new byte[LFH_LEN];
    285
    286    private final byte[] skipBuf = new byte[1024];
    287
    288    private final byte[] shortBuf = new byte[SHORT];
    289
    290    private final byte[] wordBuf = new byte[WORD];
    291
    292    private final byte[] twoDwordBuf = new byte[2 * DWORD];
    293
    294    private int entriesRead;
    295
    296    /**
    297     * The factory for extra fields or null.
    298     */
    299    // private Function<ZipShort, ZipExtraField> extraFieldSupport;
    300
    301    /**
    302     * Constructs an instance using UTF-8 encoding
    303     *
    304     * @param inputStream the stream to wrap
    305     */
    306    public ZipArchiveInputStream(final InputStream inputStream) {
    307        this(inputStream, StandardCharsets.UTF_8.name());
    308    }
    309
    310    /**
    311     * Constructs an instance using the specified encoding
    312     *
    313     * @param inputStream the stream to wrap
    314     * @param encoding    the encoding to use for file names, use null for the platform's default encoding
    315     * @since 1.5
    316     */
    317    public ZipArchiveInputStream(final InputStream inputStream, final String encoding) {
    318        this(inputStream, encoding, true);
    319    }
    320
    321    /**
    322     * Constructs an instance using the specified encoding
    323     *
    324     * @param inputStream           the stream to wrap
    325     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    326     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    327     */
    328    public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields) {
    329        this(inputStream, encoding, useUnicodeExtraFields, false);
    330    }
    331
    332    /**
    333     * Constructs an instance using the specified encoding
    334     *
    335     * @param inputStream                          the stream to wrap
    336     * @param encoding                             the encoding to use for file names, use null for the platform's default encoding
    337     * @param useUnicodeExtraFields                whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    338     * @param allowStoredEntriesWithDataDescriptor whether the stream will try to read STORED entries that use a data descriptor
    339     * @since 1.1
    340     */
    341    public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields,
    342            final boolean allowStoredEntriesWithDataDescriptor) {
    343        this(inputStream, encoding, useUnicodeExtraFields, allowStoredEntriesWithDataDescriptor, false);
    344    }
    345
    346    /**
    347     * Constructs an instance using the specified encoding
    348     *
    349     * @param inputStream                          the stream to wrap
    350     * @param encoding                             the encoding to use for file names, use null for the platform's default encoding
    351     * @param useUnicodeExtraFields                whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    352     * @param allowStoredEntriesWithDataDescriptor whether the stream will try to read STORED entries that use a data descriptor
    353     * @param skipSplitSig                         Whether the stream will try to skip the zip split signature(08074B50) at the beginning. You will need to set
    354     *                                             this to true if you want to read a split archive.
    355     * @since 1.20
    356     */
    357    public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields,
    358            final boolean allowStoredEntriesWithDataDescriptor, final boolean skipSplitSig) {
    359        super(inputStream, encoding);
    360        this.in = new PushbackInputStream(inputStream, buf.capacity());
    361        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    362        this.useUnicodeExtraFields = useUnicodeExtraFields;
    363        this.allowStoredEntriesWithDataDescriptor = allowStoredEntriesWithDataDescriptor;
    364        this.skipSplitSig = skipSplitSig;
    365        // haven't read anything so far
    366        buf.limit(0);
    367    }
    368
    369    /**
    370     * Checks whether the current buffer contains the signature of a &quot;data descriptor&quot;, &quot;local file header&quot; or &quot;central directory
    371     * entry&quot;.
    372     * <p>
    373     * If it contains such a signature, reads the data descriptor and positions the stream right after the data descriptor.
    374     * </p>
    375     */
    376    private boolean bufferContainsSignature(final ByteArrayOutputStream bos, final int offset, final int lastRead, final int expectedDDLen) throws IOException {
    377
    378        boolean done = false;
    379        for (int i = 0; !done && i < offset + lastRead - 4; i++) {
    380            if (buf.array()[i] == LFH[0] && buf.array()[i + 1] == LFH[1]) {
    381                int expectDDPos = i;
    382                if (i >= expectedDDLen && buf.array()[i + 2] == LFH[2] && buf.array()[i + 3] == LFH[3]
    383                        || buf.array()[i + 2] == CFH[2] && buf.array()[i + 3] == CFH[3]) {
    384                    // found an LFH or CFH:
    385                    expectDDPos = i - expectedDDLen;
    386                    done = true;
    387                } else if (buf.array()[i + 2] == DD[2] && buf.array()[i + 3] == DD[3]) {
    388                    // found DD:
    389                    done = true;
    390                }
    391                if (done) {
    392                    // * push back bytes read in excess as well as the data
    393                    // descriptor
    394                    // * copy the remaining bytes to cache
    395                    // * read data descriptor
    396                    pushback(buf.array(), expectDDPos, offset + lastRead - expectDDPos);
    397                    bos.write(buf.array(), 0, expectDDPos);
    398                    readDataDescriptor();
    399                }
    400            }
    401        }
    402        return done;
    403    }
    404
    405    /**
    406     * If the last read bytes could hold a data descriptor and an incomplete signature then save the last bytes to the front of the buffer and cache everything
    407     * in front of the potential data descriptor into the given ByteArrayOutputStream.
    408     * <p>
    409     * Data descriptor plus incomplete signature (3 bytes in the worst case) can be 20 bytes max.
    410     * </p>
    411     */
    412    private int cacheBytesRead(final ByteArrayOutputStream bos, int offset, final int lastRead, final int expectedDDLen) {
    413        final int cacheable = offset + lastRead - expectedDDLen - 3;
    414        if (cacheable > 0) {
    415            bos.write(buf.array(), 0, cacheable);
    416            System.arraycopy(buf.array(), cacheable, buf.array(), 0, expectedDDLen + 3);
    417            offset = expectedDDLen + 3;
    418        } else {
    419            offset += lastRead;
    420        }
    421        return offset;
    422    }
    423
    424    /**
    425     * Whether this class is able to read the given entry.
    426     * <p>
    427     * May return false if it is set up to use encryption or a compression method that hasn't been implemented yet.
    428     * </p>
    429     *
    430     * @since 1.1
    431     */
    432    @Override
    433    public boolean canReadEntryData(final ArchiveEntry ae) {
    434        if (ae instanceof ZipArchiveEntry) {
    435            final ZipArchiveEntry ze = (ZipArchiveEntry) ae;
    436            return ZipUtil.canHandleEntryData(ze) && supportsDataDescriptorFor(ze) && supportsCompressedSizeFor(ze);
    437        }
    438        return false;
    439    }
    440
    441    @Override
    442    public void close() throws IOException {
    443        if (!closed) {
    444            closed = true;
    445            try {
    446                in.close();
    447            } finally {
    448                inf.end();
    449            }
    450        }
    451    }
    452
    453    /**
    454     * Closes the current ZIP archive entry and positions the underlying stream to the beginning of the next entry. All per-entry variables and data structures
    455     * are cleared.
    456     * <p>
    457     * If the compressed size of this entry is included in the entry header, then any outstanding bytes are simply skipped from the underlying stream without
    458     * uncompressing them. This allows an entry to be safely closed even if the compression method is unsupported.
    459     * </p>
    460     * <p>
    461     * In case we don't know the compressed size of this entry or have already buffered too much data from the underlying stream to support uncompression, then
    462     * the uncompression process is completed and the end position of the stream is adjusted based on the result of that process.
    463     * </p>
    464     *
    465     * @throws IOException if an error occurs
    466     */
    467    private void closeEntry() throws IOException {
    468        if (closed) {
    469            throw new IOException("The stream is closed");
    470        }
    471        if (current == null) {
    472            return;
    473        }
    474
    475        // Ensure all entry bytes are read
    476        if (currentEntryHasOutstandingBytes()) {
    477            drainCurrentEntryData();
    478        } else {
    479            // this is guaranteed to exhaust the stream
    480            if (skip(Long.MAX_VALUE) < 0) {
    481                throw new IllegalStateException("Can't read the remainder of the stream");
    482            }
    483
    484            final long inB = current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED ? getBytesInflated() : current.bytesRead;
    485
    486            // this is at most a single read() operation and can't
    487            // exceed the range of int
    488            final int diff = (int) (current.bytesReadFromStream - inB);
    489
    490            // Pushback any required bytes
    491            if (diff > 0) {
    492                pushback(buf.array(), buf.limit() - diff, diff);
    493                current.bytesReadFromStream -= diff;
    494            }
    495
    496            // Drain remainder of entry if not all data bytes were required
    497            if (currentEntryHasOutstandingBytes()) {
    498                drainCurrentEntryData();
    499            }
    500        }
    501
    502        if (lastStoredEntry == null && current.hasDataDescriptor) {
    503            readDataDescriptor();
    504        }
    505
    506        inf.reset();
    507        buf.clear().flip();
    508        current = null;
    509        lastStoredEntry = null;
    510    }
    511
    512    /**
    513     * If the compressed size of the current entry is included in the entry header and there are any outstanding bytes in the underlying stream, then this
    514     * returns true.
    515     *
    516     * @return true, if current entry is determined to have outstanding bytes, false otherwise
    517     */
    518    private boolean currentEntryHasOutstandingBytes() {
    519        return current.bytesReadFromStream <= current.entry.getCompressedSize() && !current.hasDataDescriptor;
    520    }
    521
    522    /**
    523     * Read all data of the current entry from the underlying stream that hasn't been read, yet.
    524     */
    525    private void drainCurrentEntryData() throws IOException {
    526        long remaining = current.entry.getCompressedSize() - current.bytesReadFromStream;
    527        while (remaining > 0) {
    528            final long n = in.read(buf.array(), 0, (int) Math.min(buf.capacity(), remaining));
    529            if (n < 0) {
    530                throw new EOFException("Truncated ZIP entry: " + ArchiveUtils.sanitize(current.entry.getName()));
    531            }
    532            count(n);
    533            remaining -= n;
    534        }
    535    }
    536
    537    private int fill() throws IOException {
    538        if (closed) {
    539            throw new IOException("The stream is closed");
    540        }
    541        final int length = in.read(buf.array());
    542        if (length > 0) {
    543            buf.limit(length);
    544            count(buf.limit());
    545            inf.setInput(buf.array(), 0, buf.limit());
    546        }
    547        return length;
    548    }
    549
    550    /**
    551     * Reads forward until the signature of the &quot;End of central directory&quot; record is found.
    552     */
    553    private boolean findEocdRecord() throws IOException {
    554        int currentByte = -1;
    555        boolean skipReadCall = false;
    556        while (skipReadCall || (currentByte = readOneByte()) > -1) {
    557            skipReadCall = false;
    558            if (!isFirstByteOfEocdSig(currentByte)) {
    559                continue;
    560            }
    561            currentByte = readOneByte();
    562            if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) {
    563                if (currentByte == -1) {
    564                    break;
    565                }
    566                skipReadCall = isFirstByteOfEocdSig(currentByte);
    567                continue;
    568            }
    569            currentByte = readOneByte();
    570            if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) {
    571                if (currentByte == -1) {
    572                    break;
    573                }
    574                skipReadCall = isFirstByteOfEocdSig(currentByte);
    575                continue;
    576            }
    577            currentByte = readOneByte();
    578            if (currentByte == -1) {
    579                break;
    580            }
    581            if (currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) {
    582                return true;
    583            }
    584            skipReadCall = isFirstByteOfEocdSig(currentByte);
    585        }
    586        return false;
    587    }
    588
    589    /**
    590     * Gets the number of bytes Inflater has actually processed.
    591     * <p>
    592     * for Java &lt; Java7 the getBytes* methods in Inflater/Deflater seem to return unsigned ints rather than longs that start over with 0 at 2^32.
    593     * </p>
    594     * <p>
    595     * The stream knows how many bytes it has read, but not how many the Inflater actually consumed - it should be between the total number of bytes read for
    596     * the entry and the total number minus the last read operation. Here we just try to make the value close enough to the bytes we've read by assuming the
    597     * number of bytes consumed must be smaller than (or equal to) the number of bytes read but not smaller by more than 2^32.
    598     * </p>
    599     */
    600    private long getBytesInflated() {
    601        long inB = inf.getBytesRead();
    602        if (current.bytesReadFromStream >= TWO_EXP_32) {
    603            while (inB + TWO_EXP_32 <= current.bytesReadFromStream) {
    604                inB += TWO_EXP_32;
    605            }
    606        }
    607        return inB;
    608    }
    609
    610    /**
    611     * @since 1.17
    612     */
    613    @SuppressWarnings("resource") // checkInputStream() does not allocate.
    614    @Override
    615    public long getCompressedCount() {
    616        final int method = current.entry.getMethod();
    617        if (method == ZipArchiveOutputStream.STORED) {
    618            return current.bytesRead;
    619        }
    620        if (method == ZipArchiveOutputStream.DEFLATED) {
    621            return getBytesInflated();
    622        }
    623        if (method == ZipMethod.UNSHRINKING.getCode() || method == ZipMethod.IMPLODING.getCode() || method == ZipMethod.ENHANCED_DEFLATED.getCode()
    624                || method == ZipMethod.BZIP2.getCode()) {
    625            return ((InputStreamStatistics) current.checkInputStream()).getCompressedCount();
    626        }
    627        return -1;
    628    }
    629
    630    @Override
    631    public ZipArchiveEntry getNextEntry() throws IOException {
    632        return getNextZipEntry();
    633    }
    634
    635    /**
    636     * Gets the next entry.
    637     *
    638     * @return the next entry.
    639     * @throws IOException if an I/O error occurs.
    640     * @deprecated Use {@link #getNextEntry()}.
    641     */
    642    @Deprecated
    643    public ZipArchiveEntry getNextZipEntry() throws IOException {
    644        uncompressedCount = 0;
    645
    646        boolean firstEntry = true;
    647        if (closed || hitCentralDirectory) {
    648            return null;
    649        }
    650        if (current != null) {
    651            closeEntry();
    652            firstEntry = false;
    653        }
    654
    655        final long currentHeaderOffset = getBytesRead();
    656        try {
    657            if (firstEntry) {
    658                // split archives have a special signature before the
    659                // first local file header - look for it and fail with
    660                // the appropriate error message if this is a split
    661                // archive.
    662                if (!readFirstLocalFileHeader()) {
    663                    hitCentralDirectory = true;
    664                    skipRemainderOfArchive();
    665                    return null;
    666                }
    667            } else {
    668                readFully(lfhBuf);
    669            }
    670        } catch (final EOFException e) { // NOSONAR
    671            return null;
    672        }
    673
    674        final ZipLong sig = new ZipLong(lfhBuf);
    675        if (!sig.equals(ZipLong.LFH_SIG)) {
    676            if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG) || isApkSigningBlock(lfhBuf)) {
    677                hitCentralDirectory = true;
    678                skipRemainderOfArchive();
    679                return null;
    680            }
    681            throw new ZipException(String.format("Unexpected record signature: 0x%x", sig.getValue()));
    682        }
    683
    684        int off = WORD;
    685        current = new CurrentEntry();
    686
    687        final int versionMadeBy = ZipShort.getValue(lfhBuf, off);
    688        off += SHORT;
    689        current.entry.setPlatform(versionMadeBy >> ZipFile.BYTE_SHIFT & ZipFile.NIBLET_MASK);
    690
    691        final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfhBuf, off);
    692        final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
    693        final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
    694        current.hasDataDescriptor = gpFlag.usesDataDescriptor();
    695        current.entry.setGeneralPurposeBit(gpFlag);
    696
    697        off += SHORT;
    698
    699        current.entry.setMethod(ZipShort.getValue(lfhBuf, off));
    700        off += SHORT;
    701
    702        final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfhBuf, off));
    703        current.entry.setTime(time);
    704        off += WORD;
    705
    706        ZipLong size = null, cSize = null;
    707        if (!current.hasDataDescriptor) {
    708            current.entry.setCrc(ZipLong.getValue(lfhBuf, off));
    709            off += WORD;
    710
    711            cSize = new ZipLong(lfhBuf, off);
    712            off += WORD;
    713
    714            size = new ZipLong(lfhBuf, off);
    715            off += WORD;
    716        } else {
    717            off += 3 * WORD;
    718        }
    719
    720        final int fileNameLen = ZipShort.getValue(lfhBuf, off);
    721
    722        off += SHORT;
    723
    724        final int extraLen = ZipShort.getValue(lfhBuf, off);
    725        off += SHORT; // NOSONAR - assignment as documentation
    726
    727        final byte[] fileName = readRange(fileNameLen);
    728        current.entry.setName(entryEncoding.decode(fileName), fileName);
    729        if (hasUTF8Flag) {
    730            current.entry.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
    731        }
    732
    733        final byte[] extraData = readRange(extraLen);
    734        try {
    735            current.entry.setExtra(extraData);
    736        } catch (final RuntimeException ex) {
    737            final ZipException z = new ZipException("Invalid extra data in entry " + current.entry.getName());
    738            z.initCause(ex);
    739            throw z;
    740        }
    741
    742        if (!hasUTF8Flag && useUnicodeExtraFields) {
    743            ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, null);
    744        }
    745
    746        processZip64Extra(size, cSize);
    747
    748        current.entry.setLocalHeaderOffset(currentHeaderOffset);
    749        current.entry.setDataOffset(getBytesRead());
    750        current.entry.setStreamContiguous(true);
    751
    752        final ZipMethod m = ZipMethod.getMethodByCode(current.entry.getMethod());
    753        if (current.entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN) {
    754            if (ZipUtil.canHandleEntryData(current.entry) && m != ZipMethod.STORED && m != ZipMethod.DEFLATED) {
    755                final InputStream bis = new BoundCountInputStream(in, current.entry.getCompressedSize());
    756                switch (m) {
    757                case UNSHRINKING:
    758                    current.inputStream = new UnshrinkingInputStream(bis);
    759                    break;
    760                case IMPLODING:
    761                    try {
    762                        current.inputStream = new ExplodingInputStream(current.entry.getGeneralPurposeBit().getSlidingDictionarySize(),
    763                                current.entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), bis);
    764                    } catch (final IllegalArgumentException ex) {
    765                        throw new IOException("bad IMPLODE data", ex);
    766                    }
    767                    break;
    768                case BZIP2:
    769                    current.inputStream = new BZip2CompressorInputStream(bis);
    770                    break;
    771                case ENHANCED_DEFLATED:
    772                    current.inputStream = new Deflate64CompressorInputStream(bis);
    773                    break;
    774                default:
    775                    // we should never get here as all supported methods have been covered
    776                    // will cause an error when read is invoked, don't throw an exception here so people can
    777                    // skip unsupported entries
    778                    break;
    779                }
    780            }
    781        } else if (m == ZipMethod.ENHANCED_DEFLATED) {
    782            current.inputStream = new Deflate64CompressorInputStream(in);
    783        }
    784
    785        entriesRead++;
    786        return current.entry;
    787    }
    788
    789    /**
    790     * Gets the uncompressed count.
    791     *
    792     * @since 1.17
    793     */
    794    @Override
    795    public long getUncompressedCount() {
    796        return uncompressedCount;
    797    }
    798
    799    /**
    800     * Checks whether this might be an APK Signing Block.
    801     * <p>
    802     * Unfortunately the APK signing block does not start with some kind of signature, it rather ends with one. It starts with a length, so what we do is parse
    803     * the suspect length, skip ahead far enough, look for the signature and if we've found it, return true.
    804     * </p>
    805     *
    806     * @param suspectLocalFileHeader the bytes read from the underlying stream in the expectation that they would hold the local file header of the next entry.
    807     * @return true if this looks like an APK signing block
    808     * @see <a href="https://source.android.com/security/apksigning/v2">https://source.android.com/security/apksigning/v2</a>
    809     */
    810    private boolean isApkSigningBlock(final byte[] suspectLocalFileHeader) throws IOException {
    811        // length of block excluding the size field itself
    812        final BigInteger len = ZipEightByteInteger.getValue(suspectLocalFileHeader);
    813        // LFH has already been read and all but the first eight bytes contain (part of) the APK signing block,
    814        // also subtract 16 bytes in order to position us at the magic string
    815        BigInteger toSkip = len.add(BigInteger.valueOf(DWORD - suspectLocalFileHeader.length - (long) APK_SIGNING_BLOCK_MAGIC.length));
    816        final byte[] magic = new byte[APK_SIGNING_BLOCK_MAGIC.length];
    817
    818        try {
    819            if (toSkip.signum() < 0) {
    820                // suspectLocalFileHeader contains the start of suspect magic string
    821                final int off = suspectLocalFileHeader.length + toSkip.intValue();
    822                // length was shorter than magic length
    823                if (off < DWORD) {
    824                    return false;
    825                }
    826                final int bytesInBuffer = Math.abs(toSkip.intValue());
    827                System.arraycopy(suspectLocalFileHeader, off, magic, 0, Math.min(bytesInBuffer, magic.length));
    828                if (bytesInBuffer < magic.length) {
    829                    readFully(magic, bytesInBuffer);
    830                }
    831            } else {
    832                while (toSkip.compareTo(LONG_MAX) > 0) {
    833                    realSkip(Long.MAX_VALUE);
    834                    toSkip = toSkip.add(LONG_MAX.negate());
    835                }
    836                realSkip(toSkip.longValue());
    837                readFully(magic);
    838            }
    839        } catch (final EOFException ex) { // NOSONAR
    840            // length was invalid
    841            return false;
    842        }
    843        return Arrays.equals(magic, APK_SIGNING_BLOCK_MAGIC);
    844    }
    845
    846    private boolean isFirstByteOfEocdSig(final int b) {
    847        return b == ZipArchiveOutputStream.EOCD_SIG[0];
    848    }
    849
    850    /**
    851     * Records whether a Zip64 extra is present and sets the size information from it if sizes are 0xFFFFFFFF and the entry doesn't use a data descriptor.
    852     */
    853    private void processZip64Extra(final ZipLong size, final ZipLong cSize) throws ZipException {
    854        final ZipExtraField extra = current.entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
    855        if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
    856            throw new ZipException("archive contains unparseable zip64 extra field");
    857        }
    858        final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra;
    859        current.usesZip64 = z64 != null;
    860        if (!current.hasDataDescriptor) {
    861            if (z64 != null // same as current.usesZip64 but avoids NPE warning
    862                    && (ZipLong.ZIP64_MAGIC.equals(cSize) || ZipLong.ZIP64_MAGIC.equals(size))) {
    863                if (z64.getCompressedSize() == null || z64.getSize() == null) {
    864                    // avoid NPE if it's a corrupted ZIP archive
    865                    throw new ZipException("archive contains corrupted zip64 extra field");
    866                }
    867                long s = z64.getCompressedSize().getLongValue();
    868                if (s < 0) {
    869                    throw new ZipException("broken archive, entry with negative compressed size");
    870                }
    871                current.entry.setCompressedSize(s);
    872                s = z64.getSize().getLongValue();
    873                if (s < 0) {
    874                    throw new ZipException("broken archive, entry with negative size");
    875                }
    876                current.entry.setSize(s);
    877            } else if (cSize != null && size != null) {
    878                if (cSize.getValue() < 0) {
    879                    throw new ZipException("broken archive, entry with negative compressed size");
    880                }
    881                current.entry.setCompressedSize(cSize.getValue());
    882                if (size.getValue() < 0) {
    883                    throw new ZipException("broken archive, entry with negative size");
    884                }
    885                current.entry.setSize(size.getValue());
    886            }
    887        }
    888    }
    889
    890    private void pushback(final byte[] buf, final int offset, final int length) throws IOException {
    891        if (offset < 0) {
    892            // Instead of ArrayIndexOutOfBoundsException
    893            throw new IOException(String.format("Negative offset %,d into buffer", offset));
    894        }
    895        ((PushbackInputStream) in).unread(buf, offset, length);
    896        pushedBackBytes(length);
    897    }
    898
    899    @Override
    900    public int read(final byte[] buffer, final int offset, final int length) throws IOException {
    901        if (length == 0) {
    902            return 0;
    903        }
    904        if (closed) {
    905            throw new IOException("The stream is closed");
    906        }
    907
    908        if (current == null) {
    909            return -1;
    910        }
    911
    912        // avoid int overflow, check null buffer
    913        if (offset > buffer.length || length < 0 || offset < 0 || buffer.length - offset < length) {
    914            throw new ArrayIndexOutOfBoundsException();
    915        }
    916
    917        ZipUtil.checkRequestedFeatures(current.entry);
    918        if (!supportsDataDescriptorFor(current.entry)) {
    919            throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.DATA_DESCRIPTOR, current.entry);
    920        }
    921        if (!supportsCompressedSizeFor(current.entry)) {
    922            throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.UNKNOWN_COMPRESSED_SIZE, current.entry);
    923        }
    924
    925        final int read;
    926        if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) {
    927            read = readStored(buffer, offset, length);
    928        } else if (current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED) {
    929            read = readDeflated(buffer, offset, length);
    930        } else if (current.entry.getMethod() == ZipMethod.UNSHRINKING.getCode() || current.entry.getMethod() == ZipMethod.IMPLODING.getCode()
    931                || current.entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() || current.entry.getMethod() == ZipMethod.BZIP2.getCode()) {
    932            read = current.inputStream.read(buffer, offset, length);
    933        } else {
    934            throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(current.entry.getMethod()), current.entry);
    935        }
    936
    937        if (read >= 0) {
    938            current.crc.update(buffer, offset, read);
    939            uncompressedCount += read;
    940        }
    941
    942        return read;
    943    }
    944
    945    private void readDataDescriptor() throws IOException {
    946        readFully(wordBuf);
    947        ZipLong val = new ZipLong(wordBuf);
    948        if (ZipLong.DD_SIG.equals(val)) {
    949            // data descriptor with signature, skip sig
    950            readFully(wordBuf);
    951            val = new ZipLong(wordBuf);
    952        }
    953        current.entry.setCrc(val.getValue());
    954
    955        // if there is a ZIP64 extra field, sizes are eight bytes
    956        // each, otherwise four bytes each. Unfortunately some
    957        // implementations - namely Java7 - use eight bytes without
    958        // using a ZIP64 extra field -
    959        // https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588
    960
    961        // just read 16 bytes and check whether bytes nine to twelve
    962        // look like one of the signatures of what could follow a data
    963        // descriptor (ignoring archive decryption headers for now).
    964        // If so, push back eight bytes and assume sizes are four
    965        // bytes, otherwise sizes are eight bytes each.
    966        readFully(twoDwordBuf);
    967        final ZipLong potentialSig = new ZipLong(twoDwordBuf, DWORD);
    968        if (potentialSig.equals(ZipLong.CFH_SIG) || potentialSig.equals(ZipLong.LFH_SIG)) {
    969            pushback(twoDwordBuf, DWORD, DWORD);
    970            long size = ZipLong.getValue(twoDwordBuf);
    971            if (size < 0) {
    972                throw new ZipException("broken archive, entry with negative compressed size");
    973            }
    974            current.entry.setCompressedSize(size);
    975            size = ZipLong.getValue(twoDwordBuf, WORD);
    976            if (size < 0) {
    977                throw new ZipException("broken archive, entry with negative size");
    978            }
    979            current.entry.setSize(size);
    980        } else {
    981            long size = ZipEightByteInteger.getLongValue(twoDwordBuf);
    982            if (size < 0) {
    983                throw new ZipException("broken archive, entry with negative compressed size");
    984            }
    985            current.entry.setCompressedSize(size);
    986            size = ZipEightByteInteger.getLongValue(twoDwordBuf, DWORD);
    987            if (size < 0) {
    988                throw new ZipException("broken archive, entry with negative size");
    989            }
    990            current.entry.setSize(size);
    991        }
    992    }
    993
    994    /**
    995     * Implements read for DEFLATED entries.
    996     */
    997    private int readDeflated(final byte[] buffer, final int offset, final int length) throws IOException {
    998        final int read = readFromInflater(buffer, offset, length);
    999        if (read <= 0) {
    1000            if (inf.finished()) {
    1001                return -1;
    1002            }
    1003            if (inf.needsDictionary()) {
    1004                throw new ZipException("This archive needs a preset dictionary" + " which is not supported by Commons" + " Compress.");
    1005            }
    1006            if (read == -1) {
    1007                throw new IOException("Truncated ZIP file");
    1008            }
    1009        }
    1010        return read;
    1011    }
    1012
    1013    /**
    1014     * Fills the given array with the first local file header and deals with splitting/spanning markers that may prefix the first LFH.
    1015     */
    1016    private boolean readFirstLocalFileHeader() throws IOException {
    1017        // for empty archive, we may get only EOCD size:
    1018        final byte[] header = new byte[Math.min(LFH_LEN, ZipFile.MIN_EOCD_SIZE)];
    1019        readFully(header);
    1020        try {
    1021            READ_LOOP: for (int i = 0; ; ) {
    1022                for (int j = 0; i <= PREAMBLE_GARBAGE_MAX_SIZE - 4 && j <= header.length - 4; ++j, ++i) {
    1023                    final ZipLong sig = new ZipLong(header, j);
    1024                    if (sig.equals(ZipLong.LFH_SIG) ||
    1025                        sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) ||
    1026                        sig.equals(ZipLong.DD_SIG)) {
    1027                        // regular archive containing at least one entry:
    1028                        System.arraycopy(header, j, header, 0, header.length - j);
    1029                        readFully(header, header.length - j);
    1030                        break READ_LOOP;
    1031                    }
    1032                    if (sig.equals(new ZipLong(ZipArchiveOutputStream.EOCD_SIG))) {
    1033                        // empty archive:
    1034                        pushback(header, j, header.length - j);
    1035                        return false;
    1036                    }
    1037                }
    1038                if (i >= PREAMBLE_GARBAGE_MAX_SIZE - 4) {
    1039                    throw new ZipException("Cannot find zip signature within the first " + PREAMBLE_GARBAGE_MAX_SIZE + " bytes");
    1040                }
    1041                System.arraycopy(header, header.length - 3, header, 0, 3);
    1042                readFully(header, 3);
    1043            }
    1044            System.arraycopy(header, 0, lfhBuf, 0, header.length);
    1045            readFully(lfhBuf, header.length);
    1046        } catch (final EOFException ex) {
    1047            throw new ZipException("Cannot find zip signature within the file");
    1048        }
    1049        final ZipLong sig = new ZipLong(lfhBuf);
    1050
    1051        if (!skipSplitSig && sig.equals(ZipLong.DD_SIG)) {
    1052            throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.SPLITTING);
    1053        }
    1054
    1055        // the split ZIP signature(08074B50) should only be skipped when the skipSplitSig is set
    1056        if (sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) || sig.equals(ZipLong.DD_SIG)) {
    1057            // Just skip over the marker.
    1058            System.arraycopy(lfhBuf, 4, lfhBuf, 0, lfhBuf.length - 4);
    1059            readFully(lfhBuf, lfhBuf.length - 4);
    1060        }
    1061        return true;
    1062    }
    1063
    1064    /**
    1065     * Potentially reads more bytes to fill the inflater's buffer and reads from it.
    1066     */
    1067    private int readFromInflater(final byte[] buffer, final int offset, final int length) throws IOException {
    1068        int read = 0;
    1069        do {
    1070            if (inf.needsInput()) {
    1071                final int l = fill();
    1072                if (l > 0) {
    1073                    current.bytesReadFromStream += buf.limit();
    1074                } else if (l == -1) {
    1075                    return -1;
    1076                } else {
    1077                    break;
    1078                }
    1079            }
    1080            try {
    1081                read = inf.inflate(buffer, offset, length);
    1082            } catch (final DataFormatException e) {
    1083                throw (IOException) new ZipException(e.getMessage()).initCause(e);
    1084            }
    1085        } while (read == 0 && inf.needsInput());
    1086        return read;
    1087    }
    1088
    1089    private void readFully(final byte[] b) throws IOException {
    1090        readFully(b, 0);
    1091    }
    1092
    1093    private void readFully(final byte[] b, final int off) throws IOException {
    1094        final int len = b.length - off;
    1095        final int count = IOUtils.readFully(in, b, off, len);
    1096        count(count);
    1097        if (count < len) {
    1098            throw new EOFException();
    1099        }
    1100    }
    1101
    1102    // End of Central Directory Record
    1103    // end of central dir signature WORD
    1104    // number of this disk SHORT
    1105    // number of the disk with the
    1106    // start of the central directory SHORT
    1107    // total number of entries in the
    1108    // central directory on this disk SHORT
    1109    // total number of entries in
    1110    // the central directory SHORT
    1111    // size of the central directory WORD
    1112    // offset of start of central
    1113    // directory with respect to
    1114    // the starting disk number WORD
    1115    // .ZIP file comment length SHORT
    1116    // .ZIP file comment up to 64KB
    1117    //
    1118
    1119    /**
    1120     * Reads bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #read} would do.
    1121     *
    1122     * Also updates bytes-read counter.
    1123     */
    1124    private int readOneByte() throws IOException {
    1125        final int b = in.read();
    1126        if (b != -1) {
    1127            count(1);
    1128        }
    1129        return b;
    1130    }
    1131
    1132    private byte[] readRange(final int len) throws IOException {
    1133        final byte[] ret = IOUtils.readRange(in, len);
    1134        count(ret.length);
    1135        if (ret.length < len) {
    1136            throw new EOFException();
    1137        }
    1138        return ret;
    1139    }
    1140
    1141    /**
    1142     * Implements read for STORED entries.
    1143     */
    1144    private int readStored(final byte[] buffer, final int offset, final int length) throws IOException {
    1145
    1146        if (current.hasDataDescriptor) {
    1147            if (lastStoredEntry == null) {
    1148                readStoredEntry();
    1149            }
    1150            return lastStoredEntry.read(buffer, offset, length);
    1151        }
    1152
    1153        final long csize = current.entry.getSize();
    1154        if (current.bytesRead >= csize) {
    1155            return -1;
    1156        }
    1157
    1158        if (buf.position() >= buf.limit()) {
    1159            buf.position(0);
    1160            final int l = in.read(buf.array());
    1161            if (l == -1) {
    1162                buf.limit(0);
    1163                throw new IOException("Truncated ZIP file");
    1164            }
    1165            buf.limit(l);
    1166
    1167            count(l);
    1168            current.bytesReadFromStream += l;
    1169        }
    1170
    1171        int toRead = Math.min(buf.remaining(), length);
    1172        if (csize - current.bytesRead < toRead) {
    1173            // if it is smaller than toRead then it fits into an int
    1174            toRead = (int) (csize - current.bytesRead);
    1175        }
    1176        buf.get(buffer, offset, toRead);
    1177        current.bytesRead += toRead;
    1178        return toRead;
    1179    }
    1180
    1181    /**
    1182     * Caches a stored entry that uses the data descriptor.
    1183     * <ul>
    1184     * <li>Reads a stored entry until the signature of a local file header, central directory header or data descriptor has been found.</li>
    1185     * <li>Stores all entry data in lastStoredEntry.
    1186     * </p>
    1187     * <li>Rewinds the stream to position at the data descriptor.</li>
    1188     * <li>reads the data descriptor</li>
    1189     * </ul>
    1190     * <p>
    1191     * After calling this method the entry should know its size, the entry's data is cached and the stream is positioned at the next local file or central
    1192     * directory header.
    1193     * </p>
    1194     */
    1195    private void readStoredEntry() throws IOException {
    1196        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
    1197        int off = 0;
    1198        boolean done = false;
    1199
    1200        // length of DD without signature
    1201        final int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD;
    1202
    1203        while (!done) {
    1204            final int r = in.read(buf.array(), off, ZipArchiveOutputStream.BUFFER_SIZE - off);
    1205            if (r <= 0) {
    1206                // read the whole archive without ever finding a
    1207                // central directory
    1208                throw new IOException("Truncated ZIP file");
    1209            }
    1210            if (r + off < 4) {
    1211                // buffer too small to check for a signature, loop
    1212                off += r;
    1213                continue;
    1214            }
    1215
    1216            done = bufferContainsSignature(bos, off, r, ddLen);
    1217            if (!done) {
    1218                off = cacheBytesRead(bos, off, r, ddLen);
    1219            }
    1220        }
    1221        if (current.entry.getCompressedSize() != current.entry.getSize()) {
    1222            throw new ZipException("compressed and uncompressed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER);
    1223        }
    1224        final byte[] b = bos.toByteArray();
    1225        if (b.length != current.entry.getSize()) {
    1226            throw new ZipException("actual and claimed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER);
    1227        }
    1228        lastStoredEntry = new ByteArrayInputStream(b);
    1229    }
    1230
    1231    /**
    1232     * Skips bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #skip} would do.
    1233     *
    1234     * Also updates bytes-read counter.
    1235     */
    1236    private void realSkip(final long value) throws IOException {
    1237        if (value >= 0) {
    1238            long skipped = 0;
    1239            while (skipped < value) {
    1240                final long rem = value - skipped;
    1241                final int x = in.read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length));
    1242                if (x == -1) {
    1243                    return;
    1244                }
    1245                count(x);
    1246                skipped += x;
    1247            }
    1248            return;
    1249        }
    1250        throw new IllegalArgumentException();
    1251    }
    1252
    1253    /**
    1254     * Currently unused.
    1255     *
    1256     * Sets the custom extra fields factory.
    1257     * @param extraFieldSupport the lookup function based on extra field header id.
    1258     * @return the archive.
    1259     */
    1260    public ZipArchiveInputStream setExtraFieldSupport(final Function<ZipShort, ZipExtraField> extraFieldSupport) {
    1261        // this.extraFieldSupport = extraFieldSupport;
    1262        return this;
    1263    }
    1264
    1265    /**
    1266     * Skips over and discards value bytes of data from this input stream.
    1267     * <p>
    1268     * This implementation may end up skipping over some smaller number of bytes, possibly 0, if and only if it reaches the end of the underlying stream.
    1269     * </p>
    1270     * <p>
    1271     * The actual number of bytes skipped is returned.
    1272     * </p>
    1273     *
    1274     * @param value the number of bytes to be skipped.
    1275     * @return the actual number of bytes skipped.
    1276     * @throws IOException              - if an I/O error occurs.
    1277     * @throws IllegalArgumentException - if value is negative.
    1278     */
    1279    @Override
    1280    public long skip(final long value) throws IOException {
    1281        if (value >= 0) {
    1282            long skipped = 0;
    1283            while (skipped < value) {
    1284                final long rem = value - skipped;
    1285                final int x = read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length));
    1286                if (x == -1) {
    1287                    return skipped;
    1288                }
    1289                skipped += x;
    1290            }
    1291            return skipped;
    1292        }
    1293        throw new IllegalArgumentException("Negative skip value");
    1294    }
    1295
    1296    /**
    1297     * Reads the stream until it find the "End of central directory record" and consumes it as well.
    1298     */
    1299    private void skipRemainderOfArchive() throws IOException {
    1300        // skip over central directory. One LFH has been read too much
    1301        // already. The calculation discounts file names and extra
    1302        // data, so it will be too short.
    1303        if (entriesRead > 0) {
    1304            realSkip((long) entriesRead * CFH_LEN - LFH_LEN);
    1305        }
    1306        final boolean foundEocd = findEocdRecord();
    1307        if (foundEocd) {
    1308            realSkip((long) ZipFile.MIN_EOCD_SIZE - WORD /* signature */ - SHORT /* comment len */);
    1309            readFully(shortBuf);
    1310            // file comment
    1311            final int commentLen = ZipShort.getValue(shortBuf);
    1312            if (commentLen >= 0) {
    1313                realSkip(commentLen);
    1314                return;
    1315            }
    1316        }
    1317        throw new IOException("Truncated ZIP file");
    1318    }
    1319
    1320    /**
    1321     * Whether the compressed size for the entry is either known or not required by the compression method being used.
    1322     */
    1323    private boolean supportsCompressedSizeFor(final ZipArchiveEntry entry) {
    1324        return entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN || entry.getMethod() == ZipEntry.DEFLATED
    1325                || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode()
    1326                || entry.getGeneralPurposeBit().usesDataDescriptor() && allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED;
    1327    }
    1328
    1329    /**
    1330     * Whether this entry requires a data descriptor this library can work with.
    1331     *
    1332     * @return true if allowStoredEntriesWithDataDescriptor is true, the entry doesn't require any data descriptor or the method is DEFLATED or
    1333     *         ENHANCED_DEFLATED.
    1334     */
    1335    private boolean supportsDataDescriptorFor(final ZipArchiveEntry entry) {
    1336        return !entry.getGeneralPurposeBit().usesDataDescriptor() || allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED
    1337                || entry.getMethod() == ZipEntry.DEFLATED || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode();
    1338    }
    1339}