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