    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 *   https://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 java.io.BufferedInputStream;
    022import java.io.ByteArrayInputStream;
    023import java.io.Closeable;
    024import java.io.EOFException;
    025import java.io.File;
    026import java.io.IOException;
    027import java.io.InputStream;
    028import java.io.SequenceInputStream;
    029import java.nio.ByteBuffer;
    030import java.nio.ByteOrder;
    031import java.nio.channels.FileChannel;
    032import java.nio.channels.SeekableByteChannel;
    033import java.nio.charset.Charset;
    034import java.nio.charset.StandardCharsets;
    035import java.nio.file.Files;
    036import java.nio.file.OpenOption;
    037import java.nio.file.Path;
    038import java.nio.file.StandardOpenOption;
    039import java.util.Arrays;
    040import java.util.Collections;
    041import java.util.Comparator;
    042import java.util.EnumSet;
    043import java.util.Enumeration;
    044import java.util.HashMap;
    045import java.util.LinkedList;
    046import java.util.List;
    047import java.util.Map;
    048import java.util.Objects;
    049import java.util.stream.Collectors;
    050import java.util.stream.IntStream;
    051import java.util.zip.Inflater;
    052import java.util.zip.ZipException;
    053
    054import org.apache.commons.compress.archivers.EntryStreamOffsets;
    055import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
    056import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
    057import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
    058import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
    059import org.apache.commons.compress.utils.BoundedArchiveInputStream;
    060import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
    061import org.apache.commons.compress.utils.IOUtils;
    062import org.apache.commons.compress.utils.InputStreamStatistics;
    063import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
    064import org.apache.commons.io.Charsets;
    065import org.apache.commons.io.FilenameUtils;
    066import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
    067import org.apache.commons.io.build.AbstractStreamBuilder;
    068import org.apache.commons.io.function.IOFunction;
    069import org.apache.commons.io.function.IOStream;
    070import org.apache.commons.io.input.BoundedInputStream;
    071
    072/**
    073 * Replacement for {@link java.util.zip.ZipFile}.
    074 * <p>
    075 * This class adds support for file name encodings other than UTF-8 (which is required to work on ZIP files created by native ZIP tools and is able to skip a
    076 * preamble like the one found in self extracting archives. Furthermore it returns instances of
    077 * {@code org.apache.commons.compress.archivers.zip.ZipArchiveEntry} instead of {@link java.util.zip.ZipEntry}.
    078 * </p>
    079 * <p>
    080 * It doesn't extend {@link java.util.zip.ZipFile} as it would have to reimplement all methods anyway. Like {@link java.util.zip.ZipFile}, it uses
    081 * SeekableByteChannel under the covers and supports compressed and uncompressed entries. As of Apache Commons Compress 1.3 it also transparently supports Zip64
    082 * extensions and thus individual entries and archives larger than 4 GB or with more than 65,536 entries.
    083 * </p>
    084 * <p>
    085 * The method signatures mimic the ones of {@link java.util.zip.ZipFile}, with a couple of exceptions:
    086 * </p>
    087 * <ul>
    088 * <li>There is no getName method.</li>
    089 * <li>entries has been renamed to getEntries.</li>
    090 * <li>getEntries and getEntry return {@code org.apache.commons.compress.archivers.zip.ZipArchiveEntry} instances.</li>
    091 * <li>close is allowed to throw IOException.</li>
    092 * </ul>
    093 */
    094public class ZipFile implements Closeable {
    095
    096    /**
    097     * Lock-free implementation of BoundedInputStream. The implementation uses positioned reads on the underlying archive file channel and therefore performs
    098     * significantly faster in concurrent environment.
    099     */
    100    private static final class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
    101        private final FileChannel archive;
    102
    103        BoundedFileChannelInputStream(final long start, final long remaining, final FileChannel archive) {
    104            super(start, remaining);
    105            this.archive = archive;
    106        }
    107
    108        @Override
    109        protected int read(final long pos, final ByteBuffer buf) throws IOException {
    110            final int read = archive.read(buf, pos);
    111            buf.flip();
    112            return read;
    113        }
    114    }
    115
    116    /**
    117     * Builds new {@link ZipFile} instances.
    118     * <p>
    119     * The channel will be opened for reading, assuming the specified encoding for file names.
    120     * </p>
    121     * <p>
    122     * See {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} to read from an in-memory archive.
    123     * </p>
    124     * <p>
    125     * By default the central directory record and all local file headers of the archive will be read immediately which may take a considerable amount of time
    126     * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
    127     * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
    128     * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
    129     * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
    130     * </p>
    131     *
    132     * @since 1.26.0
    133     */
    134    public static class Builder extends AbstractStreamBuilder<ZipFile, Builder> {
    135
    136        static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    137        private SeekableByteChannel seekableByteChannel;
    138        private boolean useUnicodeExtraFields = true;
    139        private boolean ignoreLocalFileHeader;
    140        private long maxNumberOfDisks = 1;
    141        private IOFunction<InputStream, InputStream> zstdInputStreamFactory;
    142
    143        /**
    144         * Constructs a new instance.
    145         */
    146        public Builder() {
    147            setCharset(DEFAULT_CHARSET);
    148            setCharsetDefault(DEFAULT_CHARSET);
    149        }
    150
    151        @Override
    152        public ZipFile get() throws IOException {
    153            final SeekableByteChannel actualChannel;
    154            final String actualDescription;
    155            if (seekableByteChannel != null) {
    156                actualChannel = seekableByteChannel;
    157                actualDescription = actualChannel.getClass().getSimpleName();
    158            } else if (checkOrigin() instanceof ByteArrayOrigin) {
    159                actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
    160                actualDescription = actualChannel.getClass().getSimpleName();
    161            } else {
    162                OpenOption[] openOptions = getOpenOptions();
    163                if (openOptions.length == 0) {
    164                    openOptions = new OpenOption[] { StandardOpenOption.READ };
    165                }
    166                final Path path = getPath();
    167                actualChannel = openZipChannel(path, maxNumberOfDisks, openOptions);
    168                actualDescription = path.toString();
    169            }
    170            final boolean closeOnError = seekableByteChannel != null;
    171            return new ZipFile(actualChannel, actualDescription, getCharset(), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader,
    172                    zstdInputStreamFactory);
    173        }
    174
    175        /**
    176         * Sets whether to ignore information stored inside the local file header.
    177         *
    178         * @param ignoreLocalFileHeader whether to ignore information stored inside.
    179         * @return {@code this} instance.
    180         */
    181        public Builder setIgnoreLocalFileHeader(final boolean ignoreLocalFileHeader) {
    182            this.ignoreLocalFileHeader = ignoreLocalFileHeader;
    183            return this;
    184        }
    185
    186        /**
    187         * Sets max number of multi archive disks, default is 1 (no multi archive).
    188         *
    189         * @param maxNumberOfDisks max number of multi archive disks.
    190         * @return {@code this} instance.
    191         */
    192        public Builder setMaxNumberOfDisks(final long maxNumberOfDisks) {
    193            this.maxNumberOfDisks = maxNumberOfDisks;
    194            return this;
    195        }
    196
    197        /**
    198         * The actual channel, overrides any other input aspects like a File, Path, and so on.
    199         *
    200         * @param seekableByteChannel The actual channel.
    201         * @return {@code this} instance.
    202         */
    203        public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
    204            this.seekableByteChannel = seekableByteChannel;
    205            return this;
    206        }
    207
    208        /**
    209         * Sets whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    210         *
    211         * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    212         * @return {@code this} instance.
    213         */
    214        public Builder setUseUnicodeExtraFields(final boolean useUnicodeExtraFields) {
    215            this.useUnicodeExtraFields = useUnicodeExtraFields;
    216            return this;
    217        }
    218
    219        /**
    220         * Sets the factory {@link IOFunction} to create a Zstd {@link InputStream}. Defaults to
    221         * {@link ZstdCompressorInputStream#ZstdCompressorInputStream(InputStream)}.
    222         * <p>
    223         * Call this method to plugin an alternate Zstd input stream implementation.
    224         * </p>
    225         *
    226         * @param zstdInpStreamFactory the factory {@link IOFunction} to create a Zstd {@link InputStream}; {@code null} resets to the default.
    227         * @return {@code this} instance.
    228         * @since 1.28.0
    229         */
    230        public Builder setZstdInputStreamFactory(final IOFunction<InputStream, InputStream> zstdInpStreamFactory) {
    231            this.zstdInputStreamFactory = zstdInpStreamFactory;
    232            return this;
    233        }
    234
    235    }
    236
    237    /**
    238     * Extends ZipArchiveEntry to store the offset within the archive.
    239     */
    240    private static final class Entry extends ZipArchiveEntry {
    241
    242        @Override
    243        public boolean equals(final Object other) {
    244            if (super.equals(other)) {
    245                // super.equals would return false if other were not an Entry
    246                final Entry otherEntry = (Entry) other;
    247                return getLocalHeaderOffset() == otherEntry.getLocalHeaderOffset() && super.getDataOffset() == otherEntry.getDataOffset()
    248                        && super.getDiskNumberStart() == otherEntry.getDiskNumberStart();
    249            }
    250            return false;
    251        }
    252
    253        @Override
    254        public int hashCode() {
    255            return 3 * super.hashCode() + (int) getLocalHeaderOffset() + (int) (getLocalHeaderOffset() >> 32);
    256        }
    257    }
    258
    259    private static final class NameAndComment {
    260        private final byte[] name;
    261        private final byte[] comment;
    262
    263        private NameAndComment(final byte[] name, final byte[] comment) {
    264            this.name = name;
    265            this.comment = comment;
    266        }
    267    }
    268
    269    private static final class StoredStatisticsStream extends BoundedInputStream implements InputStreamStatistics {
    270        StoredStatisticsStream(final InputStream in) {
    271            super(in);
    272        }
    273
    274        @Override
    275        public long getCompressedCount() {
    276            return super.getCount();
    277        }
    278
    279        @Override
    280        public long getUncompressedCount() {
    281            return getCompressedCount();
    282        }
    283    }
    284
    285    private static final String DEFAULT_CHARSET_NAME = StandardCharsets.UTF_8.name();
    286
    287    private static final EnumSet<StandardOpenOption> READ = EnumSet.of(StandardOpenOption.READ);
    288
    289    private static final int HASH_SIZE = 509;
    290    static final int NIBLET_MASK = 0x0f;
    291    static final int BYTE_SHIFT = 8;
    292    private static final int POS_0 = 0;
    293    private static final int POS_1 = 1;
    294    private static final int POS_2 = 2;
    295    private static final int POS_3 = 3;
    296    private static final byte[] ONE_ZERO_BYTE = new byte[1];
    297
    298    /**
    299     * Length of a "central directory" entry structure without file name, extra fields or comment.
    300     */
    301    private static final int CFH_LEN =
    302    // @formatter:off
    303        /* version made by                 */ ZipConstants.SHORT
    304        /* version needed to extract       */ + ZipConstants.SHORT
    305        /* general purpose bit flag        */ + ZipConstants.SHORT
    306        /* compression method              */ + ZipConstants.SHORT
    307        /* last mod file time              */ + ZipConstants.SHORT
    308        /* last mod file date              */ + ZipConstants.SHORT
    309        /* CRC-32                          */ + ZipConstants.WORD
    310        /* compressed size                 */ + ZipConstants.WORD
    311        /* uncompressed size               */ + ZipConstants.WORD
    312        /* file name length                */ + ZipConstants. SHORT
    313        /* extra field length              */ + ZipConstants.SHORT
    314        /* file comment length             */ + ZipConstants.SHORT
    315        /* disk number start               */ + ZipConstants.SHORT
    316        /* internal file attributes        */ + ZipConstants.SHORT
    317        /* external file attributes        */ + ZipConstants.WORD
    318        /* relative offset of local header */ + ZipConstants.WORD;
    319    // @formatter:on
    320
    321    private static final long CFH_SIG = ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
    322
    323    /**
    324     * Length of the "End of central directory record" - which is supposed to be the last structure of the archive - without file comment.
    325     */
    326    static final int MIN_EOCD_SIZE =
    327    // @formatter:off
    328        /* end of central dir signature    */ ZipConstants.WORD
    329        /* number of this disk             */ + ZipConstants.SHORT
    330        /* number of the disk with the     */
    331        /* start of the central directory  */ + ZipConstants.SHORT
    332        /* total number of entries in      */
    333        /* the central dir on this disk    */ + ZipConstants.SHORT
    334        /* total number of entries in      */
    335        /* the central dir                 */ + ZipConstants.SHORT
    336        /* size of the central directory   */ + ZipConstants.WORD
    337        /* offset of start of central      */
    338        /* directory with respect to       */
    339        /* the starting disk number        */ + ZipConstants.WORD
    340        /* ZIP file comment length         */ + ZipConstants.SHORT;
    341    // @formatter:on
    342
    343    /**
    344     * Maximum length of the "End of central directory record" with a file comment.
    345     */
    346    private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
    347    // @formatter:off
    348        /* maximum length of ZIP file comment */ + ZipConstants.ZIP64_MAGIC_SHORT;
    349    // @formatter:on
    350
    351    /**
    352     * Offset of the field that holds the location of the length of the central directory inside the "End of central directory record" relative to the start of
    353     * the "End of central directory record".
    354     */
    355    private static final int CFD_LENGTH_OFFSET =
    356    // @formatter:off
    357        /* end of central dir signature    */ ZipConstants.WORD
    358        /* number of this disk             */ + ZipConstants.SHORT
    359        /* number of the disk with the     */
    360        /* start of the central directory  */ + ZipConstants.SHORT
    361        /* total number of entries in      */
    362        /* the central dir on this disk    */ + ZipConstants.SHORT
    363        /* total number of entries in      */
    364        /* the central dir                 */ + ZipConstants.SHORT;
    365    // @formatter:on
    366
    367    /**
    368     * Offset of the field that holds the disk number of the first central directory entry inside the "End of central directory record" relative to the start of
    369     * the "End of central directory record".
    370     */
    371    private static final int CFD_DISK_OFFSET =
    372    // @formatter:off
    373            /* end of central dir signature    */ ZipConstants.WORD
    374            /* number of this disk             */ + ZipConstants.SHORT;
    375    // @formatter:on
    376
    377    /**
    378     * Offset of the field that holds the location of the first central directory entry inside the "End of central directory record" relative to the "number of
    379     * the disk with the start of the central directory".
    380     */
    381    private static final int CFD_LOCATOR_RELATIVE_OFFSET =
    382    // @formatter:off
    383            /* total number of entries in      */
    384            /* the central dir on this disk    */ + ZipConstants.SHORT
    385            /* total number of entries in      */
    386            /* the central dir                 */ + ZipConstants.SHORT
    387            /* size of the central directory   */ + ZipConstants.WORD;
    388    // @formatter:on
    389
    390    /**
    391     * Length of the "Zip64 end of central directory locator" - which should be right in front of the "end of central directory record" if one is present at
    392     * all.
    393     */
    394    private static final int ZIP64_EOCDL_LENGTH =
    395    // @formatter:off
    396        /* zip64 end of central dir locator sig */ ZipConstants.WORD
    397        /* number of the disk with the start    */
    398        /* start of the zip64 end of            */
    399        /* central directory                    */ + ZipConstants.WORD
    400        /* relative offset of the zip64         */
    401        /* end of central directory record      */ + ZipConstants.DWORD
    402        /* total number of disks                */ + ZipConstants.WORD;
    403    // @formatter:on
    404
    405    /**
    406     * Offset of the field that holds the location of the "Zip64 end of central directory record" inside the "Zip64 end of central directory locator" relative
    407     * to the start of the "Zip64 end of central directory locator".
    408     */
    409    private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
    410    // @formatter:off
    411        /* zip64 end of central dir locator sig */ ZipConstants.WORD
    412        /* number of the disk with the start    */
    413        /* start of the zip64 end of            */
    414        /* central directory                    */ + ZipConstants.WORD;
    415    // @formatter:on
    416
    417    /**
    418     * Offset of the field that holds the location of the first central directory entry inside the "Zip64 end of central directory record" relative to the start
    419     * of the "Zip64 end of central directory record".
    420     */
    421    private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
    422    // @formatter:off
    423        /* zip64 end of central dir        */
    424        /* signature                       */ ZipConstants.WORD
    425        /* size of zip64 end of central    */
    426        /* directory record                */ + ZipConstants.DWORD
    427        /* version made by                 */ + ZipConstants.SHORT
    428        /* version needed to extract       */ + ZipConstants.SHORT
    429        /* number of this disk             */ + ZipConstants.WORD
    430        /* number of the disk with the     */
    431        /* start of the central directory  */ + ZipConstants.WORD
    432        /* total number of entries in the  */
    433        /* central directory on this disk  */ + ZipConstants.DWORD
    434        /* total number of entries in the  */
    435        /* central directory               */ + ZipConstants.DWORD
    436        /* size of the central directory   */ + ZipConstants.DWORD;
    437    // @formatter:on
    438
    439    /**
    440     * Offset of the field that holds the disk number of the first central directory entry inside the "Zip64 end of central directory record" relative to the
    441     * start of the "Zip64 end of central directory record".
    442     */
    443    private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
    444    // @formatter:off
    445            /* zip64 end of central dir        */
    446            /* signature                       */ ZipConstants.WORD
    447            /* size of zip64 end of central    */
    448            /* directory record                */ + ZipConstants.DWORD
    449            /* version made by                 */ + ZipConstants.SHORT
    450            /* version needed to extract       */ + ZipConstants.SHORT
    451            /* number of this disk             */ + ZipConstants.WORD;
    452    // @formatter:on
    453
    454    /**
    455     * Offset of the field that holds the location of the first central directory entry inside the "Zip64 end of central directory record" relative to the
    456     * "number of the disk with the start of the central directory".
    457     */
    458    private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
    459    // @formatter:off
    460            /* total number of entries in the  */
    461            /* central directory on this disk  */ ZipConstants.DWORD
    462            /* total number of entries in the  */
    463            /* central directory               */ + ZipConstants.DWORD
    464            /* size of the central directory   */ + ZipConstants.DWORD;
    465    // @formatter:on
    466
    467    /**
    468     * Number of bytes in local file header up to the &quot;length of file name&quot; entry.
    469     */
    470    private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
    471    // @formatter:off
    472        /* local file header signature     */ ZipConstants.WORD
    473        /* version needed to extract       */ + ZipConstants.SHORT
    474        /* general purpose bit flag        */ + ZipConstants.SHORT
    475        /* compression method              */ + ZipConstants.SHORT
    476        /* last mod file time              */ + ZipConstants.SHORT
    477        /* last mod file date              */ + ZipConstants.SHORT
    478        /* CRC-32                          */ + ZipConstants.WORD
    479        /* compressed size                 */ + ZipConstants.WORD
    480        /* uncompressed size               */ + (long) ZipConstants.WORD;
    481    // @formatter:on
    482
    483    /**
    484     * Compares two ZipArchiveEntries based on their offset within the archive.
    485     * <p>
    486     * Won't return any meaningful results if one of the entries isn't part of the archive at all.
    487     * </p>
    488     *
    489     * @since 1.1
    490     */
    491    private static final Comparator<ZipArchiveEntry> offsetComparator = Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
    492            .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);
    493
    494    /**
    495     * Creates a new Builder.
    496     *
    497     * @return a new Builder.
    498     * @since 1.26.0
    499     */
    500    public static Builder builder() {
    501        return new Builder();
    502    }
    503
    504    /**
    505     * Closes a ZIP file quietly; throwing no IOException, does nothing on null input.
    506     *
    507     * @param zipFile file to close, can be null
    508     */
    509    public static void closeQuietly(final ZipFile zipFile) {
    510        org.apache.commons.io.IOUtils.closeQuietly(zipFile);
    511    }
    512
    513    /**
    514     * Creates a new SeekableByteChannel for reading.
    515     *
    516     * @param path the path to the file to open or create
    517     * @return a new seekable byte channel
    518     * @throws IOException if an I/O error occurs
    519     */
    520    private static SeekableByteChannel newReadByteChannel(final Path path) throws IOException {
    521        return Files.newByteChannel(path, READ);
    522    }
    523
    524    private static SeekableByteChannel openZipChannel(final Path path, final long maxNumberOfDisks, final OpenOption[] openOptions) throws IOException {
    525        final FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
    526        try {
    527            final boolean is64 = positionAtEndOfCentralDirectoryRecord(channel);
    528            final long numberOfDisks;
    529            if (is64) {
    530                channel.position(channel.position() + ZipConstants.WORD + ZipConstants.WORD + ZipConstants.DWORD);
    531                final ByteBuffer buf = ByteBuffer.allocate(ZipConstants.WORD);
    532                buf.order(ByteOrder.LITTLE_ENDIAN);
    533                IOUtils.readFully(channel, buf);
    534                buf.flip();
    535                numberOfDisks = buf.getInt() & 0xffffffffL;
    536            } else {
    537                channel.position(channel.position() + ZipConstants.WORD);
    538                final ByteBuffer buf = ByteBuffer.allocate(ZipConstants.SHORT);
    539                buf.order(ByteOrder.LITTLE_ENDIAN);
    540                IOUtils.readFully(channel, buf);
    541                buf.flip();
    542                numberOfDisks = (buf.getShort() & 0xffff) + 1;
    543            }
    544            if (numberOfDisks > Math.min(maxNumberOfDisks, Integer.MAX_VALUE)) {
    545                throw new IOException("Too many disks for zip archive, max=" + Math.min(maxNumberOfDisks, Integer.MAX_VALUE) + " actual=" + numberOfDisks);
    546            }
    547
    548            if (numberOfDisks <= 1) {
    549                return channel;
    550            }
    551            channel.close();
    552
    553            final Path parent = path.getParent();
    554            final String basename = FilenameUtils.removeExtension(Objects.toString(path.getFileName(), null));
    555
    556            return ZipSplitReadOnlySeekableByteChannel.forPaths(IntStream.range(0, (int) numberOfDisks).mapToObj(i -> {
    557                if (i == numberOfDisks - 1) {
    558                    return path;
    559                }
    560                final Path lowercase = parent.resolve(String.format("%s.z%02d", basename, i + 1));
    561                if (Files.exists(lowercase)) {
    562                    return lowercase;
    563                }
    564                final Path uppercase = parent.resolve(String.format("%s.Z%02d", basename, i + 1));
    565                if (Files.exists(uppercase)) {
    566                    return uppercase;
    567                }
    568                return lowercase;
    569            }).collect(Collectors.toList()), openOptions);
    570        } catch (final Throwable ex) {
    571            org.apache.commons.io.IOUtils.closeQuietly(channel);
    572            throw ex;
    573        }
    574    }
    575
    576    /**
    577     * Searches for the and positions the stream at the start of the &quot;End of central dir record&quot;.
    578     *
    579     * @return true if it's Zip64 end of central directory or false if it's Zip32
    580     */
    581    private static boolean positionAtEndOfCentralDirectoryRecord(final SeekableByteChannel channel) throws IOException {
    582        final boolean found = tryToLocateSignature(channel, MIN_EOCD_SIZE, MAX_EOCD_SIZE, ZipArchiveOutputStream.EOCD_SIG);
    583        if (!found) {
    584            throw new ZipException("Archive is not a ZIP archive");
    585        }
    586        boolean found64 = false;
    587        final long position = channel.position();
    588        if (position > ZIP64_EOCDL_LENGTH) {
    589            final ByteBuffer wordBuf = ByteBuffer.allocate(4);
    590            channel.position(channel.position() - ZIP64_EOCDL_LENGTH);
    591            wordBuf.rewind();
    592            IOUtils.readFully(channel, wordBuf);
    593            wordBuf.flip();
    594            found64 = wordBuf.equals(ByteBuffer.wrap(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG));
    595            if (!found64) {
    596                channel.position(position);
    597            } else {
    598                channel.position(channel.position() - ZipConstants.WORD);
    599            }
    600        }
    601
    602        return found64;
    603    }
    604
    605    /**
    606     * Converts a raw version made by int to a <a href="https://pkwaredownloads.blob.core.windows.net/pkware-general/Documentation/APPNOTE_6.2.0.TXT">platform
    607     * code</a>.
    608     * <ul>
    609     * <li>0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)</li>
    610     * <li>1 - Amiga</li>
    611     * <li>2 - OpenVMS</li>
    612     * <li>3 - Unix</li>
    613     * <li>4 - VM/CMS</li>
    614     * <li>5 - Atari ST</li>
    615     * <li>6 - OS/2 H.P.F.S.</li>
    616     * <li>7 - Macintosh</li>
    617     * <li>8 - Z-System</li>
    618     * <li>9 - CP/M</li>
    619     * <li>10 - Windows NTFS</li>
    620     * <li>11 - MVS (OS/390 - Z/OS)</li>
    621     * <li>12 - VSE</li>
    622     * <li>13 - Acorn Risc</li>
    623     * <li>14 - VFAT</li>
    624     * <li>15 - alternate MVS</li>
    625     * <li>16 - BeOS</li>
    626     * <li>17 - Tandem</li>
    627     * <li>18 - OS/400</li>
    628     * <li>19 - OS/X (Darwin)</li>
    629     * <li>20 thru 255 - unused</li>
    630     * </ul>
    631     *
    632     * @param versionMadeBy version/
    633     * @return a platform code.
    634     */
    635    static int toPlatform(final int versionMadeBy) {
    636        return versionMadeBy >> BYTE_SHIFT & NIBLET_MASK;
    637    }
    638
    639    /**
    640     * Searches the archive backwards from minDistance to maxDistance for the given signature, positions the RandomaccessFile right at the signature if it has
    641     * been found.
    642     */
    643    private static boolean tryToLocateSignature(final SeekableByteChannel channel, final long minDistanceFromEnd, final long maxDistanceFromEnd,
    644            final byte[] sig) throws IOException {
    645        final ByteBuffer wordBuf = ByteBuffer.allocate(ZipConstants.WORD);
    646        boolean found = false;
    647        long off = channel.size() - minDistanceFromEnd;
    648        final long stopSearching = Math.max(0L, channel.size() - maxDistanceFromEnd);
    649        if (off >= 0) {
    650            for (; off >= stopSearching; off--) {
    651                channel.position(off);
    652                try {
    653                    wordBuf.rewind();
    654                    IOUtils.readFully(channel, wordBuf);
    655                    wordBuf.flip();
    656                } catch (final EOFException ex) { // NOSONAR
    657                    break;
    658                }
    659                int curr = wordBuf.get();
    660                if (curr == sig[POS_0]) {
    661                    curr = wordBuf.get();
    662                    if (curr == sig[POS_1]) {
    663                        curr = wordBuf.get();
    664                        if (curr == sig[POS_2]) {
    665                            curr = wordBuf.get();
    666                            if (curr == sig[POS_3]) {
    667                                found = true;
    668                                break;
    669                            }
    670                        }
    671                    }
    672                }
    673            }
    674        }
    675        if (found) {
    676            channel.position(off);
    677        }
    678        return found;
    679    }
    680
    681    /**
    682     * List of entries in the order they appear inside the central directory.
    683     */
    684    private final List<ZipArchiveEntry> entries = new LinkedList<>();
    685
    686    /**
    687     * Maps String to list of ZipArchiveEntrys, name -> actual entries.
    688     */
    689    private final Map<String, LinkedList<ZipArchiveEntry>> nameMap = new HashMap<>(HASH_SIZE);
    690
    691    /**
    692     * The encoding to use for file names and the file comment.
    693     * <p>
    694     * For a list of possible values see <a href="Supported Encodings">https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html</a>.
    695     * Defaults to UTF-8.
    696     * </p>
    697     */
    698    private final Charset encoding;
    699
    700    /**
    701     * The ZIP encoding to use for file names and the file comment.
    702     */
    703    private final ZipEncoding zipEncoding;
    704
    705    /**
    706     * The actual data source.
    707     */
    708    private final SeekableByteChannel archive;
    709
    710    /**
    711     * Whether to look for and use Unicode extra fields.
    712     */
    713    private final boolean useUnicodeExtraFields;
    714
    715    /**
    716     * Whether the file is closed.
    717     */
    718    private volatile boolean closed = true;
    719
    720    /**
    721     * Whether the ZIP archive is a split ZIP archive
    722     */
    723    private final boolean isSplitZipArchive;
    724
    725    // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
    726    private final byte[] dwordBuf = new byte[ZipConstants.DWORD];
    727
    728    private final byte[] wordBuf = new byte[ZipConstants.WORD];
    729
    730    private final byte[] cfhBuf = new byte[CFH_LEN];
    731
    732    private final byte[] shortBuf = new byte[ZipConstants.SHORT];
    733
    734    private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
    735
    736    private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
    737
    738    private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
    739
    740    private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
    741
    742    private final IOFunction<InputStream, InputStream> zstdInputStreamFactory;
    743
    744    private long centralDirectoryStartDiskNumber;
    745
    746    private long centralDirectoryStartRelativeOffset;
    747
    748    private long centralDirectoryStartOffset;
    749
    750    private long firstLocalFileHeaderOffset;
    751
    752    /**
    753     * Opens the given file for reading, assuming "UTF8" for file names.
    754     *
    755     * @param file the archive.
    756     * @throws IOException if an error occurs while reading the file.
    757     * @deprecated Use {@link Builder#get()}.
    758     */
    759    @Deprecated
    760    public ZipFile(final File file) throws IOException {
    761        this(file, DEFAULT_CHARSET_NAME);
    762    }
    763
    764    /**
    765     * Opens the given file for reading, assuming the specified encoding for file names and scanning for Unicode extra fields.
    766     *
    767     * @param file     the archive.
    768     * @param encoding the encoding to use for file names, use null for the platform's default encoding
    769     * @throws IOException if an error occurs while reading the file.
    770     * @deprecated Use {@link Builder#get()}.
    771     */
    772    @Deprecated
    773    public ZipFile(final File file, final String encoding) throws IOException {
    774        this(file.toPath(), encoding, true);
    775    }
    776
    777    /**
    778     * Opens the given file for reading, assuming the specified encoding for file names.
    779     *
    780     * @param file                  the archive.
    781     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    782     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    783     * @throws IOException if an error occurs while reading the file.
    784     * @deprecated Use {@link Builder#get()}.
    785     */
    786    @Deprecated
    787    public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
    788        this(file.toPath(), encoding, useUnicodeExtraFields, false);
    789    }
    790
    791    /**
    792     * Opens the given file for reading, assuming the specified encoding for file names.
    793     * <p>
    794     * By default the central directory record and all local file headers of the archive will be read immediately which may take a considerable amount of time
    795     * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
    796     * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
    797     * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
    798     * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
    799     * </p>
    800     *
    801     * @param file                  the archive.
    802     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    803     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    804     * @param ignoreLocalFileHeader whether to ignore information stored inside the local file header (see the notes in this method's Javadoc)
    805     * @throws IOException if an error occurs while reading the file.
    806     * @since 1.19
    807     * @deprecated Use {@link Builder#get()}.
    808     */
    809    @Deprecated
    810    @SuppressWarnings("resource") // Caller closes
    811    public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
    812        this(newReadByteChannel(file.toPath()), file.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
    813    }
    814
    815    /**
    816     * Opens the given path for reading, assuming "UTF-8" for file names.
    817     *
    818     * @param path path to the archive.
    819     * @throws IOException if an error occurs while reading the file.
    820     * @since 1.22
    821     * @deprecated Use {@link Builder#get()}.
    822     */
    823    @Deprecated
    824    public ZipFile(final Path path) throws IOException {
    825        this(path, DEFAULT_CHARSET_NAME);
    826    }
    827
    828    /**
    829     * Opens the given path for reading, assuming the specified encoding for file names and scanning for Unicode extra fields.
    830     *
    831     * @param path     path to the archive.
    832     * @param encoding the encoding to use for file names, use null for the platform's default encoding
    833     * @throws IOException if an error occurs while reading the file.
    834     * @since 1.22
    835     * @deprecated Use {@link Builder#get()}.
    836     */
    837    @Deprecated
    838    public ZipFile(final Path path, final String encoding) throws IOException {
    839        this(path, encoding, true);
    840    }
    841
    842    /**
    843     * Opens the given path for reading, assuming the specified encoding for file names.
    844     *
    845     * @param path                  path to the archive.
    846     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    847     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    848     * @throws IOException if an error occurs while reading the file.
    849     * @since 1.22
    850     * @deprecated Use {@link Builder#get()}.
    851     */
    852    @Deprecated
    853    public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
    854        this(path, encoding, useUnicodeExtraFields, false);
    855    }
    856
    857    /**
    858     * Opens the given path for reading, assuming the specified encoding for file names.
    859     * <p>
    860     * By default the central directory record and all local file headers of the archive will be read immediately which may take a considerable amount of time
    861     * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
    862     * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
    863     * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
    864     * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
    865     * </p>
    866     *
    867     * @param path                  path to the archive.
    868     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    869     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    870     * @param ignoreLocalFileHeader whether to ignore information stored inside the local file header (see the notes in this method's Javadoc)
    871     * @throws IOException if an error occurs while reading the file.
    872     * @since 1.22
    873     * @deprecated Use {@link Builder#get()}.
    874     */
    875    @SuppressWarnings("resource") // Caller closes
    876    @Deprecated
    877    public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
    878        this(newReadByteChannel(path), path.toAbsolutePath().toString(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
    879    }
    880
    881    /**
    882     * Opens the given channel for reading, assuming "UTF-8" for file names.
    883     * <p>
    884     * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
    885     * </p>
    886     *
    887     * @param channel the archive.
    888     * @throws IOException if an error occurs while reading the file.
    889     * @since 1.13
    890     * @deprecated Use {@link Builder#get()}.
    891     */
    892    @Deprecated
    893    public ZipFile(final SeekableByteChannel channel) throws IOException {
    894        this(channel, "a SeekableByteChannel", DEFAULT_CHARSET_NAME, true);
    895    }
    896
    897    /**
    898     * Opens the given channel for reading, assuming the specified encoding for file names.
    899     * <p>
    900     * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
    901     * </p>
    902     *
    903     * @param channel  the archive.
    904     * @param encoding the encoding to use for file names, use null for the platform's default encoding
    905     * @throws IOException if an error occurs while reading the file.
    906     * @since 1.13
    907     * @deprecated Use {@link Builder#get()}.
    908     */
    909    @Deprecated
    910    public ZipFile(final SeekableByteChannel channel, final String encoding) throws IOException {
    911        this(channel, "a SeekableByteChannel", encoding, true);
    912    }
    913
    914    private ZipFile(final SeekableByteChannel channel, final String channelDescription, final Charset encoding, final boolean useUnicodeExtraFields,
    915            final boolean closeOnError, final boolean ignoreLocalFileHeader, final IOFunction<InputStream, InputStream> zstdInputStream) throws IOException {
    916        this.isSplitZipArchive = channel instanceof ZipSplitReadOnlySeekableByteChannel;
    917        this.encoding = Charsets.toCharset(encoding, Builder.DEFAULT_CHARSET);
    918        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    919        this.useUnicodeExtraFields = useUnicodeExtraFields;
    920        this.archive = channel;
    921        this.zstdInputStreamFactory = zstdInputStream;
    922        boolean success = false;
    923        try {
    924            final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag = populateFromCentralDirectory();
    925            if (!ignoreLocalFileHeader) {
    926                resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
    927            }
    928            fillNameMap();
    929            success = true;
    930        } catch (final IOException e) {
    931            throw new IOException("Error reading Zip content from " + channelDescription, e);
    932        } finally {
    933            this.closed = !success;
    934            if (!success && closeOnError) {
    935                org.apache.commons.io.IOUtils.closeQuietly(archive);
    936            }
    937        }
    938    }
    939
    940    /**
    941     * Opens the given channel for reading, assuming the specified encoding for file names.
    942     * <p>
    943     * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
    944     * </p>
    945     *
    946     * @param channel               the archive.
    947     * @param channelDescription    description of the archive, used for error messages only.
    948     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    949     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    950     * @throws IOException if an error occurs while reading the file.
    951     * @since 1.13
    952     * @deprecated Use {@link Builder#get()}.
    953     */
    954    @Deprecated
    955    public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields)
    956            throws IOException {
    957        this(channel, channelDescription, encoding, useUnicodeExtraFields, false, false);
    958    }
    959
    960    /**
    961     * Opens the given channel for reading, assuming the specified encoding for file names.
    962     * <p>
    963     * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
    964     * </p>
    965     * <p>
    966     * By default the central directory record and all local file headers of the archive will be read immediately which may take a considerable amount of time
    967     * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
    968     * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
    969     * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
    970     * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
    971     * </p>
    972     *
    973     * @param channel               the archive.
    974     * @param channelDescription    description of the archive, used for error messages only.
    975     * @param encoding              the encoding to use for file names, use null for the platform's default encoding
    976     * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
    977     * @param ignoreLocalFileHeader whether to ignore information stored inside the local file header (see the notes in this method's Javadoc)
    978     * @throws IOException if an error occurs while reading the file.
    979     * @since 1.19
    980     * @deprecated Use {@link Builder#get()}.
    981     */
    982    @Deprecated
    983    public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
    984            final boolean ignoreLocalFileHeader) throws IOException {
    985        this(channel, channelDescription, encoding, useUnicodeExtraFields, false, ignoreLocalFileHeader);
    986    }
    987
    988    private ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
    989            final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException {
    990        this(channel, channelDescription, Charsets.toCharset(encoding), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader, null);
    991    }
    992
    993    /**
    994     * Opens the given file for reading, assuming "UTF-8".
    995     *
    996     * @param name name of the archive.
    997     * @throws IOException if an error occurs while reading the file.
    998     * @deprecated Use {@link Builder#get()}.
    999     */
    1000    @Deprecated
    1001    public ZipFile(final String name) throws IOException {
    1002        this(new File(name).toPath(), DEFAULT_CHARSET_NAME);
    1003    }
    1004
    1005    /**
    1006     * Opens the given file for reading, assuming the specified encoding for file names, scanning unicode extra fields.
    1007     *
    1008     * @param name     name of the archive.
    1009     * @param encoding the encoding to use for file names, use null for the platform's default encoding
    1010     * @throws IOException if an error occurs while reading the file.
    1011     * @deprecated Use {@link Builder#get()}.
    1012     */
    1013    @Deprecated
    1014    public ZipFile(final String name, final String encoding) throws IOException {
    1015        this(new File(name).toPath(), encoding, true);
    1016    }
    1017
    1018    /**
    1019     * Tests whether this class is able to read the given entry.
    1020     * <p>
    1021     * May return false if it is set up to use encryption or a compression method that hasn't been implemented yet.
    1022     * </p>
    1023     *
    1024     * @param entry the entry
    1025     * @return whether this class is able to read the given entry.
    1026     * @since 1.1
    1027     */
    1028    public boolean canReadEntryData(final ZipArchiveEntry entry) {
    1029        return ZipUtil.canHandleEntryData(entry);
    1030    }
    1031
    1032    /**
    1033     * Closes the archive.
    1034     *
    1035     * @throws IOException if an error occurs closing the archive.
    1036     */
    1037    @Override
    1038    public void close() throws IOException {
    1039        // this flag is only written here and read in finalize() which
    1040        // can never be run in parallel.
    1041        // no synchronization needed.
    1042        closed = true;
    1043        archive.close();
    1044    }
    1045
    1046    /**
    1047     * Transfer selected entries from this ZIP file to a given #ZipArchiveOutputStream. Compression and all other attributes will be as in this file.
    1048     * <p>
    1049     * This method transfers entries based on the central directory of the ZIP file.
    1050     * </p>
    1051     *
    1052     * @param target    The zipArchiveOutputStream to write the entries to
    1053     * @param predicate A predicate that selects which entries to write
    1054     * @throws IOException on error
    1055     */
    1056    public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate) throws IOException {
    1057        final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
    1058        while (src.hasMoreElements()) {
    1059            final ZipArchiveEntry entry = src.nextElement();
    1060            if (predicate.test(entry)) {
    1061                target.addRawArchiveEntry(entry, getRawInputStream(entry));
    1062            }
    1063        }
    1064    }
    1065
    1066    /**
    1067     * Creates new BoundedInputStream, according to implementation of underlying archive channel.
    1068     */
    1069    private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
    1070        if (start < 0 || remaining < 0 || start + remaining < start) {
    1071            throw new IllegalArgumentException("Corrupted archive, stream boundaries are out of range");
    1072        }
    1073        return archive instanceof FileChannel ? new BoundedFileChannelInputStream(start, remaining, (FileChannel) archive)
    1074                : new BoundedSeekableByteChannelInputStream(start, remaining, archive);
    1075    }
    1076
    1077    /**
    1078     * Creates an InputStream for the Zstd compression method.
    1079     *
    1080     * @param in the input stream which should be used for compression.
    1081     * @return the {@link InputStream} for handling the Zstd compression.
    1082     * @throws IOException if an I/O error occurs.
    1083     */
    1084    @SuppressWarnings("resource")
    1085    InputStream createZstdInputStream(final InputStream in) throws IOException {
    1086        // This method is the only location that references ZstdCompressorInputStream directly to avoid requiring the JAR for all use cases.
    1087        return zstdInputStreamFactory != null ? zstdInputStreamFactory.apply(in) : new ZstdCompressorInputStream(in);
    1088    }
    1089
    1090    private void fillNameMap() {
    1091        entries.forEach(ze -> {
    1092            // entries are filled in populateFromCentralDirectory and
    1093            // never modified
    1094            final String name = ze.getName();
    1095            final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
    1096            entriesOfThatName.addLast(ze);
    1097        });
    1098    }
    1099
    1100    /**
    1101     * Ensures that the close method of this ZIP file is called when there are no more references to it.
    1102     *
    1103     * @see #close()
    1104     */
    1105    @Override
    1106    protected void finalize() throws Throwable {
    1107        try {
    1108            if (!closed) {
    1109                close();
    1110            }
    1111        } finally {
    1112            super.finalize();
    1113        }
    1114    }
    1115
    1116    /**
    1117     * Gets an InputStream for reading the content before the first local file header.
    1118     *
    1119     * @return null if there is no content before the first local file header. Otherwise, returns a stream to read the content before the first local file
    1120     *         header.
    1121     * @since 1.23
    1122     */
    1123    public InputStream getContentBeforeFirstLocalFileHeader() {
    1124        return firstLocalFileHeaderOffset == 0 ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
    1125    }
    1126
    1127    private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
    1128        final long s = ze.getDataOffset();
    1129        if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
    1130            setDataOffset(ze);
    1131            return ze.getDataOffset();
    1132        }
    1133        return s;
    1134    }
    1135
    1136    /**
    1137     * Gets the encoding to use for file names and the file comment.
    1138     *
    1139     * @return null if using the platform's default character encoding.
    1140     */
    1141    public String getEncoding() {
    1142        return encoding.name();
    1143    }
    1144
    1145    /**
    1146     * Gets all entries.
    1147     * <p>
    1148     * Entries will be returned in the same order they appear within the archive's central directory.
    1149     * </p>
    1150     *
    1151     * @return all entries as {@link ZipArchiveEntry} instances
    1152     */
    1153    public Enumeration<ZipArchiveEntry> getEntries() {
    1154        return Collections.enumeration(entries);
    1155    }
    1156
    1157    /**
    1158     * Gets all named entries in the same order they appear within the archive's central directory.
    1159     *
    1160     * @param name name of the entry.
    1161     * @return the Iterable&lt;ZipArchiveEntry&gt; corresponding to the given name
    1162     * @since 1.6
    1163     */
    1164    public Iterable<ZipArchiveEntry> getEntries(final String name) {
    1165        return nameMap.getOrDefault(name, ZipArchiveEntry.EMPTY_LINKED_LIST);
    1166    }
    1167
    1168    /**
    1169     * Gets all entries in physical order.
    1170     * <p>
    1171     * Entries will be returned in the same order their contents appear within the archive.
    1172     * </p>
    1173     *
    1174     * @return all entries as {@link ZipArchiveEntry} instances
    1175     * @since 1.1
    1176     */
    1177    public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
    1178        final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
    1179        return Collections.enumeration(Arrays.asList(sortByOffset(allEntries)));
    1180    }
    1181
    1182    /**
    1183     * Gets all named entries in the same order their contents appear within the archive.
    1184     *
    1185     * @param name name of the entry.
    1186     * @return the Iterable&lt;ZipArchiveEntry&gt; corresponding to the given name
    1187     * @since 1.6
    1188     */
    1189    public Iterable<ZipArchiveEntry> getEntriesInPhysicalOrder(final String name) {
    1190        final LinkedList<ZipArchiveEntry> linkedList = nameMap.getOrDefault(name, ZipArchiveEntry.EMPTY_LINKED_LIST);
    1191        return Arrays.asList(sortByOffset(linkedList.toArray(ZipArchiveEntry.EMPTY_ARRAY)));
    1192    }
    1193
    1194    /**
    1195     * Gets a named entry or {@code null} if no entry by that name exists.
    1196     * <p>
    1197     * If multiple entries with the same name exist the first entry in the archive's central directory by that name is returned.
    1198     * </p>
    1199     *
    1200     * @param name name of the entry.
    1201     * @return the ZipArchiveEntry corresponding to the given name - or {@code null} if not present.
    1202     */
    1203    public ZipArchiveEntry getEntry(final String name) {
    1204        final LinkedList<ZipArchiveEntry> entries = nameMap.get(name);
    1205        return entries != null ? entries.getFirst() : null;
    1206    }
    1207
    1208    /**
    1209     * Gets the offset of the first local file header in the file.
    1210     *
    1211     * @return the length of the content before the first local file header
    1212     * @since 1.23
    1213     */
    1214    public long getFirstLocalFileHeaderOffset() {
    1215        return firstLocalFileHeaderOffset;
    1216    }
    1217
    1218    /**
    1219     * Gets an InputStream for reading the contents of the given entry.
    1220     *
    1221     * @param entry the entry to get the stream for.
    1222     * @return a stream to read the entry from. The returned stream implements {@link InputStreamStatistics}.
    1223     * @throws IOException if unable to create an input stream from the zipEntry.
    1224     */
    1225    public InputStream getInputStream(final ZipArchiveEntry entry) throws IOException {
    1226        if (!(entry instanceof Entry)) {
    1227            return null;
    1228        }
    1229        // cast validity is checked just above
    1230        ZipUtil.checkRequestedFeatures(entry);
    1231
    1232        // doesn't get closed if the method is not supported - which
    1233        // should never happen because of the checkRequestedFeatures
    1234        // call above
    1235        final InputStream is = new BufferedInputStream(getRawInputStream(entry)); // NOSONAR
    1236        switch (ZipMethod.getMethodByCode(entry.getMethod())) {
    1237        case STORED:
    1238            return new StoredStatisticsStream(is);
    1239        case UNSHRINKING:
    1240            return new UnshrinkingInputStream(is);
    1241        case IMPLODING:
    1242            try {
    1243                return new ExplodingInputStream(entry.getGeneralPurposeBit().getSlidingDictionarySize(),
    1244                        entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), is);
    1245            } catch (final IllegalArgumentException ex) {
    1246                throw new IOException("bad IMPLODE data", ex);
    1247            }
    1248        case DEFLATED:
    1249            final Inflater inflater = new Inflater(true);
    1250            // Inflater with nowrap=true has this odd contract for a zero padding
    1251            // byte following the data stream; this used to be zlib's requirement
    1252            // and has been fixed a long time ago, but the contract persists so
    1253            // we comply.
    1254            // https://docs.oracle.com/javase/8/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
    1255            return new InflaterInputStreamWithStatistics(new SequenceInputStream(is, new ByteArrayInputStream(ONE_ZERO_BYTE)), inflater) {
    1256                @Override
    1257                public void close() throws IOException {
    1258                    try {
    1259                        super.close();
    1260                    } finally {
    1261                        inflater.end();
    1262                    }
    1263                }
    1264            };
    1265        case BZIP2:
    1266            return new BZip2CompressorInputStream(is);
    1267        case ENHANCED_DEFLATED:
    1268            return new Deflate64CompressorInputStream(is);
    1269        case ZSTD:
    1270        case ZSTD_DEPRECATED:
    1271            return createZstdInputStream(is);
    1272        case XZ:
    1273            return new XZCompressorInputStream(is);
    1274        case AES_ENCRYPTED:
    1275        case EXPANDING_LEVEL_1:
    1276        case EXPANDING_LEVEL_2:
    1277        case EXPANDING_LEVEL_3:
    1278        case EXPANDING_LEVEL_4:
    1279        case JPEG:
    1280        case LZMA:
    1281        case PKWARE_IMPLODING:
    1282        case PPMD:
    1283        case TOKENIZATION:
    1284        case UNKNOWN:
    1285        case WAVPACK:
    1286        default:
    1287            throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(entry.getMethod()), entry);
    1288        }
    1289    }
    1290
    1291    /**
    1292     * Gets the raw stream of the archive entry (compressed form).
    1293     * <p>
    1294     * This method does not relate to how/if we understand the payload in the stream, since we really only intend to move it on to somewhere else.
    1295     * </p>
    1296     * <p>
    1297     * Since version 1.22, this method will make an attempt to read the entry's data stream offset, even if the {@code ignoreLocalFileHeader} parameter was
    1298     * {@code true} in the constructor. An IOException can also be thrown from the body of the method if this lookup fails for some reason.
    1299     * </p>
    1300     *
    1301     * @param entry The entry to get the stream for
    1302     * @return The raw input stream containing (possibly) compressed data.
    1303     * @throws IOException if there is a problem reading data offset (added in version 1.22).
    1304     * @since 1.11
    1305     */
    1306    public InputStream getRawInputStream(final ZipArchiveEntry entry) throws IOException {
    1307        if (!(entry instanceof Entry)) {
    1308            return null;
    1309        }
    1310        final long start = getDataOffset(entry);
    1311        if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
    1312            return null;
    1313        }
    1314        return createBoundedInputStream(start, entry.getCompressedSize());
    1315    }
    1316
    1317    /**
    1318     * Gets the entry's content as a String if isUnixSymlink() returns true for it, otherwise returns null.
    1319     * <p>
    1320     * This method assumes the symbolic link's file name uses the same encoding that as been specified for this ZipFile.
    1321     * </p>
    1322     *
    1323     * @param entry ZipArchiveEntry object that represents the symbolic link
    1324     * @return entry's content as a String
    1325     * @throws IOException problem with content's input stream
    1326     * @since 1.5
    1327     */
    1328    public String getUnixSymlink(final ZipArchiveEntry entry) throws IOException {
    1329        if (entry != null && entry.isUnixSymlink()) {
    1330            try (InputStream in = getInputStream(entry)) {
    1331                return zipEncoding.decode(org.apache.commons.io.IOUtils.toByteArray(in));
    1332            }
    1333        }
    1334        return null;
    1335    }
    1336
    1337    /**
    1338     * Reads the central directory of the given archive and populates the internal tables with ZipArchiveEntry instances.
    1339     * <p>
    1340     * The ZipArchiveEntrys will know all data that can be obtained from the central directory alone, but not the data that requires the local file header or
    1341     * additional data to be read.
    1342     * </p>
    1343     *
    1344     * @return a map of zip entries that didn't have the language encoding flag set when read.
    1345     */
    1346    private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory() throws IOException {
    1347        final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag = new HashMap<>();
    1348
    1349        positionAtCentralDirectory();
    1350        centralDirectoryStartOffset = archive.position();
    1351
    1352        wordBbuf.rewind();
    1353        IOUtils.readFully(archive, wordBbuf);
    1354        long sig = ZipLong.getValue(wordBuf);
    1355
    1356        if (sig != CFH_SIG && startsWithLocalFileHeader()) {
    1357            throw new IOException("Central directory is empty, can't expand corrupt archive.");
    1358        }
    1359
    1360        while (sig == CFH_SIG) {
    1361            readCentralDirectoryEntry(noUTF8Flag);
    1362            wordBbuf.rewind();
    1363            IOUtils.readFully(archive, wordBbuf);
    1364            sig = ZipLong.getValue(wordBuf);
    1365        }
    1366        return noUTF8Flag;
    1367    }
    1368
    1369    /**
    1370     * Searches for either the &quot;Zip64 end of central directory locator&quot; or the &quot;End of central dir record&quot;, parses it and positions the
    1371     * stream at the first central directory record.
    1372     */
    1373    private void positionAtCentralDirectory() throws IOException {
    1374        final boolean is64 = positionAtEndOfCentralDirectoryRecord(archive);
    1375        if (!is64) {
    1376            positionAtCentralDirectory32();
    1377        } else {
    1378            positionAtCentralDirectory64();
    1379        }
    1380    }
    1381
    1382    /**
    1383     * Parses the &quot;End of central dir record&quot; and positions the stream at the first central directory record.
    1384     *
    1385     * Expects stream to be positioned at the beginning of the &quot;End of central dir record&quot;.
    1386     */
    1387    private void positionAtCentralDirectory32() throws IOException {
    1388        final long endOfCentralDirectoryRecordOffset = archive.position();
    1389        if (isSplitZipArchive) {
    1390            skipBytes(CFD_DISK_OFFSET);
    1391            shortBbuf.rewind();
    1392            IOUtils.readFully(archive, shortBbuf);
    1393            centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);
    1394
    1395            skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
    1396
    1397            wordBbuf.rewind();
    1398            IOUtils.readFully(archive, wordBbuf);
    1399            centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
    1400            ((ZipSplitReadOnlySeekableByteChannel) archive).position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
    1401        } else {
    1402            skipBytes(CFD_LENGTH_OFFSET);
    1403            wordBbuf.rewind();
    1404            IOUtils.readFully(archive, wordBbuf);
    1405            final long centralDirectoryLength = ZipLong.getValue(wordBuf);
    1406
    1407            wordBbuf.rewind();
    1408            IOUtils.readFully(archive, wordBbuf);
    1409            centralDirectoryStartDiskNumber = 0;
    1410            centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
    1411
    1412            firstLocalFileHeaderOffset = Long.max(endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset, 0L);
    1413            archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
    1414        }
    1415    }
    1416
    1417    /**
    1418     * Parses the &quot;Zip64 end of central directory locator&quot;, finds the &quot;Zip64 end of central directory record&quot; using the parsed information,
    1419     * parses that and positions the stream at the first central directory record.
    1420     *
    1421     * Expects stream to be positioned right behind the &quot;Zip64 end of central directory locator&quot;'s signature.
    1422     */
    1423    private void positionAtCentralDirectory64() throws IOException {
    1424        skipBytes(ZipConstants.WORD);
    1425        if (isSplitZipArchive) {
    1426            wordBbuf.rewind();
    1427            IOUtils.readFully(archive, wordBbuf);
    1428            final long diskNumberOfEOCD = ZipLong.getValue(wordBuf);
    1429
    1430            dwordBbuf.rewind();
    1431            IOUtils.readFully(archive, dwordBbuf);
    1432            final long relativeOffsetOfEOCD = ZipEightByteInteger.getLongValue(dwordBuf);
    1433            ((ZipSplitReadOnlySeekableByteChannel) archive).position(diskNumberOfEOCD, relativeOffsetOfEOCD);
    1434        } else {
    1435            skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET - ZipConstants.WORD /* signature has already been read */);
    1436            dwordBbuf.rewind();
    1437            IOUtils.readFully(archive, dwordBbuf);
    1438            archive.position(ZipEightByteInteger.getLongValue(dwordBuf));
    1439        }
    1440
    1441        wordBbuf.rewind();
    1442        IOUtils.readFully(archive, wordBbuf);
    1443        if (!Arrays.equals(wordBuf, ZipArchiveOutputStream.ZIP64_EOCD_SIG)) {
    1444            throw new ZipException("Archive's ZIP64 end of central directory locator is corrupt.");
    1445        }
    1446
    1447        if (isSplitZipArchive) {
    1448            skipBytes(ZIP64_EOCD_CFD_DISK_OFFSET - ZipConstants.WORD /* signature has already been read */);
    1449            wordBbuf.rewind();
    1450            IOUtils.readFully(archive, wordBbuf);
    1451            centralDirectoryStartDiskNumber = ZipLong.getValue(wordBuf);
    1452
    1453            skipBytes(ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET);
    1454
    1455            dwordBbuf.rewind();
    1456            IOUtils.readFully(archive, dwordBbuf);
    1457            centralDirectoryStartRelativeOffset = ZipEightByteInteger.getLongValue(dwordBuf);
    1458            ((ZipSplitReadOnlySeekableByteChannel) archive).position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
    1459        } else {
    1460            skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET - ZipConstants.WORD /* signature has already been read */);
    1461            dwordBbuf.rewind();
    1462            IOUtils.readFully(archive, dwordBbuf);
    1463            centralDirectoryStartDiskNumber = 0;
    1464            centralDirectoryStartRelativeOffset = ZipEightByteInteger.getLongValue(dwordBuf);
    1465            archive.position(centralDirectoryStartRelativeOffset);
    1466        }
    1467    }
    1468
    1469    /**
    1470     * Reads an individual entry of the central directory, creates an ZipArchiveEntry from it and adds it to the global maps.
    1471     *
    1472     * @param noUTF8Flag map used to collect entries that don't have their UTF-8 flag set and whose name will be set by data read from the local file header
    1473     *                   later. The current entry may be added to this map.
    1474     */
    1475    private void readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag) throws IOException {
    1476        cfhBbuf.rewind();
    1477        IOUtils.readFully(archive, cfhBbuf);
    1478        int off = 0;
    1479        final Entry ze = new Entry();
    1480
    1481        final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
    1482        off += ZipConstants.SHORT;
    1483        ze.setVersionMadeBy(versionMadeBy);
    1484        ze.setPlatform(toPlatform(versionMadeBy));
    1485
    1486        ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
    1487        off += ZipConstants.SHORT; // version required
    1488
    1489        final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
    1490        final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
    1491        final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
    1492        if (hasUTF8Flag) {
    1493            ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
    1494        }
    1495        ze.setGeneralPurposeBit(gpFlag);
    1496        ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
    1497
    1498        off += ZipConstants.SHORT;
    1499
    1500        // noinspection MagicConstant
    1501        ze.setMethod(ZipShort.getValue(cfhBuf, off));
    1502        off += ZipConstants.SHORT;
    1503
    1504        final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
    1505        ze.setTime(time);
    1506        off += ZipConstants.WORD;
    1507
    1508        ze.setCrc(ZipLong.getValue(cfhBuf, off));
    1509        off += ZipConstants.WORD;
    1510
    1511        long size = ZipLong.getValue(cfhBuf, off);
    1512        if (size < 0) {
    1513            throw new IOException("broken archive, entry with negative compressed size");
    1514        }
    1515        ze.setCompressedSize(size);
    1516        off += ZipConstants.WORD;
    1517
    1518        size = ZipLong.getValue(cfhBuf, off);
    1519        if (size < 0) {
    1520            throw new IOException("broken archive, entry with negative size");
    1521        }
    1522        ze.setSize(size);
    1523        off += ZipConstants.WORD;
    1524
    1525        final int fileNameLen = ZipShort.getValue(cfhBuf, off);
    1526        off += ZipConstants.SHORT;
    1527        if (fileNameLen < 0) {
    1528            throw new IOException("broken archive, entry with negative fileNameLen");
    1529        }
    1530
    1531        final int extraLen = ZipShort.getValue(cfhBuf, off);
    1532        off += ZipConstants.SHORT;
    1533        if (extraLen < 0) {
    1534            throw new IOException("broken archive, entry with negative extraLen");
    1535        }
    1536
    1537        final int commentLen = ZipShort.getValue(cfhBuf, off);
    1538        off += ZipConstants.SHORT;
    1539        if (commentLen < 0) {
    1540            throw new IOException("broken archive, entry with negative commentLen");
    1541        }
    1542
    1543        ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
    1544        off += ZipConstants.SHORT;
    1545
    1546        ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
    1547        off += ZipConstants.SHORT;
    1548
    1549        ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
    1550        off += ZipConstants.WORD;
    1551
    1552        final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
    1553        if (fileName.length < fileNameLen) {
    1554            throw new EOFException();
    1555        }
    1556        ze.setName(entryEncoding.decode(fileName), fileName);
    1557
    1558        // LFH offset,
    1559        ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
    1560        // data offset will be filled later
    1561        entries.add(ze);
    1562
    1563        final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
    1564        if (cdExtraData.length < extraLen) {
    1565            throw new EOFException();
    1566        }
    1567        try {
    1568            ze.setCentralDirectoryExtra(cdExtraData);
    1569        } catch (final RuntimeException e) {
    1570            throw ZipUtil.newZipException("Invalid extra data in entry " + ze.getName(), e);
    1571        }
    1572
    1573        setSizesAndOffsetFromZip64Extra(ze);
    1574        sanityCheckLFHOffset(ze);
    1575
    1576        final byte[] comment = IOUtils.readRange(archive, commentLen);
    1577        if (comment.length < commentLen) {
    1578            throw new EOFException();
    1579        }
    1580        ze.setComment(entryEncoding.decode(comment));
    1581
    1582        if (!hasUTF8Flag && useUnicodeExtraFields) {
    1583            noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
    1584        }
    1585
    1586        ze.setStreamContiguous(true);
    1587    }
    1588
    1589    /**
    1590     * Walks through all recorded entries and adds the data available from the local file header.
    1591     * <p>
    1592     * Also records the offsets for the data to read from the entries.
    1593     * </p>
    1594     */
    1595    private void resolveLocalFileHeaderData(final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag) throws IOException {
    1596        for (final ZipArchiveEntry zipArchiveEntry : entries) {
    1597            // entries are filled in populateFromCentralDirectory and never modified
    1598            final Entry ze = (Entry) zipArchiveEntry;
    1599            final int[] lens = setDataOffset(ze);
    1600            final int fileNameLen = lens[0];
    1601            final int extraFieldLen = lens[1];
    1602            skipBytes(fileNameLen);
    1603            final byte[] localExtraData = IOUtils.readRange(archive, extraFieldLen);
    1604            if (localExtraData.length < extraFieldLen) {
    1605                throw new EOFException();
    1606            }
    1607            try {
    1608                ze.setExtra(localExtraData);
    1609            } catch (final RuntimeException e) {
    1610                throw ZipUtil.newZipException("Invalid extra data in entry " + ze.getName(), e);
    1611            }
    1612            if (entriesWithoutUTF8Flag.containsKey(ze)) {
    1613                final NameAndComment nc = entriesWithoutUTF8Flag.get(ze);
    1614                ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, nc.comment);
    1615            }
    1616        }
    1617    }
    1618
    1619    private void sanityCheckLFHOffset(final ZipArchiveEntry entry) throws IOException {
    1620        if (entry.getDiskNumberStart() < 0) {
    1621            throw new IOException("broken archive, entry with negative disk number");
    1622        }
    1623        if (entry.getLocalHeaderOffset() < 0) {
    1624            throw new IOException("broken archive, entry with negative local file header offset");
    1625        }
    1626        if (isSplitZipArchive) {
    1627            if (entry.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
    1628                throw new IOException("local file header for " + entry.getName() + " starts on a later disk than central directory");
    1629            }
    1630            if (entry.getDiskNumberStart() == centralDirectoryStartDiskNumber && entry.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
    1631                throw new IOException("local file header for " + entry.getName() + " starts after central directory");
    1632            }
    1633        } else if (entry.getLocalHeaderOffset() > centralDirectoryStartOffset) {
    1634            throw new IOException("local file header for " + entry.getName() + " starts after central directory");
    1635        }
    1636    }
    1637
    1638    private int[] setDataOffset(final ZipArchiveEntry entry) throws IOException {
    1639        long offset = entry.getLocalHeaderOffset();
    1640        if (isSplitZipArchive) {
    1641            ((ZipSplitReadOnlySeekableByteChannel) archive).position(entry.getDiskNumberStart(), offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
    1642            // the offset should be updated to the global offset
    1643            offset = archive.position() - LFH_OFFSET_FOR_FILENAME_LENGTH;
    1644        } else {
    1645            archive.position(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
    1646        }
    1647        wordBbuf.rewind();
    1648        IOUtils.readFully(archive, wordBbuf);
    1649        wordBbuf.flip();
    1650        wordBbuf.get(shortBuf);
    1651        final int fileNameLen = ZipShort.getValue(shortBuf);
    1652        wordBbuf.get(shortBuf);
    1653        final int extraFieldLen = ZipShort.getValue(shortBuf);
    1654        entry.setDataOffset(offset + LFH_OFFSET_FOR_FILENAME_LENGTH + ZipConstants.SHORT + ZipConstants.SHORT + fileNameLen + extraFieldLen);
    1655        if (entry.getDataOffset() + entry.getCompressedSize() > centralDirectoryStartOffset) {
    1656            throw new IOException("data for " + entry.getName() + " overlaps with central directory.");
    1657        }
    1658        return new int[] { fileNameLen, extraFieldLen };
    1659    }
    1660
    1661    /**
    1662     * If the entry holds a Zip64 extended information extra field, read sizes from there if the entry's sizes are set to 0xFFFFFFFFF, do the same for the
    1663     * offset of the local file header.
    1664     * <p>
    1665     * Ensures the Zip64 extra either knows both compressed and uncompressed size or neither of both as the internal logic in ExtraFieldUtils forces the field
    1666     * to create local header data even if they are never used - and here a field with only one size would be invalid.
    1667     * </p>
    1668     */
    1669    private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry entry) throws IOException {
    1670        final ZipExtraField extra = entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
    1671        if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
    1672            throw new ZipException("archive contains unparseable zip64 extra field");
    1673        }
    1674        final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra;
    1675        if (z64 != null) {
    1676            final boolean hasUncompressedSize = entry.getSize() == ZipConstants.ZIP64_MAGIC;
    1677            final boolean hasCompressedSize = entry.getCompressedSize() == ZipConstants.ZIP64_MAGIC;
    1678            final boolean hasRelativeHeaderOffset = entry.getLocalHeaderOffset() == ZipConstants.ZIP64_MAGIC;
    1679            final boolean hasDiskStart = entry.getDiskNumberStart() == ZipConstants.ZIP64_MAGIC_SHORT;
    1680            z64.reparseCentralDirectoryData(hasUncompressedSize, hasCompressedSize, hasRelativeHeaderOffset, hasDiskStart);
    1681
    1682            if (hasUncompressedSize) {
    1683                final long size = z64.getSize().getLongValue();
    1684                if (size < 0) {
    1685                    throw new IOException("broken archive, entry with negative size");
    1686                }
    1687                entry.setSize(size);
    1688            } else if (hasCompressedSize) {
    1689                z64.setSize(new ZipEightByteInteger(entry.getSize()));
    1690            }
    1691
    1692            if (hasCompressedSize) {
    1693                final long size = z64.getCompressedSize().getLongValue();
    1694                if (size < 0) {
    1695                    throw new IOException("broken archive, entry with negative compressed size");
    1696                }
    1697                entry.setCompressedSize(size);
    1698            } else if (hasUncompressedSize) {
    1699                z64.setCompressedSize(new ZipEightByteInteger(entry.getCompressedSize()));
    1700            }
    1701
    1702            if (hasRelativeHeaderOffset) {
    1703                entry.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
    1704            }
    1705
    1706            if (hasDiskStart) {
    1707                entry.setDiskNumberStart(z64.getDiskStartNumber().getValue());
    1708            }
    1709        }
    1710    }
    1711
    1712    /**
    1713     * Skips the given number of bytes or throws an EOFException if skipping failed.
    1714     */
    1715    private void skipBytes(final int count) throws IOException {
    1716        final long currentPosition = archive.position();
    1717        final long newPosition = currentPosition + count;
    1718        if (newPosition > archive.size()) {
    1719            throw new EOFException();
    1720        }
    1721        archive.position(newPosition);
    1722    }
    1723
    1724    /**
    1725     * Sorts entries in place by offset.
    1726     *
    1727     * @param allEntries entries to sort
    1728     * @return the given entries, sorted.
    1729     */
    1730    private ZipArchiveEntry[] sortByOffset(final ZipArchiveEntry[] allEntries) {
    1731        Arrays.sort(allEntries, offsetComparator);
    1732        return allEntries;
    1733    }
    1734
    1735    /**
    1736     * Checks whether the archive starts with an LFH. If it doesn't, it may be an empty archive.
    1737     */
    1738    private boolean startsWithLocalFileHeader() throws IOException {
    1739        archive.position(firstLocalFileHeaderOffset);
    1740        wordBbuf.rewind();
    1741        IOUtils.readFully(archive, wordBbuf);
    1742        return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
    1743    }
    1744
    1745    /**
    1746     * Returns an ordered {@code Stream} over the ZIP file entries.
    1747     * <p>
    1748     * Entries appear in the {@code Stream} in the order they appear in the central directory of the ZIP file.
    1749     * </p>
    1750     *
    1751     * @return an ordered {@code Stream} of entries in this ZIP file.
    1752     * @throws IllegalStateException if the ZIP file has been closed.
    1753     * @since 1.28.0
    1754     */
    1755    public IOStream<? extends ZipArchiveEntry> stream() {
    1756        return IOStream.adapt(entries.stream());
    1757    }
    1758
    1759}