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