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 getMaxLength() >= 0 && getCount() >= getMaxLength();
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 = getMaxLength() >= 0 ? Math.min(len, getMaxLength() - 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             skip(Long.MAX_VALUE); // NOSONAR
481 
482             final long inB = current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED ? getBytesInflated() : current.bytesRead;
483 
484             // this is at most a single read() operation and can't
485             // exceed the range of int
486             final int diff = (int) (current.bytesReadFromStream - inB);
487 
488             // Pushback any required bytes
489             if (diff > 0) {
490                 pushback(buf.array(), buf.limit() - diff, diff);
491                 current.bytesReadFromStream -= diff;
492             }
493 
494             // Drain remainder of entry if not all data bytes were required
495             if (currentEntryHasOutstandingBytes()) {
496                 drainCurrentEntryData();
497             }
498         }
499 
500         if (lastStoredEntry == null && current.hasDataDescriptor) {
501             readDataDescriptor();
502         }
503 
504         inf.reset();
505         buf.clear().flip();
506         current = null;
507         lastStoredEntry = null;
508     }
509 
510     /**
511      * 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
512      * returns true.
513      *
514      * @return true, if current entry is determined to have outstanding bytes, false otherwise
515      */
516     private boolean currentEntryHasOutstandingBytes() {
517         return current.bytesReadFromStream <= current.entry.getCompressedSize() && !current.hasDataDescriptor;
518     }
519 
520     /**
521      * Read all data of the current entry from the underlying stream that hasn't been read, yet.
522      */
523     private void drainCurrentEntryData() throws IOException {
524         long remaining = current.entry.getCompressedSize() - current.bytesReadFromStream;
525         while (remaining > 0) {
526             final long n = in.read(buf.array(), 0, (int) Math.min(buf.capacity(), remaining));
527             if (n < 0) {
528                 throw new EOFException("Truncated ZIP entry: " + ArchiveUtils.sanitize(current.entry.getName()));
529             }
530             count(n);
531             remaining -= n;
532         }
533     }
534 
535     private int fill() throws IOException {
536         if (closed) {
537             throw new IOException("The stream is closed");
538         }
539         final int length = in.read(buf.array());
540         if (length > 0) {
541             buf.limit(length);
542             count(buf.limit());
543             inf.setInput(buf.array(), 0, buf.limit());
544         }
545         return length;
546     }
547 
548     /**
549      * Reads forward until the signature of the &quot;End of central directory&quot; record is found.
550      */
551     private boolean findEocdRecord() throws IOException {
552         int currentByte = -1;
553         boolean skipReadCall = false;
554         while (skipReadCall || (currentByte = readOneByte()) > -1) {
555             skipReadCall = false;
556             if (!isFirstByteOfEocdSig(currentByte)) {
557                 continue;
558             }
559             currentByte = readOneByte();
560             if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) {
561                 if (currentByte == -1) {
562                     break;
563                 }
564                 skipReadCall = isFirstByteOfEocdSig(currentByte);
565                 continue;
566             }
567             currentByte = readOneByte();
568             if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) {
569                 if (currentByte == -1) {
570                     break;
571                 }
572                 skipReadCall = isFirstByteOfEocdSig(currentByte);
573                 continue;
574             }
575             currentByte = readOneByte();
576             if (currentByte == -1) {
577                 break;
578             }
579             if (currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) {
580                 return true;
581             }
582             skipReadCall = isFirstByteOfEocdSig(currentByte);
583         }
584         return false;
585     }
586 
587     /**
588      * Gets the number of bytes Inflater has actually processed.
589      * <p>
590      * 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.
591      * </p>
592      * <p>
593      * 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
594      * 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
595      * number of bytes consumed must be smaller than (or equal to) the number of bytes read but not smaller by more than 2^32.
596      * </p>
597      */
598     private long getBytesInflated() {
599         long inB = inf.getBytesRead();
600         if (current.bytesReadFromStream >= TWO_EXP_32) {
601             while (inB + TWO_EXP_32 <= current.bytesReadFromStream) {
602                 inB += TWO_EXP_32;
603             }
604         }
605         return inB;
606     }
607 
608     /**
609      * @since 1.17
610      */
611     @SuppressWarnings("resource") // checkInputStream() does not allocate.
612     @Override
613     public long getCompressedCount() {
614         final int method = current.entry.getMethod();
615         if (method == ZipArchiveOutputStream.STORED) {
616             return current.bytesRead;
617         }
618         if (method == ZipArchiveOutputStream.DEFLATED) {
619             return getBytesInflated();
620         }
621         if (method == ZipMethod.UNSHRINKING.getCode() || method == ZipMethod.IMPLODING.getCode() || method == ZipMethod.ENHANCED_DEFLATED.getCode()
622                 || method == ZipMethod.BZIP2.getCode()) {
623             return ((InputStreamStatistics) current.checkInputStream()).getCompressedCount();
624         }
625         return -1;
626     }
627 
628     @Override
629     public ZipArchiveEntry getNextEntry() throws IOException {
630         return getNextZipEntry();
631     }
632 
633     /**
634      * Gets the next entry.
635      *
636      * @return the next entry.
637      * @throws IOException if an I/O error occurs.
638      * @deprecated Use {@link #getNextEntry()}.
639      */
640     @Deprecated
641     public ZipArchiveEntry getNextZipEntry() throws IOException {
642         uncompressedCount = 0;
643 
644         boolean firstEntry = true;
645         if (closed || hitCentralDirectory) {
646             return null;
647         }
648         if (current != null) {
649             closeEntry();
650             firstEntry = false;
651         }
652 
653         final long currentHeaderOffset = getBytesRead();
654         try {
655             if (firstEntry) {
656                 // split archives have a special signature before the
657                 // first local file header - look for it and fail with
658                 // the appropriate error message if this is a split
659                 // archive.
660                 if (!readFirstLocalFileHeader()) {
661                     hitCentralDirectory = true;
662                     skipRemainderOfArchive(true);
663                     return null;
664                 }
665             } else {
666                 readFully(lfhBuf);
667             }
668         } catch (final EOFException e) { // NOSONAR
669             return null;
670         }
671 
672         final ZipLong sig = new ZipLong(lfhBuf);
673         if (!sig.equals(ZipLong.LFH_SIG)) {
674             if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG) || isApkSigningBlock(lfhBuf)) {
675                 hitCentralDirectory = true;
676                 skipRemainderOfArchive(false);
677                 return null;
678             }
679             throw new ZipException(String.format("Unexpected record signature: 0x%x", sig.getValue()));
680         }
681 
682         int off = WORD;
683         current = new CurrentEntry();
684 
685         final int versionMadeBy = ZipShort.getValue(lfhBuf, off);
686         off += SHORT;
687         current.entry.setPlatform(versionMadeBy >> ZipFile.BYTE_SHIFT & ZipFile.NIBLET_MASK);
688 
689         final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfhBuf, off);
690         final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
691         final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
692         current.hasDataDescriptor = gpFlag.usesDataDescriptor();
693         current.entry.setGeneralPurposeBit(gpFlag);
694 
695         off += SHORT;
696 
697         current.entry.setMethod(ZipShort.getValue(lfhBuf, off));
698         off += SHORT;
699 
700         final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfhBuf, off));
701         current.entry.setTime(time);
702         off += WORD;
703 
704         ZipLong size = null, cSize = null;
705         if (!current.hasDataDescriptor) {
706             current.entry.setCrc(ZipLong.getValue(lfhBuf, off));
707             off += WORD;
708 
709             cSize = new ZipLong(lfhBuf, off);
710             off += WORD;
711 
712             size = new ZipLong(lfhBuf, off);
713             off += WORD;
714         } else {
715             off += 3 * WORD;
716         }
717 
718         final int fileNameLen = ZipShort.getValue(lfhBuf, off);
719 
720         off += SHORT;
721 
722         final int extraLen = ZipShort.getValue(lfhBuf, off);
723         off += SHORT; // NOSONAR - assignment as documentation
724 
725         final byte[] fileName = readRange(fileNameLen);
726         current.entry.setName(entryEncoding.decode(fileName), fileName);
727         if (hasUTF8Flag) {
728             current.entry.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
729         }
730 
731         final byte[] extraData = readRange(extraLen);
732         try {
733             current.entry.setExtra(extraData);
734         } catch (final RuntimeException ex) {
735             final ZipException z = new ZipException("Invalid extra data in entry " + current.entry.getName());
736             z.initCause(ex);
737             throw z;
738         }
739 
740         if (!hasUTF8Flag && useUnicodeExtraFields) {
741             ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, null);
742         }
743 
744         processZip64Extra(size, cSize);
745 
746         current.entry.setLocalHeaderOffset(currentHeaderOffset);
747         current.entry.setDataOffset(getBytesRead());
748         current.entry.setStreamContiguous(true);
749 
750         final ZipMethod m = ZipMethod.getMethodByCode(current.entry.getMethod());
751         if (current.entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN) {
752             if (ZipUtil.canHandleEntryData(current.entry) && m != ZipMethod.STORED && m != ZipMethod.DEFLATED) {
753                 final InputStream bis = new BoundCountInputStream(in, current.entry.getCompressedSize());
754                 switch (m) {
755                 case UNSHRINKING:
756                     current.inputStream = new UnshrinkingInputStream(bis);
757                     break;
758                 case IMPLODING:
759                     try {
760                         current.inputStream = new ExplodingInputStream(current.entry.getGeneralPurposeBit().getSlidingDictionarySize(),
761                                 current.entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), bis);
762                     } catch (final IllegalArgumentException ex) {
763                         throw new IOException("bad IMPLODE data", ex);
764                     }
765                     break;
766                 case BZIP2:
767                     current.inputStream = new BZip2CompressorInputStream(bis);
768                     break;
769                 case ENHANCED_DEFLATED:
770                     current.inputStream = new Deflate64CompressorInputStream(bis);
771                     break;
772                 default:
773                     // we should never get here as all supported methods have been covered
774                     // will cause an error when read is invoked, don't throw an exception here so people can
775                     // skip unsupported entries
776                     break;
777                 }
778             }
779         } else if (m == ZipMethod.ENHANCED_DEFLATED) {
780             current.inputStream = new Deflate64CompressorInputStream(in);
781         }
782 
783         entriesRead++;
784         return current.entry;
785     }
786 
787     /**
788      * Gets the uncompressed count.
789      *
790      * @since 1.17
791      */
792     @Override
793     public long getUncompressedCount() {
794         return uncompressedCount;
795     }
796 
797     /**
798      * Checks whether this might be an APK Signing Block.
799      * <p>
800      * 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
801      * the suspect length, skip ahead far enough, look for the signature and if we've found it, return true.
802      * </p>
803      *
804      * @param suspectLocalFileHeader the bytes read from the underlying stream in the expectation that they would hold the local file header of the next entry.
805      * @return true if this looks like an APK signing block
806      * @see <a href="https://source.android.com/security/apksigning/v2">https://source.android.com/security/apksigning/v2</a>
807      */
808     private boolean isApkSigningBlock(final byte[] suspectLocalFileHeader) throws IOException {
809         // length of block excluding the size field itself
810         final BigInteger len = ZipEightByteInteger.getValue(suspectLocalFileHeader);
811         // LFH has already been read and all but the first eight bytes contain (part of) the APK signing block,
812         // also subtract 16 bytes in order to position us at the magic string
813         BigInteger toSkip = len.add(BigInteger.valueOf(DWORD - suspectLocalFileHeader.length - (long) APK_SIGNING_BLOCK_MAGIC.length));
814         final byte[] magic = new byte[APK_SIGNING_BLOCK_MAGIC.length];
815 
816         try {
817             if (toSkip.signum() < 0) {
818                 // suspectLocalFileHeader contains the start of suspect magic string
819                 final int off = suspectLocalFileHeader.length + toSkip.intValue();
820                 // length was shorter than magic length
821                 if (off < DWORD) {
822                     return false;
823                 }
824                 final int bytesInBuffer = Math.abs(toSkip.intValue());
825                 System.arraycopy(suspectLocalFileHeader, off, magic, 0, Math.min(bytesInBuffer, magic.length));
826                 if (bytesInBuffer < magic.length) {
827                     readFully(magic, bytesInBuffer);
828                 }
829             } else {
830                 while (toSkip.compareTo(LONG_MAX) > 0) {
831                     realSkip(Long.MAX_VALUE);
832                     toSkip = toSkip.add(LONG_MAX.negate());
833                 }
834                 realSkip(toSkip.longValue());
835                 readFully(magic);
836             }
837         } catch (final EOFException ex) { // NOSONAR
838             // length was invalid
839             return false;
840         }
841         return Arrays.equals(magic, APK_SIGNING_BLOCK_MAGIC);
842     }
843 
844     private boolean isFirstByteOfEocdSig(final int b) {
845         return b == ZipArchiveOutputStream.EOCD_SIG[0];
846     }
847 
848     /**
849      * 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.
850      */
851     private void processZip64Extra(final ZipLong size, final ZipLong cSize) throws ZipException {
852         final ZipExtraField extra = current.entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
853         if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
854             throw new ZipException("archive contains unparseable zip64 extra field");
855         }
856         final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra;
857         current.usesZip64 = z64 != null;
858         if (!current.hasDataDescriptor) {
859             if (z64 != null // same as current.usesZip64 but avoids NPE warning
860                     && (ZipLong.ZIP64_MAGIC.equals(cSize) || ZipLong.ZIP64_MAGIC.equals(size))) {
861                 if (z64.getCompressedSize() == null || z64.getSize() == null) {
862                     // avoid NPE if it's a corrupted ZIP archive
863                     throw new ZipException("archive contains corrupted zip64 extra field");
864                 }
865                 long s = z64.getCompressedSize().getLongValue();
866                 if (s < 0) {
867                     throw new ZipException("broken archive, entry with negative compressed size");
868                 }
869                 current.entry.setCompressedSize(s);
870                 s = z64.getSize().getLongValue();
871                 if (s < 0) {
872                     throw new ZipException("broken archive, entry with negative size");
873                 }
874                 current.entry.setSize(s);
875             } else if (cSize != null && size != null) {
876                 if (cSize.getValue() < 0) {
877                     throw new ZipException("broken archive, entry with negative compressed size");
878                 }
879                 current.entry.setCompressedSize(cSize.getValue());
880                 if (size.getValue() < 0) {
881                     throw new ZipException("broken archive, entry with negative size");
882                 }
883                 current.entry.setSize(size.getValue());
884             }
885         }
886     }
887 
888     private void pushback(final byte[] buf, final int offset, final int length) throws IOException {
889         if (offset < 0) {
890             // Instead of ArrayIndexOutOfBoundsException
891             throw new IOException(String.format("Negative offset %,d into buffer", offset));
892         }
893         ((PushbackInputStream) in).unread(buf, offset, length);
894         pushedBackBytes(length);
895     }
896 
897     @Override
898     public int read(final byte[] buffer, final int offset, final int length) throws IOException {
899         if (length == 0) {
900             return 0;
901         }
902         if (closed) {
903             throw new IOException("The stream is closed");
904         }
905 
906         if (current == null) {
907             return -1;
908         }
909 
910         // avoid int overflow, check null buffer
911         if (offset > buffer.length || length < 0 || offset < 0 || buffer.length - offset < length) {
912             throw new ArrayIndexOutOfBoundsException();
913         }
914 
915         ZipUtil.checkRequestedFeatures(current.entry);
916         if (!supportsDataDescriptorFor(current.entry)) {
917             throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.DATA_DESCRIPTOR, current.entry);
918         }
919         if (!supportsCompressedSizeFor(current.entry)) {
920             throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.UNKNOWN_COMPRESSED_SIZE, current.entry);
921         }
922 
923         final int read;
924         if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) {
925             read = readStored(buffer, offset, length);
926         } else if (current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED) {
927             read = readDeflated(buffer, offset, length);
928         } else if (current.entry.getMethod() == ZipMethod.UNSHRINKING.getCode() || current.entry.getMethod() == ZipMethod.IMPLODING.getCode()
929                 || current.entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() || current.entry.getMethod() == ZipMethod.BZIP2.getCode()) {
930             read = current.inputStream.read(buffer, offset, length);
931         } else {
932             throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(current.entry.getMethod()), current.entry);
933         }
934 
935         if (read >= 0) {
936             current.crc.update(buffer, offset, read);
937             uncompressedCount += read;
938         }
939 
940         return read;
941     }
942 
943     private void readDataDescriptor() throws IOException {
944         readFully(wordBuf);
945         ZipLong val = new ZipLong(wordBuf);
946         if (ZipLong.DD_SIG.equals(val)) {
947             // data descriptor with signature, skip sig
948             readFully(wordBuf);
949             val = new ZipLong(wordBuf);
950         }
951         current.entry.setCrc(val.getValue());
952 
953         // if there is a ZIP64 extra field, sizes are eight bytes
954         // each, otherwise four bytes each. Unfortunately some
955         // implementations - namely Java7 - use eight bytes without
956         // using a ZIP64 extra field -
957         // https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588
958 
959         // just read 16 bytes and check whether bytes nine to twelve
960         // look like one of the signatures of what could follow a data
961         // descriptor (ignoring archive decryption headers for now).
962         // If so, push back eight bytes and assume sizes are four
963         // bytes, otherwise sizes are eight bytes each.
964         readFully(twoDwordBuf);
965         final ZipLong potentialSig = new ZipLong(twoDwordBuf, DWORD);
966         if (potentialSig.equals(ZipLong.CFH_SIG) || potentialSig.equals(ZipLong.LFH_SIG)) {
967             pushback(twoDwordBuf, DWORD, DWORD);
968             long size = ZipLong.getValue(twoDwordBuf);
969             if (size < 0) {
970                 throw new ZipException("broken archive, entry with negative compressed size");
971             }
972             current.entry.setCompressedSize(size);
973             size = ZipLong.getValue(twoDwordBuf, WORD);
974             if (size < 0) {
975                 throw new ZipException("broken archive, entry with negative size");
976             }
977             current.entry.setSize(size);
978         } else {
979             long size = ZipEightByteInteger.getLongValue(twoDwordBuf);
980             if (size < 0) {
981                 throw new ZipException("broken archive, entry with negative compressed size");
982             }
983             current.entry.setCompressedSize(size);
984             size = ZipEightByteInteger.getLongValue(twoDwordBuf, DWORD);
985             if (size < 0) {
986                 throw new ZipException("broken archive, entry with negative size");
987             }
988             current.entry.setSize(size);
989         }
990     }
991 
992     /**
993      * Implementation of read for DEFLATED entries.
994      */
995     private int readDeflated(final byte[] buffer, final int offset, final int length) throws IOException {
996         final int read = readFromInflater(buffer, offset, length);
997         if (read <= 0) {
998             if (inf.finished()) {
999                 return -1;
1000             }
1001             if (inf.needsDictionary()) {
1002                 throw new ZipException("This archive needs a preset dictionary" + " which is not supported by Commons" + " Compress.");
1003             }
1004             if (read == -1) {
1005                 throw new IOException("Truncated ZIP file");
1006             }
1007         }
1008         return read;
1009     }
1010 
1011     /**
1012      * Fills the given array with the first local file header and deals with splitting/spanning markers that may prefix the first LFH.
1013      */
1014     private boolean readFirstLocalFileHeader() throws IOException {
1015         // for empty archive, we may get only EOCD size:
1016         final byte[] header = new byte[Math.min(LFH_LEN, ZipFile.MIN_EOCD_SIZE)];
1017         readFully(header);
1018         try {
1019             READ_LOOP: for (int i = 0; ; ) {
1020                 for (int j = 0; i <= PREAMBLE_GARBAGE_MAX_SIZE - 4 && j <= header.length - 4; ++j, ++i) {
1021                     final ZipLong sig = new ZipLong(header, j);
1022                     if (
1023                             sig.equals(ZipLong.LFH_SIG) ||
1024                             sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) ||
1025                             sig.equals(ZipLong.DD_SIG)) {
1026                         // regular archive containing at least one entry:
1027                         System.arraycopy(header, j, header, 0, header.length - j);
1028                         readFully(header, header.length - j);
1029                         break READ_LOOP;
1030                     }
1031                     if (
1032                             sig.equals(new ZipLong(ZipArchiveOutputStream.EOCD_SIG))
1033                     ) {
1034                         // empty archive:
1035                         pushback(header, j, header.length - j);
1036                         return false;
1037                     }
1038                 }
1039                 if (i >= PREAMBLE_GARBAGE_MAX_SIZE - 4) {
1040                     throw new ZipException("Cannot find zip signature within the first " + PREAMBLE_GARBAGE_MAX_SIZE + " bytes");
1041                 }
1042                 System.arraycopy(header, header.length - 3, header, 0, 3);
1043                 readFully(header, 3);
1044             }
1045             System.arraycopy(header, 0, lfhBuf, 0, header.length);
1046             readFully(lfhBuf, header.length);
1047         } catch (final EOFException ex) {
1048             throw new ZipException("Cannot find zip signature within the file");
1049         }
1050         final ZipLong sig = new ZipLong(lfhBuf);
1051 
1052         if (!skipSplitSig && sig.equals(ZipLong.DD_SIG)) {
1053             throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.SPLITTING);
1054         }
1055 
1056         // the split ZIP signature(08074B50) should only be skipped when the skipSplitSig is set
1057         if (sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) || sig.equals(ZipLong.DD_SIG)) {
1058             // Just skip over the marker.
1059             System.arraycopy(lfhBuf, 4, lfhBuf, 0, lfhBuf.length - 4);
1060             readFully(lfhBuf, lfhBuf.length - 4);
1061         }
1062         return true;
1063     }
1064 
1065     /**
1066      * Potentially reads more bytes to fill the inflater's buffer and reads from it.
1067      */
1068     private int readFromInflater(final byte[] buffer, final int offset, final int length) throws IOException {
1069         int read = 0;
1070         do {
1071             if (inf.needsInput()) {
1072                 final int l = fill();
1073                 if (l > 0) {
1074                     current.bytesReadFromStream += buf.limit();
1075                 } else if (l == -1) {
1076                     return -1;
1077                 } else {
1078                     break;
1079                 }
1080             }
1081             try {
1082                 read = inf.inflate(buffer, offset, length);
1083             } catch (final DataFormatException e) {
1084                 throw (IOException) new ZipException(e.getMessage()).initCause(e);
1085             }
1086         } while (read == 0 && inf.needsInput());
1087         return read;
1088     }
1089 
1090     private void readFully(final byte[] b) throws IOException {
1091         readFully(b, 0);
1092     }
1093 
1094     private void readFully(final byte[] b, final int off) throws IOException {
1095         final int len = b.length - off;
1096         final int count = IOUtils.readFully(in, b, off, len);
1097         count(count);
1098         if (count < len) {
1099             throw new EOFException();
1100         }
1101     }
1102 
1103     // End of Central Directory Record
1104     // end of central dir signature WORD
1105     // number of this disk SHORT
1106     // number of the disk with the
1107     // start of the central directory SHORT
1108     // total number of entries in the
1109     // central directory on this disk SHORT
1110     // total number of entries in
1111     // the central directory SHORT
1112     // size of the central directory WORD
1113     // offset of start of central
1114     // directory with respect to
1115     // the starting disk number WORD
1116     // .ZIP file comment length SHORT
1117     // .ZIP file comment up to 64KB
1118     //
1119 
1120     /**
1121      * Reads bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #read} would do.
1122      *
1123      * Also updates bytes-read counter.
1124      */
1125     private int readOneByte() throws IOException {
1126         final int b = in.read();
1127         if (b != -1) {
1128             count(1);
1129         }
1130         return b;
1131     }
1132 
1133     private byte[] readRange(final int len) throws IOException {
1134         final byte[] ret = IOUtils.readRange(in, len);
1135         count(ret.length);
1136         if (ret.length < len) {
1137             throw new EOFException();
1138         }
1139         return ret;
1140     }
1141 
1142     /**
1143      * Implementation of read for STORED entries.
1144      */
1145     private int readStored(final byte[] buffer, final int offset, final int length) throws IOException {
1146 
1147         if (current.hasDataDescriptor) {
1148             if (lastStoredEntry == null) {
1149                 readStoredEntry();
1150             }
1151             return lastStoredEntry.read(buffer, offset, length);
1152         }
1153 
1154         final long csize = current.entry.getSize();
1155         if (current.bytesRead >= csize) {
1156             return -1;
1157         }
1158 
1159         if (buf.position() >= buf.limit()) {
1160             buf.position(0);
1161             final int l = in.read(buf.array());
1162             if (l == -1) {
1163                 buf.limit(0);
1164                 throw new IOException("Truncated ZIP file");
1165             }
1166             buf.limit(l);
1167 
1168             count(l);
1169             current.bytesReadFromStream += l;
1170         }
1171 
1172         int toRead = Math.min(buf.remaining(), length);
1173         if (csize - current.bytesRead < toRead) {
1174             // if it is smaller than toRead then it fits into an int
1175             toRead = (int) (csize - current.bytesRead);
1176         }
1177         buf.get(buffer, offset, toRead);
1178         current.bytesRead += toRead;
1179         return toRead;
1180     }
1181 
1182     /**
1183      * Caches a stored entry that uses the data descriptor.
1184      * <ul>
1185      * <li>Reads a stored entry until the signature of a local file header, central directory header or data descriptor has been found.</li>
1186      * <li>Stores all entry data in lastStoredEntry.
1187      * </p>
1188      * <li>Rewinds the stream to position at the data descriptor.</li>
1189      * <li>reads the data descriptor</li>
1190      * </ul>
1191      * <p>
1192      * 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
1193      * directory header.
1194      * </p>
1195      */
1196     private void readStoredEntry() throws IOException {
1197         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
1198         int off = 0;
1199         boolean done = false;
1200 
1201         // length of DD without signature
1202         final int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD;
1203 
1204         while (!done) {
1205             final int r = in.read(buf.array(), off, ZipArchiveOutputStream.BUFFER_SIZE - off);
1206             if (r <= 0) {
1207                 // read the whole archive without ever finding a
1208                 // central directory
1209                 throw new IOException("Truncated ZIP file");
1210             }
1211             if (r + off < 4) {
1212                 // buffer too small to check for a signature, loop
1213                 off += r;
1214                 continue;
1215             }
1216 
1217             done = bufferContainsSignature(bos, off, r, ddLen);
1218             if (!done) {
1219                 off = cacheBytesRead(bos, off, r, ddLen);
1220             }
1221         }
1222         if (current.entry.getCompressedSize() != current.entry.getSize()) {
1223             throw new ZipException("compressed and uncompressed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER);
1224         }
1225         final byte[] b = bos.toByteArray();
1226         if (b.length != current.entry.getSize()) {
1227             throw new ZipException("actual and claimed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER);
1228         }
1229         lastStoredEntry = new ByteArrayInputStream(b);
1230     }
1231 
1232     /**
1233      * Skips bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #skip} would do.
1234      *
1235      * Also updates bytes-read counter.
1236      */
1237     private void realSkip(final long value) throws IOException {
1238         if (value >= 0) {
1239             long skipped = 0;
1240             while (skipped < value) {
1241                 final long rem = value - skipped;
1242                 final int x = in.read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length));
1243                 if (x == -1) {
1244                     return;
1245                 }
1246                 count(x);
1247                 skipped += x;
1248             }
1249             return;
1250         }
1251         throw new IllegalArgumentException();
1252     }
1253 
1254     /**
1255      * Enable custom extra fields factory.
1256      * @param extraFieldSupport the lookup function based on extra field header id.
1257      * @return the archive.
1258      */
1259     public ZipArchiveInputStream setExtraFieldSupport(final Function<ZipShort, ZipExtraField> extraFieldSupport) {
1260         this.extraFieldSupport = extraFieldSupport;
1261         return this;
1262     }
1263 
1264     /**
1265      * Skips over and discards value bytes of data from this input stream.
1266      * <p>
1267      * 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.
1268      * </p>
1269      * <p>
1270      * The actual number of bytes skipped is returned.
1271      * </p>
1272      *
1273      * @param value the number of bytes to be skipped.
1274      * @return the actual number of bytes skipped.
1275      * @throws IOException              - if an I/O error occurs.
1276      * @throws IllegalArgumentException - if value is negative.
1277      */
1278     @Override
1279     public long skip(final long value) throws IOException {
1280         if (value >= 0) {
1281             long skipped = 0;
1282             while (skipped < value) {
1283                 final long rem = value - skipped;
1284                 final int x = read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length));
1285                 if (x == -1) {
1286                     return skipped;
1287                 }
1288                 skipped += x;
1289             }
1290             return skipped;
1291         }
1292         throw new IllegalArgumentException();
1293     }
1294 
1295     /**
1296      * Reads the stream until it find the "End of central directory record" and consumes it as well.
1297      */
1298     private void skipRemainderOfArchive(final boolean read) throws IOException {
1299         // skip over central directory. One LFH has been read too much
1300         // already. The calculation discounts file names and extra
1301         // data, so it will be too short.
1302         if (entriesRead > 0) {
1303             realSkip((long) entriesRead * CFH_LEN - LFH_LEN);
1304         }
1305         final boolean foundEocd = findEocdRecord();
1306         if (foundEocd) {
1307             realSkip((long) ZipFile.MIN_EOCD_SIZE - WORD /* signature */ - SHORT /* comment len */);
1308             readFully(shortBuf);
1309             // file comment
1310             final int commentLen = ZipShort.getValue(shortBuf);
1311             if (commentLen >= 0) {
1312                 realSkip(commentLen);
1313                 return;
1314             }
1315         }
1316         throw new IOException("Truncated ZIP file");
1317     }
1318 
1319     /**
1320      * Whether the compressed size for the entry is either known or not required by the compression method being used.
1321      */
1322     private boolean supportsCompressedSizeFor(final ZipArchiveEntry entry) {
1323         return entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN || entry.getMethod() == ZipEntry.DEFLATED
1324                 || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode()
1325                 || entry.getGeneralPurposeBit().usesDataDescriptor() && allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED;
1326     }
1327 
1328     /**
1329      * Whether this entry requires a data descriptor this library can work with.
1330      *
1331      * @return true if allowStoredEntriesWithDataDescriptor is true, the entry doesn't require any data descriptor or the method is DEFLATED or
1332      *         ENHANCED_DEFLATED.
1333      */
1334     private boolean supportsDataDescriptorFor(final ZipArchiveEntry entry) {
1335         return !entry.getGeneralPurposeBit().usesDataDescriptor() || allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED
1336                 || entry.getMethod() == ZipEntry.DEFLATED || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode();
1337     }
1338 }