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}