View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.zip;
20  
21  import java.io.BufferedInputStream;
22  import java.io.ByteArrayInputStream;
23  import java.io.Closeable;
24  import java.io.EOFException;
25  import java.io.File;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.SequenceInputStream;
29  import java.nio.ByteBuffer;
30  import java.nio.ByteOrder;
31  import java.nio.channels.FileChannel;
32  import java.nio.channels.SeekableByteChannel;
33  import java.nio.charset.Charset;
34  import java.nio.charset.StandardCharsets;
35  import java.nio.file.Files;
36  import java.nio.file.OpenOption;
37  import java.nio.file.Path;
38  import java.nio.file.StandardOpenOption;
39  import java.util.Arrays;
40  import java.util.Collections;
41  import java.util.Comparator;
42  import java.util.EnumSet;
43  import java.util.Enumeration;
44  import java.util.HashMap;
45  import java.util.LinkedList;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.Objects;
49  import java.util.stream.Collectors;
50  import java.util.stream.IntStream;
51  import java.util.zip.Inflater;
52  import java.util.zip.ZipException;
53  
54  import org.apache.commons.compress.archivers.EntryStreamOffsets;
55  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
56  import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
57  import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
58  import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
59  import org.apache.commons.compress.utils.BoundedArchiveInputStream;
60  import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
61  import org.apache.commons.compress.utils.IOUtils;
62  import org.apache.commons.compress.utils.InputStreamStatistics;
63  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
64  import org.apache.commons.io.Charsets;
65  import org.apache.commons.io.FilenameUtils;
66  import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
67  import org.apache.commons.io.build.AbstractStreamBuilder;
68  import org.apache.commons.io.function.IOFunction;
69  import org.apache.commons.io.function.IOStream;
70  import org.apache.commons.io.input.BoundedInputStream;
71  
72  /**
73   * Replacement for {@link java.util.zip.ZipFile}.
74   * <p>
75   * 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
76   * preamble like the one found in self extracting archives. Furthermore it returns instances of
77   * {@code org.apache.commons.compress.archivers.zip.ZipArchiveEntry} instead of {@link java.util.zip.ZipEntry}.
78   * </p>
79   * <p>
80   * 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
81   * SeekableByteChannel under the covers and supports compressed and uncompressed entries. As of Apache Commons Compress 1.3 it also transparently supports Zip64
82   * extensions and thus individual entries and archives larger than 4 GB or with more than 65,536 entries.
83   * </p>
84   * <p>
85   * The method signatures mimic the ones of {@link java.util.zip.ZipFile}, with a couple of exceptions:
86   * </p>
87   * <ul>
88   * <li>There is no getName method.</li>
89   * <li>entries has been renamed to getEntries.</li>
90   * <li>getEntries and getEntry return {@code org.apache.commons.compress.archivers.zip.ZipArchiveEntry} instances.</li>
91   * <li>close is allowed to throw IOException.</li>
92   * </ul>
93   */
94  public class ZipFile implements Closeable {
95  
96      /**
97       * Lock-free implementation of BoundedInputStream. The implementation uses positioned reads on the underlying archive file channel and therefore performs
98       * significantly faster in concurrent environment.
99       */
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 }