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}