ZipFile.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.archivers.zip;

  18. import java.io.BufferedInputStream;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.Closeable;
  21. import java.io.EOFException;
  22. import java.io.File;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.SequenceInputStream;
  26. import java.nio.ByteBuffer;
  27. import java.nio.ByteOrder;
  28. import java.nio.channels.FileChannel;
  29. import java.nio.channels.SeekableByteChannel;
  30. import java.nio.charset.Charset;
  31. import java.nio.charset.StandardCharsets;
  32. import java.nio.file.Files;
  33. import java.nio.file.OpenOption;
  34. import java.nio.file.Path;
  35. import java.nio.file.StandardOpenOption;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.Collections;
  39. import java.util.Comparator;
  40. import java.util.EnumSet;
  41. import java.util.Enumeration;
  42. import java.util.HashMap;
  43. import java.util.LinkedList;
  44. import java.util.List;
  45. import java.util.Map;
  46. import java.util.Objects;
  47. import java.util.stream.Collectors;
  48. import java.util.stream.IntStream;
  49. import java.util.zip.Inflater;
  50. import java.util.zip.ZipException;

  51. import org.apache.commons.compress.archivers.EntryStreamOffsets;
  52. import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
  53. import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
  54. import org.apache.commons.compress.utils.BoundedArchiveInputStream;
  55. import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
  56. import org.apache.commons.compress.utils.IOUtils;
  57. import org.apache.commons.compress.utils.InputStreamStatistics;
  58. import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
  59. import org.apache.commons.io.Charsets;
  60. import org.apache.commons.io.FilenameUtils;
  61. import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
  62. import org.apache.commons.io.build.AbstractStreamBuilder;
  63. import org.apache.commons.io.input.BoundedInputStream;

  64. /**
  65.  * Replacement for {@link java.util.zip.ZipFile}.
  66.  * <p>
  67.  * 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
  68.  * preamble like the one found in self extracting archives. Furthermore it returns instances of
  69.  * {@code org.apache.commons.compress.archivers.zip.ZipArchiveEntry} instead of {@link java.util.zip.ZipEntry}.
  70.  * </p>
  71.  * <p>
  72.  * 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
  73.  * SeekableByteChannel under the covers and supports compressed and uncompressed entries. As of Apache Commons Compress 1.3 it also transparently supports Zip64
  74.  * extensions and thus individual entries and archives larger than 4 GB or with more than 65,536 entries.
  75.  * </p>
  76.  * <p>
  77.  * The method signatures mimic the ones of {@link java.util.zip.ZipFile}, with a couple of exceptions:
  78.  * </p>
  79.  * <ul>
  80.  * <li>There is no getName method.</li>
  81.  * <li>entries has been renamed to getEntries.</li>
  82.  * <li>getEntries and getEntry return {@code org.apache.commons.compress.archivers.zip.ZipArchiveEntry} instances.</li>
  83.  * <li>close is allowed to throw IOException.</li>
  84.  * </ul>
  85.  */
  86. public class ZipFile implements Closeable {

  87.     /**
  88.      * Lock-free implementation of BoundedInputStream. The implementation uses positioned reads on the underlying archive file channel and therefore performs
  89.      * significantly faster in concurrent environment.
  90.      */
  91.     private static class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
  92.         private final FileChannel archive;

  93.         BoundedFileChannelInputStream(final long start, final long remaining, final FileChannel archive) {
  94.             super(start, remaining);
  95.             this.archive = archive;
  96.         }

  97.         @Override
  98.         protected int read(final long pos, final ByteBuffer buf) throws IOException {
  99.             final int read = archive.read(buf, pos);
  100.             buf.flip();
  101.             return read;
  102.         }
  103.     }

  104.     /**
  105.      * Builds new {@link ZipFile} instances.
  106.      * <p>
  107.      * The channel will be opened for reading, assuming the specified encoding for file names.
  108.      * </p>
  109.      * <p>
  110.      * See {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} to read from an in-memory archive.
  111.      * </p>
  112.      * <p>
  113.      * 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
  114.      * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
  115.      * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
  116.      * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
  117.      * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
  118.      * </p>
  119.      *
  120.      * @since 1.26.0
  121.      */
  122.     public static class Builder extends AbstractStreamBuilder<ZipFile, Builder> {

  123.         static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

  124.         private SeekableByteChannel seekableByteChannel;
  125.         private boolean useUnicodeExtraFields = true;
  126.         private boolean ignoreLocalFileHeader;
  127.         private long maxNumberOfDisks = 1;

  128.         public Builder() {
  129.             setCharset(DEFAULT_CHARSET);
  130.             setCharsetDefault(DEFAULT_CHARSET);
  131.         }

  132.         @Override
  133.         public ZipFile get() throws IOException {
  134.             final SeekableByteChannel actualChannel;
  135.             final String actualDescription;
  136.             if (seekableByteChannel != null) {
  137.                 actualChannel = seekableByteChannel;
  138.                 actualDescription = actualChannel.getClass().getSimpleName();
  139.             } else if (checkOrigin() instanceof ByteArrayOrigin) {
  140.                 actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
  141.                 actualDescription = actualChannel.getClass().getSimpleName();
  142.             } else {
  143.                 OpenOption[] openOptions = getOpenOptions();
  144.                 if (openOptions.length == 0) {
  145.                     openOptions = new OpenOption[] { StandardOpenOption.READ };
  146.                 }
  147.                 final Path path = getPath();
  148.                 actualChannel = openZipChannel(path, maxNumberOfDisks, openOptions);
  149.                 actualDescription = path.toString();
  150.             }
  151.             final boolean closeOnError = seekableByteChannel != null;
  152.             return new ZipFile(actualChannel, actualDescription, getCharset(), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader);
  153.         }

  154.         /**
  155.          * Sets whether to ignore information stored inside the local file header.
  156.          *
  157.          * @param ignoreLocalFileHeader whether to ignore information stored inside.
  158.          * @return {@code this} instance.
  159.          */
  160.         public Builder setIgnoreLocalFileHeader(final boolean ignoreLocalFileHeader) {
  161.             this.ignoreLocalFileHeader = ignoreLocalFileHeader;
  162.             return this;
  163.         }

  164.         /**
  165.          * Sets max number of multi archive disks, default is 1 (no multi archive).
  166.          *
  167.          * @param maxNumberOfDisks max number of multi archive disks.
  168.          *
  169.          * @return {@code this} instance.
  170.          */
  171.         public Builder setMaxNumberOfDisks(final long maxNumberOfDisks) {
  172.             this.maxNumberOfDisks = maxNumberOfDisks;
  173.             return this;
  174.         }

  175.         /**
  176.          * The actual channel, overrides any other input aspects like a File, Path, and so on.
  177.          *
  178.          * @param seekableByteChannel The actual channel.
  179.          * @return {@code this} instance.
  180.          */
  181.         public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
  182.             this.seekableByteChannel = seekableByteChannel;
  183.             return this;
  184.         }

  185.         /**
  186.          * Sets whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  187.          *
  188.          * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  189.          * @return {@code this} instance.
  190.          */
  191.         public Builder setUseUnicodeExtraFields(final boolean useUnicodeExtraFields) {
  192.             this.useUnicodeExtraFields = useUnicodeExtraFields;
  193.             return this;
  194.         }

  195.     }

  196.     /**
  197.      * Extends ZipArchiveEntry to store the offset within the archive.
  198.      */
  199.     private static final class Entry extends ZipArchiveEntry {

  200.         @Override
  201.         public boolean equals(final Object other) {
  202.             if (super.equals(other)) {
  203.                 // super.equals would return false if other were not an Entry
  204.                 final Entry otherEntry = (Entry) other;
  205.                 return getLocalHeaderOffset() == otherEntry.getLocalHeaderOffset() && super.getDataOffset() == otherEntry.getDataOffset()
  206.                         && super.getDiskNumberStart() == otherEntry.getDiskNumberStart();
  207.             }
  208.             return false;
  209.         }

  210.         @Override
  211.         public int hashCode() {
  212.             return 3 * super.hashCode() + (int) getLocalHeaderOffset() + (int) (getLocalHeaderOffset() >> 32);
  213.         }
  214.     }

  215.     private static final class NameAndComment {
  216.         private final byte[] name;
  217.         private final byte[] comment;

  218.         private NameAndComment(final byte[] name, final byte[] comment) {
  219.             this.name = name;
  220.             this.comment = comment;
  221.         }
  222.     }

  223.     private static final class StoredStatisticsStream extends BoundedInputStream implements InputStreamStatistics {
  224.         StoredStatisticsStream(final InputStream in) {
  225.             super(in);
  226.         }

  227.         @Override
  228.         public long getCompressedCount() {
  229.             return super.getCount();
  230.         }

  231.         @Override
  232.         public long getUncompressedCount() {
  233.             return getCompressedCount();
  234.         }
  235.     }

  236.     private static final String DEFAULT_CHARSET_NAME = StandardCharsets.UTF_8.name();

  237.     private static final EnumSet<StandardOpenOption> READ = EnumSet.of(StandardOpenOption.READ);

  238.     private static final int HASH_SIZE = 509;
  239.     static final int NIBLET_MASK = 0x0f;
  240.     static final int BYTE_SHIFT = 8;
  241.     private static final int POS_0 = 0;
  242.     private static final int POS_1 = 1;
  243.     private static final int POS_2 = 2;
  244.     private static final int POS_3 = 3;
  245.     private static final byte[] ONE_ZERO_BYTE = new byte[1];

  246.     /**
  247.      * Length of a "central directory" entry structure without file name, extra fields or comment.
  248.      */
  249.     private static final int CFH_LEN =
  250.     // @formatter:off
  251.         /* version made by                 */ ZipConstants.SHORT
  252.         /* version needed to extract       */ + ZipConstants.SHORT
  253.         /* general purpose bit flag        */ + ZipConstants.SHORT
  254.         /* compression method              */ + ZipConstants.SHORT
  255.         /* last mod file time              */ + ZipConstants.SHORT
  256.         /* last mod file date              */ + ZipConstants.SHORT
  257.         /* crc-32                          */ + ZipConstants.WORD
  258.         /* compressed size                 */ + ZipConstants.WORD
  259.         /* uncompressed size               */ + ZipConstants.WORD
  260.         /* file name length                */ + ZipConstants. SHORT
  261.         /* extra field length              */ + ZipConstants.SHORT
  262.         /* file comment length             */ + ZipConstants.SHORT
  263.         /* disk number start               */ + ZipConstants.SHORT
  264.         /* internal file attributes        */ + ZipConstants.SHORT
  265.         /* external file attributes        */ + ZipConstants.WORD
  266.         /* relative offset of local header */ + ZipConstants.WORD;
  267.     // @formatter:on

  268.     private static final long CFH_SIG = ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);

  269.     /**
  270.      * Length of the "End of central directory record" - which is supposed to be the last structure of the archive - without file comment.
  271.      */
  272.     static final int MIN_EOCD_SIZE =
  273.     // @formatter:off
  274.         /* end of central dir signature    */ ZipConstants.WORD
  275.         /* number of this disk             */ + ZipConstants.SHORT
  276.         /* number of the disk with the     */
  277.         /* start of the central directory  */ + ZipConstants.SHORT
  278.         /* total number of entries in      */
  279.         /* the central dir on this disk    */ + ZipConstants.SHORT
  280.         /* total number of entries in      */
  281.         /* the central dir                 */ + ZipConstants.SHORT
  282.         /* size of the central directory   */ + ZipConstants.WORD
  283.         /* offset of start of central      */
  284.         /* directory with respect to       */
  285.         /* the starting disk number        */ + ZipConstants.WORD
  286.         /* ZIP file comment length         */ + ZipConstants.SHORT;
  287.     // @formatter:on

  288.     /**
  289.      * Maximum length of the "End of central directory record" with a file comment.
  290.      */
  291.     private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
  292.     // @formatter:off
  293.         /* maximum length of ZIP file comment */ + ZipConstants.ZIP64_MAGIC_SHORT;
  294.     // @formatter:on

  295.     /**
  296.      * 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
  297.      * the "End of central directory record".
  298.      */
  299.     private static final int CFD_LENGTH_OFFSET =
  300.     // @formatter:off
  301.         /* end of central dir signature    */ ZipConstants.WORD
  302.         /* number of this disk             */ + ZipConstants.SHORT
  303.         /* number of the disk with the     */
  304.         /* start of the central directory  */ + ZipConstants.SHORT
  305.         /* total number of entries in      */
  306.         /* the central dir on this disk    */ + ZipConstants.SHORT
  307.         /* total number of entries in      */
  308.         /* the central dir                 */ + ZipConstants.SHORT;
  309.     // @formatter:on

  310.     /**
  311.      * 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
  312.      * the "End of central directory record".
  313.      */
  314.     private static final int CFD_DISK_OFFSET =
  315.     // @formatter:off
  316.             /* end of central dir signature    */ ZipConstants.WORD
  317.             /* number of this disk             */ + ZipConstants.SHORT;
  318.     // @formatter:on

  319.     /**
  320.      * 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
  321.      * the disk with the start of the central directory".
  322.      */
  323.     private static final int CFD_LOCATOR_RELATIVE_OFFSET =
  324.     // @formatter:off
  325.             /* total number of entries in      */
  326.             /* the central dir on this disk    */ + ZipConstants.SHORT
  327.             /* total number of entries in      */
  328.             /* the central dir                 */ + ZipConstants.SHORT
  329.             /* size of the central directory   */ + ZipConstants.WORD;
  330.     // @formatter:on

  331.     /**
  332.      * 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
  333.      * all.
  334.      */
  335.     private static final int ZIP64_EOCDL_LENGTH =
  336.     // @formatter:off
  337.         /* zip64 end of central dir locator sig */ ZipConstants.WORD
  338.         /* number of the disk with the start    */
  339.         /* start of the zip64 end of            */
  340.         /* central directory                    */ + ZipConstants.WORD
  341.         /* relative offset of the zip64         */
  342.         /* end of central directory record      */ + ZipConstants.DWORD
  343.         /* total number of disks                */ + ZipConstants.WORD;
  344.     // @formatter:on

  345.     /**
  346.      * 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
  347.      * to the start of the "Zip64 end of central directory locator".
  348.      */
  349.     private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
  350.     // @formatter:off
  351.         /* zip64 end of central dir locator sig */ ZipConstants.WORD
  352.         /* number of the disk with the start    */
  353.         /* start of the zip64 end of            */
  354.         /* central directory                    */ + ZipConstants.WORD;
  355.     // @formatter:on

  356.     /**
  357.      * 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
  358.      * of the "Zip64 end of central directory record".
  359.      */
  360.     private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
  361.     // @formatter:off
  362.         /* zip64 end of central dir        */
  363.         /* signature                       */ ZipConstants.WORD
  364.         /* size of zip64 end of central    */
  365.         /* directory record                */ + ZipConstants.DWORD
  366.         /* version made by                 */ + ZipConstants.SHORT
  367.         /* version needed to extract       */ + ZipConstants.SHORT
  368.         /* number of this disk             */ + ZipConstants.WORD
  369.         /* number of the disk with the     */
  370.         /* start of the central directory  */ + ZipConstants.WORD
  371.         /* total number of entries in the  */
  372.         /* central directory on this disk  */ + ZipConstants.DWORD
  373.         /* total number of entries in the  */
  374.         /* central directory               */ + ZipConstants.DWORD
  375.         /* size of the central directory   */ + ZipConstants.DWORD;
  376.     // @formatter:on

  377.     /**
  378.      * 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
  379.      * start of the "Zip64 end of central directory record".
  380.      */
  381.     private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
  382.     // @formatter:off
  383.             /* zip64 end of central dir        */
  384.             /* signature                       */ ZipConstants.WORD
  385.             /* size of zip64 end of central    */
  386.             /* directory record                */ + ZipConstants.DWORD
  387.             /* version made by                 */ + ZipConstants.SHORT
  388.             /* version needed to extract       */ + ZipConstants.SHORT
  389.             /* number of this disk             */ + ZipConstants.WORD;
  390.     // @formatter:on

  391.     /**
  392.      * 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
  393.      * "number of the disk with the start of the central directory".
  394.      */
  395.     private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
  396.     // @formatter:off
  397.             /* total number of entries in the  */
  398.             /* central directory on this disk  */ ZipConstants.DWORD
  399.             /* total number of entries in the  */
  400.             /* central directory               */ + ZipConstants.DWORD
  401.             /* size of the central directory   */ + ZipConstants.DWORD;
  402.     // @formatter:on

  403.     /**
  404.      * Number of bytes in local file header up to the &quot;length of file name&quot; entry.
  405.      */
  406.     private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
  407.     // @formatter:off
  408.         /* local file header signature     */ ZipConstants.WORD
  409.         /* version needed to extract       */ + ZipConstants.SHORT
  410.         /* general purpose bit flag        */ + ZipConstants.SHORT
  411.         /* compression method              */ + ZipConstants.SHORT
  412.         /* last mod file time              */ + ZipConstants.SHORT
  413.         /* last mod file date              */ + ZipConstants.SHORT
  414.         /* crc-32                          */ + ZipConstants.WORD
  415.         /* compressed size                 */ + ZipConstants.WORD
  416.         /* uncompressed size               */ + (long) ZipConstants.WORD;
  417.     // @formatter:on

  418.     /**
  419.      * Compares two ZipArchiveEntries based on their offset within the archive.
  420.      * <p>
  421.      * Won't return any meaningful results if one of the entries isn't part of the archive at all.
  422.      * </p>
  423.      *
  424.      * @since 1.1
  425.      */
  426.     private static final Comparator<ZipArchiveEntry> offsetComparator = Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
  427.             .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);

  428.     /**
  429.      * Creates a new Builder.
  430.      *
  431.      * @return a new Builder.
  432.      * @since 1.26.0
  433.      */
  434.     public static Builder builder() {
  435.         return new Builder();
  436.     }

  437.     /**
  438.      * Closes a ZIP file quietly; throwing no IOException, does nothing on null input.
  439.      *
  440.      * @param zipFile file to close, can be null
  441.      */
  442.     public static void closeQuietly(final ZipFile zipFile) {
  443.         org.apache.commons.io.IOUtils.closeQuietly(zipFile);
  444.     }

  445.     /**
  446.      * Creates a new SeekableByteChannel for reading.
  447.      *
  448.      * @param path the path to the file to open or create
  449.      * @return a new seekable byte channel
  450.      * @throws IOException if an I/O error occurs
  451.      */
  452.     private static SeekableByteChannel newReadByteChannel(final Path path) throws IOException {
  453.         return Files.newByteChannel(path, READ);
  454.     }

  455.     private static SeekableByteChannel openZipChannel(final Path path, final long maxNumberOfDisks, final OpenOption[] openOptions) throws IOException {
  456.         final FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
  457.         final List<FileChannel> channels = new ArrayList<>();
  458.         try {
  459.             final boolean is64 = positionAtEndOfCentralDirectoryRecord(channel);
  460.             long numberOfDisks;
  461.             if (is64) {
  462.                 channel.position(channel.position() + ZipConstants.WORD + ZipConstants.WORD + ZipConstants.DWORD);
  463.                 final ByteBuffer buf = ByteBuffer.allocate(ZipConstants.WORD);
  464.                 buf.order(ByteOrder.LITTLE_ENDIAN);
  465.                 IOUtils.readFully(channel, buf);
  466.                 buf.flip();
  467.                 numberOfDisks = buf.getInt() & 0xffffffffL;
  468.             } else {
  469.                 channel.position(channel.position() + ZipConstants.WORD);
  470.                 final ByteBuffer buf = ByteBuffer.allocate(ZipConstants.SHORT);
  471.                 buf.order(ByteOrder.LITTLE_ENDIAN);
  472.                 IOUtils.readFully(channel, buf);
  473.                 buf.flip();
  474.                 numberOfDisks = (buf.getShort() & 0xffff) + 1;
  475.             }
  476.             if (numberOfDisks > Math.min(maxNumberOfDisks, Integer.MAX_VALUE)) {
  477.                 throw new IOException("Too many disks for zip archive, max=" + Math.min(maxNumberOfDisks, Integer.MAX_VALUE) + " actual=" + numberOfDisks);
  478.             }

  479.             if (numberOfDisks <= 1) {
  480.                 return channel;
  481.             }
  482.             channel.close();

  483.             final Path parent = path.getParent();
  484.             final String basename = FilenameUtils.removeExtension(Objects.toString(path.getFileName(), null));

  485.             return ZipSplitReadOnlySeekableByteChannel.forPaths(IntStream.range(0, (int) numberOfDisks).mapToObj(i -> {
  486.                 if (i == numberOfDisks - 1) {
  487.                     return path;
  488.                 }
  489.                 final Path lowercase = parent.resolve(String.format("%s.z%02d", basename, i + 1));
  490.                 if (Files.exists(lowercase)) {
  491.                     return lowercase;
  492.                 }
  493.                 final Path uppercase = parent.resolve(String.format("%s.Z%02d", basename, i + 1));
  494.                 if (Files.exists(uppercase)) {
  495.                     return uppercase;
  496.                 }
  497.                 return lowercase;
  498.             }).collect(Collectors.toList()), openOptions);
  499.         } catch (final Throwable ex) {
  500.             org.apache.commons.io.IOUtils.closeQuietly(channel);
  501.             channels.forEach(org.apache.commons.io.IOUtils::closeQuietly);
  502.             throw ex;
  503.         }
  504.     }

  505.     /**
  506.      * Searches for the and positions the stream at the start of the &quot;End of central dir record&quot;.
  507.      *
  508.      * @return true if it's Zip64 end of central directory or false if it's Zip32
  509.      */
  510.     private static boolean positionAtEndOfCentralDirectoryRecord(final SeekableByteChannel channel) throws IOException {
  511.         final boolean found = tryToLocateSignature(channel, MIN_EOCD_SIZE, MAX_EOCD_SIZE, ZipArchiveOutputStream.EOCD_SIG);
  512.         if (!found) {
  513.             throw new ZipException("Archive is not a ZIP archive");
  514.         }
  515.         boolean found64 = false;
  516.         final long position = channel.position();
  517.         if (position > ZIP64_EOCDL_LENGTH) {
  518.             final ByteBuffer wordBuf = ByteBuffer.allocate(4);
  519.             channel.position(channel.position() - ZIP64_EOCDL_LENGTH);
  520.             wordBuf.rewind();
  521.             IOUtils.readFully(channel, wordBuf);
  522.             wordBuf.flip();
  523.             found64 = wordBuf.equals(ByteBuffer.wrap(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG));
  524.             if (!found64) {
  525.                 channel.position(position);
  526.             } else {
  527.                 channel.position(channel.position() - ZipConstants.WORD);
  528.             }
  529.         }

  530.         return found64;
  531.     }

  532.     /**
  533.      * Searches the archive backwards from minDistance to maxDistance for the given signature, positions the RandomaccessFile right at the signature if it has
  534.      * been found.
  535.      */
  536.     private static boolean tryToLocateSignature(final SeekableByteChannel channel, final long minDistanceFromEnd, final long maxDistanceFromEnd,
  537.             final byte[] sig) throws IOException {
  538.         final ByteBuffer wordBuf = ByteBuffer.allocate(ZipConstants.WORD);
  539.         boolean found = false;
  540.         long off = channel.size() - minDistanceFromEnd;
  541.         final long stopSearching = Math.max(0L, channel.size() - maxDistanceFromEnd);
  542.         if (off >= 0) {
  543.             for (; off >= stopSearching; off--) {
  544.                 channel.position(off);
  545.                 try {
  546.                     wordBuf.rewind();
  547.                     IOUtils.readFully(channel, wordBuf);
  548.                     wordBuf.flip();
  549.                 } catch (final EOFException ex) { // NOSONAR
  550.                     break;
  551.                 }
  552.                 int curr = wordBuf.get();
  553.                 if (curr == sig[POS_0]) {
  554.                     curr = wordBuf.get();
  555.                     if (curr == sig[POS_1]) {
  556.                         curr = wordBuf.get();
  557.                         if (curr == sig[POS_2]) {
  558.                             curr = wordBuf.get();
  559.                             if (curr == sig[POS_3]) {
  560.                                 found = true;
  561.                                 break;
  562.                             }
  563.                         }
  564.                     }
  565.                 }
  566.             }
  567.         }
  568.         if (found) {
  569.             channel.position(off);
  570.         }
  571.         return found;
  572.     }

  573.     /**
  574.      * List of entries in the order they appear inside the central directory.
  575.      */
  576.     private final List<ZipArchiveEntry> entries = new LinkedList<>();

  577.     /**
  578.      * Maps String to list of ZipArchiveEntrys, name -> actual entries.
  579.      */
  580.     private final Map<String, LinkedList<ZipArchiveEntry>> nameMap = new HashMap<>(HASH_SIZE);

  581.     /**
  582.      * The encoding to use for file names and the file comment.
  583.      * <p>
  584.      * 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>.
  585.      * Defaults to UTF-8.
  586.      * </p>
  587.      */
  588.     private final Charset encoding;

  589.     /**
  590.      * The ZIP encoding to use for file names and the file comment.
  591.      */
  592.     private final ZipEncoding zipEncoding;

  593.     /**
  594.      * The actual data source.
  595.      */
  596.     private final SeekableByteChannel archive;

  597.     /**
  598.      * Whether to look for and use Unicode extra fields.
  599.      */
  600.     private final boolean useUnicodeExtraFields;

  601.     /**
  602.      * Whether the file is closed.
  603.      */
  604.     private volatile boolean closed = true;

  605.     /**
  606.      * Whether the ZIP archive is a split ZIP archive
  607.      */
  608.     private final boolean isSplitZipArchive;

  609.     // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
  610.     private final byte[] dwordBuf = new byte[ZipConstants.DWORD];

  611.     private final byte[] wordBuf = new byte[ZipConstants.WORD];

  612.     private final byte[] cfhBuf = new byte[CFH_LEN];

  613.     private final byte[] shortBuf = new byte[ZipConstants.SHORT];

  614.     private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);

  615.     private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);

  616.     private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);

  617.     private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);

  618.     private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset;

  619.     private long centralDirectoryStartOffset;

  620.     private long firstLocalFileHeaderOffset;

  621.     /**
  622.      * Opens the given file for reading, assuming "UTF8" for file names.
  623.      *
  624.      * @param file the archive.
  625.      *
  626.      * @throws IOException if an error occurs while reading the file.
  627.      * @deprecated Use {@link Builder#get()}.
  628.      */
  629.     @Deprecated
  630.     public ZipFile(final File file) throws IOException {
  631.         this(file, DEFAULT_CHARSET_NAME);
  632.     }

  633.     /**
  634.      * Opens the given file for reading, assuming the specified encoding for file names and scanning for Unicode extra fields.
  635.      *
  636.      * @param file     the archive.
  637.      * @param encoding the encoding to use for file names, use null for the platform's default encoding
  638.      * @throws IOException if an error occurs while reading the file.
  639.      * @deprecated Use {@link Builder#get()}.
  640.      */
  641.     @Deprecated
  642.     public ZipFile(final File file, final String encoding) throws IOException {
  643.         this(file.toPath(), encoding, true);
  644.     }

  645.     /**
  646.      * Opens the given file for reading, assuming the specified encoding for file names.
  647.      *
  648.      * @param file                  the archive.
  649.      * @param encoding              the encoding to use for file names, use null for the platform's default encoding
  650.      * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  651.      * @throws IOException if an error occurs while reading the file.
  652.      * @deprecated Use {@link Builder#get()}.
  653.      */
  654.     @Deprecated
  655.     public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
  656.         this(file.toPath(), encoding, useUnicodeExtraFields, false);
  657.     }

  658.     /**
  659.      * Opens the given file for reading, assuming the specified encoding for file names.
  660.      * <p>
  661.      * 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
  662.      * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
  663.      * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
  664.      * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
  665.      * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
  666.      * </p>
  667.      *
  668.      * @param file                  the archive.
  669.      * @param encoding              the encoding to use for file names, use null for the platform's default encoding
  670.      * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  671.      * @param ignoreLocalFileHeader whether to ignore information stored inside the local file header (see the notes in this method's Javadoc)
  672.      * @throws IOException if an error occurs while reading the file.
  673.      * @since 1.19
  674.      * @deprecated Use {@link Builder#get()}.
  675.      */
  676.     @Deprecated
  677.     @SuppressWarnings("resource") // Caller closes
  678.     public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
  679.         this(newReadByteChannel(file.toPath()), file.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
  680.     }

  681.     /**
  682.      * Opens the given path for reading, assuming "UTF-8" for file names.
  683.      *
  684.      * @param path path to the archive.
  685.      * @throws IOException if an error occurs while reading the file.
  686.      * @since 1.22
  687.      * @deprecated Use {@link Builder#get()}.
  688.      */
  689.     @Deprecated
  690.     public ZipFile(final Path path) throws IOException {
  691.         this(path, DEFAULT_CHARSET_NAME);
  692.     }

  693.     /**
  694.      * Opens the given path for reading, assuming the specified encoding for file names and scanning for Unicode extra fields.
  695.      *
  696.      * @param path     path to the archive.
  697.      * @param encoding the encoding to use for file names, use null for the platform's default encoding
  698.      * @throws IOException if an error occurs while reading the file.
  699.      * @since 1.22
  700.      * @deprecated Use {@link Builder#get()}.
  701.      */
  702.     @Deprecated
  703.     public ZipFile(final Path path, final String encoding) throws IOException {
  704.         this(path, encoding, true);
  705.     }

  706.     /**
  707.      * Opens the given path for reading, assuming the specified encoding for file names.
  708.      *
  709.      * @param path                  path to the archive.
  710.      * @param encoding              the encoding to use for file names, use null for the platform's default encoding
  711.      * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  712.      * @throws IOException if an error occurs while reading the file.
  713.      * @since 1.22
  714.      * @deprecated Use {@link Builder#get()}.
  715.      */
  716.     @Deprecated
  717.     public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
  718.         this(path, encoding, useUnicodeExtraFields, false);
  719.     }

  720.     /**
  721.      * Opens the given path for reading, assuming the specified encoding for file names.
  722.      * <p>
  723.      * 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
  724.      * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
  725.      * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
  726.      * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
  727.      * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
  728.      * </p>
  729.      *
  730.      * @param path                  path to the archive.
  731.      * @param encoding              the encoding to use for file names, use null for the platform's default encoding
  732.      * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  733.      * @param ignoreLocalFileHeader whether to ignore information stored inside the local file header (see the notes in this method's Javadoc)
  734.      * @throws IOException if an error occurs while reading the file.
  735.      * @since 1.22
  736.      * @deprecated Use {@link Builder#get()}.
  737.      */
  738.     @SuppressWarnings("resource") // Caller closes
  739.     @Deprecated
  740.     public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
  741.         this(newReadByteChannel(path), path.toAbsolutePath().toString(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
  742.     }

  743.     /**
  744.      * Opens the given channel for reading, assuming "UTF-8" for file names.
  745.      * <p>
  746.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  747.      * </p>
  748.      *
  749.      * @param channel the archive.
  750.      *
  751.      * @throws IOException if an error occurs while reading the file.
  752.      * @since 1.13
  753.      * @deprecated Use {@link Builder#get()}.
  754.      */
  755.     @Deprecated
  756.     public ZipFile(final SeekableByteChannel channel) throws IOException {
  757.         this(channel, "a SeekableByteChannel", DEFAULT_CHARSET_NAME, true);
  758.     }

  759.     /**
  760.      * Opens the given channel for reading, assuming the specified encoding for file names.
  761.      * <p>
  762.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  763.      * </p>
  764.      *
  765.      * @param channel  the archive.
  766.      * @param encoding the encoding to use for file names, use null for the platform's default encoding
  767.      * @throws IOException if an error occurs while reading the file.
  768.      * @since 1.13
  769.      * @deprecated Use {@link Builder#get()}.
  770.      */
  771.     @Deprecated
  772.     public ZipFile(final SeekableByteChannel channel, final String encoding) throws IOException {
  773.         this(channel, "a SeekableByteChannel", encoding, true);
  774.     }

  775.     private ZipFile(final SeekableByteChannel channel, final String channelDescription, final Charset encoding, final boolean useUnicodeExtraFields,
  776.             final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException {
  777.         this.isSplitZipArchive = channel instanceof ZipSplitReadOnlySeekableByteChannel;
  778.         this.encoding = Charsets.toCharset(encoding, Builder.DEFAULT_CHARSET);
  779.         this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
  780.         this.useUnicodeExtraFields = useUnicodeExtraFields;
  781.         this.archive = channel;
  782.         boolean success = false;
  783.         try {
  784.             final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag = populateFromCentralDirectory();
  785.             if (!ignoreLocalFileHeader) {
  786.                 resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
  787.             }
  788.             fillNameMap();
  789.             success = true;
  790.         } catch (final IOException e) {
  791.             throw new IOException("Error reading Zip content from " + channelDescription, e);
  792.         } finally {
  793.             this.closed = !success;
  794.             if (!success && closeOnError) {
  795.                 org.apache.commons.io.IOUtils.closeQuietly(archive);
  796.             }
  797.         }
  798.     }

  799.     /**
  800.      * Opens the given channel for reading, assuming the specified encoding for file names.
  801.      * <p>
  802.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  803.      * </p>
  804.      *
  805.      * @param channel               the archive.
  806.      * @param channelDescription    description of the archive, used for error messages only.
  807.      * @param encoding              the encoding to use for file names, use null for the platform's default encoding
  808.      * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  809.      * @throws IOException if an error occurs while reading the file.
  810.      * @since 1.13
  811.      * @deprecated Use {@link Builder#get()}.
  812.      */
  813.     @Deprecated
  814.     public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields)
  815.             throws IOException {
  816.         this(channel, channelDescription, encoding, useUnicodeExtraFields, false, false);
  817.     }

  818.     /**
  819.      * Opens the given channel for reading, assuming the specified encoding for file names.
  820.      * <p>
  821.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  822.      * </p>
  823.      * <p>
  824.      * 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
  825.      * when the archive is big. The {@code ignoreLocalFileHeader} parameter can be set to {@code true} which restricts parsing to the central directory.
  826.      * Unfortunately the local file header may contain information not present inside of the central directory which will not be available when the argument is
  827.      * set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
  828.      * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.
  829.      * </p>
  830.      *
  831.      * @param channel               the archive.
  832.      * @param channelDescription    description of the archive, used for error messages only.
  833.      * @param encoding              the encoding to use for file names, use null for the platform's default encoding
  834.      * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
  835.      * @param ignoreLocalFileHeader whether to ignore information stored inside the local file header (see the notes in this method's Javadoc)
  836.      * @throws IOException if an error occurs while reading the file.
  837.      * @since 1.19
  838.      * @deprecated Use {@link Builder#get()}.
  839.      */
  840.     @Deprecated
  841.     public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
  842.             final boolean ignoreLocalFileHeader) throws IOException {
  843.         this(channel, channelDescription, encoding, useUnicodeExtraFields, false, ignoreLocalFileHeader);
  844.     }

  845.     private ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
  846.             final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException {
  847.         this(channel, channelDescription, Charsets.toCharset(encoding), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader);
  848.     }

  849.     /**
  850.      * Opens the given file for reading, assuming "UTF-8".
  851.      *
  852.      * @param name name of the archive.
  853.      * @throws IOException if an error occurs while reading the file.
  854.      * @deprecated Use {@link Builder#get()}.
  855.      */
  856.     @Deprecated
  857.     public ZipFile(final String name) throws IOException {
  858.         this(new File(name).toPath(), DEFAULT_CHARSET_NAME);
  859.     }

  860.     /**
  861.      * Opens the given file for reading, assuming the specified encoding for file names, scanning unicode extra fields.
  862.      *
  863.      * @param name     name of the archive.
  864.      * @param encoding the encoding to use for file names, use null for the platform's default encoding
  865.      * @throws IOException if an error occurs while reading the file.
  866.      * @deprecated Use {@link Builder#get()}.
  867.      */
  868.     @Deprecated
  869.     public ZipFile(final String name, final String encoding) throws IOException {
  870.         this(new File(name).toPath(), encoding, true);
  871.     }

  872.     /**
  873.      * Whether this class is able to read the given entry.
  874.      * <p>
  875.      * May return false if it is set up to use encryption or a compression method that hasn't been implemented yet.
  876.      * </p>
  877.      *
  878.      * @since 1.1
  879.      * @param entry the entry
  880.      * @return whether this class is able to read the given entry.
  881.      */
  882.     public boolean canReadEntryData(final ZipArchiveEntry entry) {
  883.         return ZipUtil.canHandleEntryData(entry);
  884.     }

  885.     /**
  886.      * Closes the archive.
  887.      *
  888.      * @throws IOException if an error occurs closing the archive.
  889.      */
  890.     @Override
  891.     public void close() throws IOException {
  892.         // this flag is only written here and read in finalize() which
  893.         // can never be run in parallel.
  894.         // no synchronization needed.
  895.         closed = true;
  896.         archive.close();
  897.     }

  898.     /**
  899.      * Transfer selected entries from this ZIP file to a given #ZipArchiveOutputStream. Compression and all other attributes will be as in this file.
  900.      * <p>
  901.      * This method transfers entries based on the central directory of the ZIP file.
  902.      * </p>
  903.      *
  904.      * @param target    The zipArchiveOutputStream to write the entries to
  905.      * @param predicate A predicate that selects which entries to write
  906.      * @throws IOException on error
  907.      */
  908.     public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate) throws IOException {
  909.         final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
  910.         while (src.hasMoreElements()) {
  911.             final ZipArchiveEntry entry = src.nextElement();
  912.             if (predicate.test(entry)) {
  913.                 target.addRawArchiveEntry(entry, getRawInputStream(entry));
  914.             }
  915.         }
  916.     }

  917.     /**
  918.      * Creates new BoundedInputStream, according to implementation of underlying archive channel.
  919.      */
  920.     private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
  921.         if (start < 0 || remaining < 0 || start + remaining < start) {
  922.             throw new IllegalArgumentException("Corrupted archive, stream boundaries" + " are out of range");
  923.         }
  924.         return archive instanceof FileChannel ? new BoundedFileChannelInputStream(start, remaining, (FileChannel) archive)
  925.                 : new BoundedSeekableByteChannelInputStream(start, remaining, archive);
  926.     }

  927.     private void fillNameMap() {
  928.         entries.forEach(ze -> {
  929.             // entries are filled in populateFromCentralDirectory and
  930.             // never modified
  931.             final String name = ze.getName();
  932.             final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
  933.             entriesOfThatName.addLast(ze);
  934.         });
  935.     }

  936.     /**
  937.      * Ensures that the close method of this ZIP file is called when there are no more references to it.
  938.      *
  939.      * @see #close()
  940.      */
  941.     @Override
  942.     protected void finalize() throws Throwable {
  943.         try {
  944.             if (!closed) {
  945.                 close();
  946.             }
  947.         } finally {
  948.             super.finalize();
  949.         }
  950.     }

  951.     /**
  952.      * Gets an InputStream for reading the content before the first local file header.
  953.      *
  954.      * @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
  955.      *         header.
  956.      * @since 1.23
  957.      */
  958.     public InputStream getContentBeforeFirstLocalFileHeader() {
  959.         return firstLocalFileHeaderOffset == 0 ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
  960.     }

  961.     private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
  962.         final long s = ze.getDataOffset();
  963.         if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
  964.             setDataOffset(ze);
  965.             return ze.getDataOffset();
  966.         }
  967.         return s;
  968.     }

  969.     /**
  970.      * Gets the encoding to use for file names and the file comment.
  971.      *
  972.      * @return null if using the platform's default character encoding.
  973.      */
  974.     public String getEncoding() {
  975.         return encoding.name();
  976.     }

  977.     /**
  978.      * Gets all entries.
  979.      * <p>
  980.      * Entries will be returned in the same order they appear within the archive's central directory.
  981.      * </p>
  982.      *
  983.      * @return all entries as {@link ZipArchiveEntry} instances
  984.      */
  985.     public Enumeration<ZipArchiveEntry> getEntries() {
  986.         return Collections.enumeration(entries);
  987.     }

  988.     /**
  989.      * Gets all named entries in the same order they appear within the archive's central directory.
  990.      *
  991.      * @param name name of the entry.
  992.      * @return the Iterable&lt;ZipArchiveEntry&gt; corresponding to the given name
  993.      * @since 1.6
  994.      */
  995.     public Iterable<ZipArchiveEntry> getEntries(final String name) {
  996.         return nameMap.getOrDefault(name, ZipArchiveEntry.EMPTY_LINKED_LIST);
  997.     }

  998.     /**
  999.      * Gets all entries in physical order.
  1000.      * <p>
  1001.      * Entries will be returned in the same order their contents appear within the archive.
  1002.      * </p>
  1003.      *
  1004.      * @return all entries as {@link ZipArchiveEntry} instances
  1005.      *
  1006.      * @since 1.1
  1007.      */
  1008.     public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
  1009.         final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
  1010.         return Collections.enumeration(Arrays.asList(sortByOffset(allEntries)));
  1011.     }

  1012.     /**
  1013.      * Gets all named entries in the same order their contents appear within the archive.
  1014.      *
  1015.      * @param name name of the entry.
  1016.      * @return the Iterable&lt;ZipArchiveEntry&gt; corresponding to the given name
  1017.      * @since 1.6
  1018.      */
  1019.     public Iterable<ZipArchiveEntry> getEntriesInPhysicalOrder(final String name) {
  1020.         final LinkedList<ZipArchiveEntry> linkedList = nameMap.getOrDefault(name, ZipArchiveEntry.EMPTY_LINKED_LIST);
  1021.         return Arrays.asList(sortByOffset(linkedList.toArray(ZipArchiveEntry.EMPTY_ARRAY)));
  1022.     }

  1023.     /**
  1024.      * Gets a named entry or {@code null} if no entry by that name exists.
  1025.      * <p>
  1026.      * If multiple entries with the same name exist the first entry in the archive's central directory by that name is returned.
  1027.      * </p>
  1028.      *
  1029.      * @param name name of the entry.
  1030.      * @return the ZipArchiveEntry corresponding to the given name - or {@code null} if not present.
  1031.      */
  1032.     public ZipArchiveEntry getEntry(final String name) {
  1033.         final LinkedList<ZipArchiveEntry> entries = nameMap.get(name);
  1034.         return entries != null ? entries.getFirst() : null;
  1035.     }

  1036.     /**
  1037.      * Gets the offset of the first local file header in the file.
  1038.      *
  1039.      * @return the length of the content before the first local file header
  1040.      * @since 1.23
  1041.      */
  1042.     public long getFirstLocalFileHeaderOffset() {
  1043.         return firstLocalFileHeaderOffset;
  1044.     }

  1045.     /**
  1046.      * Gets an InputStream for reading the contents of the given entry.
  1047.      *
  1048.      * @param entry the entry to get the stream for.
  1049.      * @return a stream to read the entry from. The returned stream implements {@link InputStreamStatistics}.
  1050.      * @throws IOException if unable to create an input stream from the zipEntry.
  1051.      */
  1052.     public InputStream getInputStream(final ZipArchiveEntry entry) throws IOException {
  1053.         if (!(entry instanceof Entry)) {
  1054.             return null;
  1055.         }
  1056.         // cast validity is checked just above
  1057.         ZipUtil.checkRequestedFeatures(entry);

  1058.         // doesn't get closed if the method is not supported - which
  1059.         // should never happen because of the checkRequestedFeatures
  1060.         // call above
  1061.         final InputStream is = new BufferedInputStream(getRawInputStream(entry)); // NOSONAR
  1062.         switch (ZipMethod.getMethodByCode(entry.getMethod())) {
  1063.         case STORED:
  1064.             return new StoredStatisticsStream(is);
  1065.         case UNSHRINKING:
  1066.             return new UnshrinkingInputStream(is);
  1067.         case IMPLODING:
  1068.             try {
  1069.                 return new ExplodingInputStream(entry.getGeneralPurposeBit().getSlidingDictionarySize(),
  1070.                         entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), is);
  1071.             } catch (final IllegalArgumentException ex) {
  1072.                 throw new IOException("bad IMPLODE data", ex);
  1073.             }
  1074.         case DEFLATED:
  1075.             final Inflater inflater = new Inflater(true);
  1076.             // Inflater with nowrap=true has this odd contract for a zero padding
  1077.             // byte following the data stream; this used to be zlib's requirement
  1078.             // and has been fixed a long time ago, but the contract persists so
  1079.             // we comply.
  1080.             // https://docs.oracle.com/javase/8/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
  1081.             return new InflaterInputStreamWithStatistics(new SequenceInputStream(is, new ByteArrayInputStream(ONE_ZERO_BYTE)), inflater) {
  1082.                 @Override
  1083.                 public void close() throws IOException {
  1084.                     try {
  1085.                         super.close();
  1086.                     } finally {
  1087.                         inflater.end();
  1088.                     }
  1089.                 }
  1090.             };
  1091.         case BZIP2:
  1092.             return new BZip2CompressorInputStream(is);
  1093.         case ENHANCED_DEFLATED:
  1094.             return new Deflate64CompressorInputStream(is);
  1095.         case AES_ENCRYPTED:
  1096.         case EXPANDING_LEVEL_1:
  1097.         case EXPANDING_LEVEL_2:
  1098.         case EXPANDING_LEVEL_3:
  1099.         case EXPANDING_LEVEL_4:
  1100.         case JPEG:
  1101.         case LZMA:
  1102.         case PKWARE_IMPLODING:
  1103.         case PPMD:
  1104.         case TOKENIZATION:
  1105.         case UNKNOWN:
  1106.         case WAVPACK:
  1107.         case XZ:
  1108.         default:
  1109.             throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(entry.getMethod()), entry);
  1110.         }
  1111.     }

  1112.     /**
  1113.      * Gets the raw stream of the archive entry (compressed form).
  1114.      * <p>
  1115.      * 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.
  1116.      * </p>
  1117.      * <p>
  1118.      * 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
  1119.      * {@code true} in the constructor. An IOException can also be thrown from the body of the method if this lookup fails for some reason.
  1120.      * </p>
  1121.      *
  1122.      * @param entry The entry to get the stream for
  1123.      * @return The raw input stream containing (possibly) compressed data.
  1124.      * @since 1.11
  1125.      * @throws IOException if there is a problem reading data offset (added in version 1.22).
  1126.      */
  1127.     public InputStream getRawInputStream(final ZipArchiveEntry entry) throws IOException {
  1128.         if (!(entry instanceof Entry)) {
  1129.             return null;
  1130.         }
  1131.         final long start = getDataOffset(entry);
  1132.         if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
  1133.             return null;
  1134.         }
  1135.         return createBoundedInputStream(start, entry.getCompressedSize());
  1136.     }

  1137.     /**
  1138.      * Gets the entry's content as a String if isUnixSymlink() returns true for it, otherwise returns null.
  1139.      * <p>
  1140.      * This method assumes the symbolic link's file name uses the same encoding that as been specified for this ZipFile.
  1141.      * </p>
  1142.      *
  1143.      * @param entry ZipArchiveEntry object that represents the symbolic link
  1144.      * @return entry's content as a String
  1145.      * @throws IOException problem with content's input stream
  1146.      * @since 1.5
  1147.      */
  1148.     public String getUnixSymlink(final ZipArchiveEntry entry) throws IOException {
  1149.         if (entry != null && entry.isUnixSymlink()) {
  1150.             try (InputStream in = getInputStream(entry)) {
  1151.                 return zipEncoding.decode(org.apache.commons.io.IOUtils.toByteArray(in));
  1152.             }
  1153.         }
  1154.         return null;
  1155.     }

  1156.     /**
  1157.      * Reads the central directory of the given archive and populates the internal tables with ZipArchiveEntry instances.
  1158.      * <p>
  1159.      * 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
  1160.      * additional data to be read.
  1161.      * </p>
  1162.      *
  1163.      * @return a map of zip entries that didn't have the language encoding flag set when read.
  1164.      */
  1165.     private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory() throws IOException {
  1166.         final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag = new HashMap<>();

  1167.         positionAtCentralDirectory();
  1168.         centralDirectoryStartOffset = archive.position();

  1169.         wordBbuf.rewind();
  1170.         IOUtils.readFully(archive, wordBbuf);
  1171.         long sig = ZipLong.getValue(wordBuf);

  1172.         if (sig != CFH_SIG && startsWithLocalFileHeader()) {
  1173.             throw new IOException("Central directory is empty, can't expand" + " corrupt archive.");
  1174.         }

  1175.         while (sig == CFH_SIG) {
  1176.             readCentralDirectoryEntry(noUTF8Flag);
  1177.             wordBbuf.rewind();
  1178.             IOUtils.readFully(archive, wordBbuf);
  1179.             sig = ZipLong.getValue(wordBuf);
  1180.         }
  1181.         return noUTF8Flag;
  1182.     }

  1183.     /**
  1184.      * 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
  1185.      * stream at the first central directory record.
  1186.      */
  1187.     private void positionAtCentralDirectory() throws IOException {
  1188.         final boolean is64 = positionAtEndOfCentralDirectoryRecord(archive);
  1189.         if (!is64) {
  1190.             positionAtCentralDirectory32();
  1191.         } else {
  1192.             positionAtCentralDirectory64();
  1193.         }
  1194.     }

  1195.     /**
  1196.      * Parses the &quot;End of central dir record&quot; and positions the stream at the first central directory record.
  1197.      *
  1198.      * Expects stream to be positioned at the beginning of the &quot;End of central dir record&quot;.
  1199.      */
  1200.     private void positionAtCentralDirectory32() throws IOException {
  1201.         final long endOfCentralDirectoryRecordOffset = archive.position();
  1202.         if (isSplitZipArchive) {
  1203.             skipBytes(CFD_DISK_OFFSET);
  1204.             shortBbuf.rewind();
  1205.             IOUtils.readFully(archive, shortBbuf);
  1206.             centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);

  1207.             skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);

  1208.             wordBbuf.rewind();
  1209.             IOUtils.readFully(archive, wordBbuf);
  1210.             centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
  1211.             ((ZipSplitReadOnlySeekableByteChannel) archive).position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
  1212.         } else {
  1213.             skipBytes(CFD_LENGTH_OFFSET);
  1214.             wordBbuf.rewind();
  1215.             IOUtils.readFully(archive, wordBbuf);
  1216.             final long centralDirectoryLength = ZipLong.getValue(wordBuf);

  1217.             wordBbuf.rewind();
  1218.             IOUtils.readFully(archive, wordBbuf);
  1219.             centralDirectoryStartDiskNumber = 0;
  1220.             centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);

  1221.             firstLocalFileHeaderOffset = Long.max(endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset, 0L);
  1222.             archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
  1223.         }
  1224.     }

  1225.     /**
  1226.      * Parses the &quot;Zip64 end of central directory locator&quot;, finds the &quot;Zip64 end of central directory record&quot; using the parsed information,
  1227.      * parses that and positions the stream at the first central directory record.
  1228.      *
  1229.      * Expects stream to be positioned right behind the &quot;Zip64 end of central directory locator&quot;'s signature.
  1230.      */
  1231.     private void positionAtCentralDirectory64() throws IOException {
  1232.         skipBytes(ZipConstants.WORD);
  1233.         if (isSplitZipArchive) {
  1234.             wordBbuf.rewind();
  1235.             IOUtils.readFully(archive, wordBbuf);
  1236.             final long diskNumberOfEOCD = ZipLong.getValue(wordBuf);

  1237.             dwordBbuf.rewind();
  1238.             IOUtils.readFully(archive, dwordBbuf);
  1239.             final long relativeOffsetOfEOCD = ZipEightByteInteger.getLongValue(dwordBuf);
  1240.             ((ZipSplitReadOnlySeekableByteChannel) archive).position(diskNumberOfEOCD, relativeOffsetOfEOCD);
  1241.         } else {
  1242.             skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET - ZipConstants.WORD /* signature has already been read */);
  1243.             dwordBbuf.rewind();
  1244.             IOUtils.readFully(archive, dwordBbuf);
  1245.             archive.position(ZipEightByteInteger.getLongValue(dwordBuf));
  1246.         }

  1247.         wordBbuf.rewind();
  1248.         IOUtils.readFully(archive, wordBbuf);
  1249.         if (!Arrays.equals(wordBuf, ZipArchiveOutputStream.ZIP64_EOCD_SIG)) {
  1250.             throw new ZipException("Archive's ZIP64 end of central directory locator is corrupt.");
  1251.         }

  1252.         if (isSplitZipArchive) {
  1253.             skipBytes(ZIP64_EOCD_CFD_DISK_OFFSET - ZipConstants.WORD /* signature has already been read */);
  1254.             wordBbuf.rewind();
  1255.             IOUtils.readFully(archive, wordBbuf);
  1256.             centralDirectoryStartDiskNumber = ZipLong.getValue(wordBuf);

  1257.             skipBytes(ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET);

  1258.             dwordBbuf.rewind();
  1259.             IOUtils.readFully(archive, dwordBbuf);
  1260.             centralDirectoryStartRelativeOffset = ZipEightByteInteger.getLongValue(dwordBuf);
  1261.             ((ZipSplitReadOnlySeekableByteChannel) archive).position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
  1262.         } else {
  1263.             skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET - ZipConstants.WORD /* signature has already been read */);
  1264.             dwordBbuf.rewind();
  1265.             IOUtils.readFully(archive, dwordBbuf);
  1266.             centralDirectoryStartDiskNumber = 0;
  1267.             centralDirectoryStartRelativeOffset = ZipEightByteInteger.getLongValue(dwordBuf);
  1268.             archive.position(centralDirectoryStartRelativeOffset);
  1269.         }
  1270.     }

  1271.     /**
  1272.      * Reads an individual entry of the central directory, creates an ZipArchiveEntry from it and adds it to the global maps.
  1273.      *
  1274.      * @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
  1275.      *                   later. The current entry may be added to this map.
  1276.      */
  1277.     private void readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag) throws IOException {
  1278.         cfhBbuf.rewind();
  1279.         IOUtils.readFully(archive, cfhBbuf);
  1280.         int off = 0;
  1281.         final Entry ze = new Entry();

  1282.         final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
  1283.         off += ZipConstants.SHORT;
  1284.         ze.setVersionMadeBy(versionMadeBy);
  1285.         ze.setPlatform(versionMadeBy >> BYTE_SHIFT & NIBLET_MASK);

  1286.         ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
  1287.         off += ZipConstants.SHORT; // version required

  1288.         final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
  1289.         final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
  1290.         final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
  1291.         if (hasUTF8Flag) {
  1292.             ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
  1293.         }
  1294.         ze.setGeneralPurposeBit(gpFlag);
  1295.         ze.setRawFlag(ZipShort.getValue(cfhBuf, off));

  1296.         off += ZipConstants.SHORT;

  1297.         // noinspection MagicConstant
  1298.         ze.setMethod(ZipShort.getValue(cfhBuf, off));
  1299.         off += ZipConstants.SHORT;

  1300.         final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
  1301.         ze.setTime(time);
  1302.         off += ZipConstants.WORD;

  1303.         ze.setCrc(ZipLong.getValue(cfhBuf, off));
  1304.         off += ZipConstants.WORD;

  1305.         long size = ZipLong.getValue(cfhBuf, off);
  1306.         if (size < 0) {
  1307.             throw new IOException("broken archive, entry with negative compressed size");
  1308.         }
  1309.         ze.setCompressedSize(size);
  1310.         off += ZipConstants.WORD;

  1311.         size = ZipLong.getValue(cfhBuf, off);
  1312.         if (size < 0) {
  1313.             throw new IOException("broken archive, entry with negative size");
  1314.         }
  1315.         ze.setSize(size);
  1316.         off += ZipConstants.WORD;

  1317.         final int fileNameLen = ZipShort.getValue(cfhBuf, off);
  1318.         off += ZipConstants.SHORT;
  1319.         if (fileNameLen < 0) {
  1320.             throw new IOException("broken archive, entry with negative fileNameLen");
  1321.         }

  1322.         final int extraLen = ZipShort.getValue(cfhBuf, off);
  1323.         off += ZipConstants.SHORT;
  1324.         if (extraLen < 0) {
  1325.             throw new IOException("broken archive, entry with negative extraLen");
  1326.         }

  1327.         final int commentLen = ZipShort.getValue(cfhBuf, off);
  1328.         off += ZipConstants.SHORT;
  1329.         if (commentLen < 0) {
  1330.             throw new IOException("broken archive, entry with negative commentLen");
  1331.         }

  1332.         ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
  1333.         off += ZipConstants.SHORT;

  1334.         ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
  1335.         off += ZipConstants.SHORT;

  1336.         ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
  1337.         off += ZipConstants.WORD;

  1338.         final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
  1339.         if (fileName.length < fileNameLen) {
  1340.             throw new EOFException();
  1341.         }
  1342.         ze.setName(entryEncoding.decode(fileName), fileName);

  1343.         // LFH offset,
  1344.         ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
  1345.         // data offset will be filled later
  1346.         entries.add(ze);

  1347.         final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
  1348.         if (cdExtraData.length < extraLen) {
  1349.             throw new EOFException();
  1350.         }
  1351.         try {
  1352.             ze.setCentralDirectoryExtra(cdExtraData);
  1353.         } catch (final RuntimeException e) {
  1354.             final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
  1355.             z.initCause(e);
  1356.             throw z;
  1357.         }

  1358.         setSizesAndOffsetFromZip64Extra(ze);
  1359.         sanityCheckLFHOffset(ze);

  1360.         final byte[] comment = IOUtils.readRange(archive, commentLen);
  1361.         if (comment.length < commentLen) {
  1362.             throw new EOFException();
  1363.         }
  1364.         ze.setComment(entryEncoding.decode(comment));

  1365.         if (!hasUTF8Flag && useUnicodeExtraFields) {
  1366.             noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
  1367.         }

  1368.         ze.setStreamContiguous(true);
  1369.     }

  1370.     /**
  1371.      * Walks through all recorded entries and adds the data available from the local file header.
  1372.      * <p>
  1373.      * Also records the offsets for the data to read from the entries.
  1374.      * </p>
  1375.      */
  1376.     private void resolveLocalFileHeaderData(final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag) throws IOException {
  1377.         for (final ZipArchiveEntry zipArchiveEntry : entries) {
  1378.             // entries are filled in populateFromCentralDirectory and never modified
  1379.             final Entry ze = (Entry) zipArchiveEntry;
  1380.             final int[] lens = setDataOffset(ze);
  1381.             final int fileNameLen = lens[0];
  1382.             final int extraFieldLen = lens[1];
  1383.             skipBytes(fileNameLen);
  1384.             final byte[] localExtraData = IOUtils.readRange(archive, extraFieldLen);
  1385.             if (localExtraData.length < extraFieldLen) {
  1386.                 throw new EOFException();
  1387.             }
  1388.             try {
  1389.                 ze.setExtra(localExtraData);
  1390.             } catch (final RuntimeException e) {
  1391.                 final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
  1392.                 z.initCause(e);
  1393.                 throw z;
  1394.             }

  1395.             if (entriesWithoutUTF8Flag.containsKey(ze)) {
  1396.                 final NameAndComment nc = entriesWithoutUTF8Flag.get(ze);
  1397.                 ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, nc.comment);
  1398.             }
  1399.         }
  1400.     }

  1401.     private void sanityCheckLFHOffset(final ZipArchiveEntry entry) throws IOException {
  1402.         if (entry.getDiskNumberStart() < 0) {
  1403.             throw new IOException("broken archive, entry with negative disk number");
  1404.         }
  1405.         if (entry.getLocalHeaderOffset() < 0) {
  1406.             throw new IOException("broken archive, entry with negative local file header offset");
  1407.         }
  1408.         if (isSplitZipArchive) {
  1409.             if (entry.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
  1410.                 throw new IOException("local file header for " + entry.getName() + " starts on a later disk than central directory");
  1411.             }
  1412.             if (entry.getDiskNumberStart() == centralDirectoryStartDiskNumber && entry.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
  1413.                 throw new IOException("local file header for " + entry.getName() + " starts after central directory");
  1414.             }
  1415.         } else if (entry.getLocalHeaderOffset() > centralDirectoryStartOffset) {
  1416.             throw new IOException("local file header for " + entry.getName() + " starts after central directory");
  1417.         }
  1418.     }

  1419.     private int[] setDataOffset(final ZipArchiveEntry entry) throws IOException {
  1420.         long offset = entry.getLocalHeaderOffset();
  1421.         if (isSplitZipArchive) {
  1422.             ((ZipSplitReadOnlySeekableByteChannel) archive).position(entry.getDiskNumberStart(), offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
  1423.             // the offset should be updated to the global offset
  1424.             offset = archive.position() - LFH_OFFSET_FOR_FILENAME_LENGTH;
  1425.         } else {
  1426.             archive.position(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
  1427.         }
  1428.         wordBbuf.rewind();
  1429.         IOUtils.readFully(archive, wordBbuf);
  1430.         wordBbuf.flip();
  1431.         wordBbuf.get(shortBuf);
  1432.         final int fileNameLen = ZipShort.getValue(shortBuf);
  1433.         wordBbuf.get(shortBuf);
  1434.         final int extraFieldLen = ZipShort.getValue(shortBuf);
  1435.         entry.setDataOffset(offset + LFH_OFFSET_FOR_FILENAME_LENGTH + ZipConstants.SHORT + ZipConstants.SHORT + fileNameLen + extraFieldLen);
  1436.         if (entry.getDataOffset() + entry.getCompressedSize() > centralDirectoryStartOffset) {
  1437.             throw new IOException("data for " + entry.getName() + " overlaps with central directory.");
  1438.         }
  1439.         return new int[] { fileNameLen, extraFieldLen };
  1440.     }

  1441.     /**
  1442.      * 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
  1443.      * offset of the local file header.
  1444.      * <p>
  1445.      * Ensures the Zip64 extra either knows both compressed and uncompressed size or neither of both as the internal logic in ExtraFieldUtils forces the field
  1446.      * to create local header data even if they are never used - and here a field with only one size would be invalid.
  1447.      * </p>
  1448.      */
  1449.     private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry entry) throws IOException {
  1450.         final ZipExtraField extra = entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
  1451.         if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
  1452.             throw new ZipException("archive contains unparseable zip64 extra field");
  1453.         }
  1454.         final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra;
  1455.         if (z64 != null) {
  1456.             final boolean hasUncompressedSize = entry.getSize() == ZipConstants.ZIP64_MAGIC;
  1457.             final boolean hasCompressedSize = entry.getCompressedSize() == ZipConstants.ZIP64_MAGIC;
  1458.             final boolean hasRelativeHeaderOffset = entry.getLocalHeaderOffset() == ZipConstants.ZIP64_MAGIC;
  1459.             final boolean hasDiskStart = entry.getDiskNumberStart() == ZipConstants.ZIP64_MAGIC_SHORT;
  1460.             z64.reparseCentralDirectoryData(hasUncompressedSize, hasCompressedSize, hasRelativeHeaderOffset, hasDiskStart);

  1461.             if (hasUncompressedSize) {
  1462.                 final long size = z64.getSize().getLongValue();
  1463.                 if (size < 0) {
  1464.                     throw new IOException("broken archive, entry with negative size");
  1465.                 }
  1466.                 entry.setSize(size);
  1467.             } else if (hasCompressedSize) {
  1468.                 z64.setSize(new ZipEightByteInteger(entry.getSize()));
  1469.             }

  1470.             if (hasCompressedSize) {
  1471.                 final long size = z64.getCompressedSize().getLongValue();
  1472.                 if (size < 0) {
  1473.                     throw new IOException("broken archive, entry with negative compressed size");
  1474.                 }
  1475.                 entry.setCompressedSize(size);
  1476.             } else if (hasUncompressedSize) {
  1477.                 z64.setCompressedSize(new ZipEightByteInteger(entry.getCompressedSize()));
  1478.             }

  1479.             if (hasRelativeHeaderOffset) {
  1480.                 entry.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
  1481.             }

  1482.             if (hasDiskStart) {
  1483.                 entry.setDiskNumberStart(z64.getDiskStartNumber().getValue());
  1484.             }
  1485.         }
  1486.     }

  1487.     /**
  1488.      * Skips the given number of bytes or throws an EOFException if skipping failed.
  1489.      */
  1490.     private void skipBytes(final int count) throws IOException {
  1491.         final long currentPosition = archive.position();
  1492.         final long newPosition = currentPosition + count;
  1493.         if (newPosition > archive.size()) {
  1494.             throw new EOFException();
  1495.         }
  1496.         archive.position(newPosition);
  1497.     }

  1498.     /**
  1499.      * Sorts entries in place by offset.
  1500.      *
  1501.      * @param allEntries entries to sort
  1502.      * @return the given entries, sorted.
  1503.      */
  1504.     private ZipArchiveEntry[] sortByOffset(final ZipArchiveEntry[] allEntries) {
  1505.         Arrays.sort(allEntries, offsetComparator);
  1506.         return allEntries;
  1507.     }

  1508.     /**
  1509.      * Checks whether the archive starts with an LFH. If it doesn't, it may be an empty archive.
  1510.      */
  1511.     private boolean startsWithLocalFileHeader() throws IOException {
  1512.         archive.position(firstLocalFileHeaderOffset);
  1513.         wordBbuf.rewind();
  1514.         IOUtils.readFully(archive, wordBbuf);
  1515.         return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
  1516.     }
  1517. }