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