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