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