View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.zip;
20  
21  import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
22  import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
23  import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
24  import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.EOFException;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.PushbackInputStream;
32  import java.math.BigInteger;
33  import java.nio.ByteBuffer;
34  import java.nio.charset.StandardCharsets;
35  import java.util.Arrays;
36  import java.util.Objects;
37  import java.util.function.Function;
38  import java.util.zip.CRC32;
39  import java.util.zip.DataFormatException;
40  import java.util.zip.Inflater;
41  import java.util.zip.ZipEntry;
42  import java.util.zip.ZipException;
43  
44  import org.apache.commons.compress.archivers.ArchiveEntry;
45  import org.apache.commons.compress.archivers.ArchiveInputStream;
46  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
47  import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
48  import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
49  import org.apache.commons.compress.utils.ArchiveUtils;
50  import org.apache.commons.compress.utils.IOUtils;
51  import org.apache.commons.compress.utils.InputStreamStatistics;
52  import org.apache.commons.io.input.BoundedInputStream;
53  
54  /**
55   * Implements an input stream that can read Zip archives.
56   * <p>
57   * 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
58   * entries.
59   * </p>
60   * <p>
61   * The {@link ZipFile} class is preferred when reading from files as {@link ZipArchiveInputStream} is limited by not being able to read the central directory
62   * header before returning entries. In particular {@link ZipArchiveInputStream}
63   * </p>
64   * <ul>
65   * <li>may return entries that are not part of the central directory at all and shouldn't be considered part of the archive.</li>
66   * <li>may return several entries with the same name.</li>
67   * <li>will not return internal or external attributes.</li>
68   * <li>may return incomplete extra field data.</li>
69   * <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>
70   * </ul>
71   *
72   * @see ZipFile
73   * @NotThreadSafe
74   */
75  public class ZipArchiveInputStream extends ArchiveInputStream<ZipArchiveEntry> implements InputStreamStatistics {
76  
77      /**
78       * Input stream adapted from commons-io.
79       */
80      private final class BoundCountInputStream extends BoundedInputStream {
81  
82          // TODO Consider how to do this from a final class, an IO class, or basically without the current side-effect implementation.
83  
84          /**
85           * Creates a new {@code BoundedInputStream} that wraps the given input stream and limits it to a certain size.
86           *
87           * @param in   The wrapped input stream
88           * @param max The maximum number of bytes to return
89           */
90          BoundCountInputStream(final InputStream in, final long max) {
91              super(in, max);
92          }
93  
94          private boolean atMaxLength() {
95              return getMaxCount() >= 0 && getCount() >= getMaxCount();
96          }
97  
98          @Override
99          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 }