You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/12/08 01:56:31 UTC

[commons-compress] branch master updated: Javadoc, sort methods

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git


The following commit(s) were added to refs/heads/master by this push:
     new 5c2b2d20 Javadoc, sort methods
5c2b2d20 is described below

commit 5c2b2d2092e8f2f43e526ac31eb2067a18024fb8
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed Dec 7 20:56:26 2022 -0500

    Javadoc, sort methods
---
 .../commons/compress/archivers/zip/ZipFile.java    | 1719 ++++++++++----------
 1 file changed, 859 insertions(+), 860 deletions(-)

diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
index 3caf12f3..14e949b6 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
@@ -87,133 +87,377 @@ import org.apache.commons.compress.utils.InputStreamStatistics;
  *   instances.</li>
  *   <li>close is allowed to throw IOException.</li>
  * </ul>
- *
  */
 public class ZipFile implements Closeable {
+    /**
+     * Lock-free implementation of BoundedInputStream. The
+     * implementation uses positioned reads on the underlying archive
+     * file channel and therefore performs significantly faster in
+     * concurrent environment.
+     */
+    private class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
+        private final FileChannel archive;
+
+        BoundedFileChannelInputStream(final long start, final long remaining) {
+            super(start, remaining);
+            archive = (FileChannel) ZipFile.this.archive;
+        }
+
+        @Override
+        protected int read(final long pos, final ByteBuffer buf) throws IOException {
+            final int read = archive.read(buf, pos);
+            buf.flip();
+            return read;
+        }
+    }
+    /**
+     * Extends ZipArchiveEntry to store the offset within the archive.
+     */
+    private static class Entry extends ZipArchiveEntry {
+
+        Entry() {
+        }
+
+        @Override
+        public boolean equals(final Object other) {
+            if (super.equals(other)) {
+                // super.equals would return false if other were not an Entry
+                final Entry otherEntry = (Entry) other;
+                return getLocalHeaderOffset()
+                        == otherEntry.getLocalHeaderOffset()
+                    && super.getDataOffset()
+                        == otherEntry.getDataOffset()
+                    && super.getDiskNumberStart()
+                        == otherEntry.getDiskNumberStart();
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return 3 * super.hashCode()
+                + (int) getLocalHeaderOffset()+(int)(getLocalHeaderOffset()>>32);
+        }
+    }
+    private static final class NameAndComment {
+        private final byte[] name;
+        private final byte[] comment;
+        private NameAndComment(final byte[] name, final byte[] comment) {
+            this.name = name;
+            this.comment = comment;
+        }
+    }
+    private static class StoredStatisticsStream extends CountingInputStream implements InputStreamStatistics {
+        StoredStatisticsStream(final InputStream in) {
+            super(in);
+        }
+
+        @Override
+        public long getCompressedCount() {
+            return super.getBytesRead();
+        }
+
+        @Override
+        public long getUncompressedCount() {
+            return getCompressedCount();
+        }
+    }
     private static final int HASH_SIZE = 509;
     static final int NIBLET_MASK = 0x0f;
     static final int BYTE_SHIFT = 8;
     private static final int POS_0 = 0;
+
     private static final int POS_1 = 1;
+
     private static final int POS_2 = 2;
+
     private static final int POS_3 = 3;
+
     private static final byte[] ONE_ZERO_BYTE = new byte[1];
 
     /**
-     * List of entries in the order they appear inside the central
-     * directory.
+     * Length of a "central directory" entry structure without file
+     * name, extra fields or comment.
      */
-    private final List<ZipArchiveEntry> entries =
-        new LinkedList<>();
+    private static final int CFH_LEN =
+        /* version made by                 */ SHORT
+        /* version needed to extract       */ + SHORT
+        /* general purpose bit flag        */ + SHORT
+        /* compression method              */ + SHORT
+        /* last mod file time              */ + SHORT
+        /* last mod file date              */ + SHORT
+        /* crc-32                          */ + WORD
+        /* compressed size                 */ + WORD
+        /* uncompressed size               */ + WORD
+        /* file name length                 */ + SHORT
+        /* extra field length              */ + SHORT
+        /* file comment length             */ + SHORT
+        /* disk number start               */ + SHORT
+        /* internal file attributes        */ + SHORT
+        /* external file attributes        */ + WORD
+        /* relative offset of local header */ + WORD;
 
-    /**
-     * Maps String to list of ZipArchiveEntrys, name -> actual entries.
-     */
-    private final Map<String, LinkedList<ZipArchiveEntry>> nameMap =
-        new HashMap<>(HASH_SIZE);
+    private static final long CFH_SIG =
+        ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
 
     /**
-     * The encoding to use for file names and the file comment.
-     *
-     * <p>For a list of possible values see <a
-     * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
-     * Defaults to UTF-8.</p>
+     * Length of the "End of central directory record" - which is
+     * supposed to be the last structure of the archive - without file
+     * comment.
      */
-    private final String encoding;
+    static final int MIN_EOCD_SIZE =
+        /* end of central dir signature    */ WORD
+        /* number of this disk             */ + SHORT
+        /* number of the disk with the     */
+        /* start of the central directory  */ + SHORT
+        /* total number of entries in      */
+        /* the central dir on this disk    */ + SHORT
+        /* total number of entries in      */
+        /* the central dir                 */ + SHORT
+        /* size of the central directory   */ + WORD
+        /* offset of start of central      */
+        /* directory with respect to       */
+        /* the starting disk number        */ + WORD
+        /* zipfile comment length          */ + SHORT;
 
     /**
-     * The zip encoding to use for file names and the file comment.
+     * Maximum length of the "End of central directory record" with a
+     * file comment.
      */
-    private final ZipEncoding zipEncoding;
+    private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
+        /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT;
 
     /**
-     * File name of actual source.
+     * 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 the "End of central directory
+     * record".
      */
-    private final String archiveName;
+    private static final int CFD_LENGTH_OFFSET =
+        /* end of central dir signature    */ WORD
+        /* number of this disk             */ + SHORT
+        /* number of the disk with the     */
+        /* start of the central directory  */ + SHORT
+        /* total number of entries in      */
+        /* the central dir on this disk    */ + SHORT
+        /* total number of entries in      */
+        /* the central dir                 */ + SHORT;
 
     /**
-     * The actual data source.
+     * 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 the "End of central directory
+     * record".
      */
-    private final SeekableByteChannel archive;
-
+    private static final int CFD_DISK_OFFSET =
+            /* end of central dir signature    */ WORD
+            /* number of this disk             */ + SHORT;
     /**
-     * Whether to look for and use Unicode extra fields.
+     * 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 the disk with the start
+     * of the central directory".
      */
-    private final boolean useUnicodeExtraFields;
-
+    private static final int CFD_LOCATOR_RELATIVE_OFFSET =
+            /* total number of entries in      */
+            /* the central dir on this disk    */ + SHORT
+            /* total number of entries in      */
+            /* the central dir                 */ + SHORT
+            /* size of the central directory   */ + WORD;
     /**
-     * Whether the file is closed.
+     * 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 all.
      */
-    private volatile boolean closed = true;
-
+    private static final int ZIP64_EOCDL_LENGTH =
+        /* zip64 end of central dir locator sig */ WORD
+        /* number of the disk with the start    */
+        /* start of the zip64 end of            */
+        /* central directory                    */ + WORD
+        /* relative offset of the zip64         */
+        /* end of central directory record      */ + DWORD
+        /* total number of disks                */ + WORD;
     /**
-     * Whether the zip archive is a split zip archive
+     * 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 to the start of the "Zip64 end of
+     * central directory locator".
      */
-    private final boolean isSplitZipArchive;
-
-    // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
-    private final byte[] dwordBuf = new byte[DWORD];
-    private final byte[] wordBuf = new byte[WORD];
-    private final byte[] cfhBuf = new byte[CFH_LEN];
-    private final byte[] shortBuf = new byte[SHORT];
-    private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
-    private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
-    private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
-    private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
-
-    private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset;
-    private long centralDirectoryStartOffset;
-    private long firstLocalFileHeaderOffset = 0L;
-
+    private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
+        /* zip64 end of central dir locator sig */ WORD
+        /* number of the disk with the start    */
+        /* start of the zip64 end of            */
+        /* central directory                    */ + WORD;
     /**
-     * Opens the given file for reading, assuming "UTF8" for file names.
-     *
-     * @param f the archive.
-     *
-     * @throws IOException if an error occurs while reading the file.
+     * 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 of the "Zip64 end of
+     * central directory record".
      */
-    public ZipFile(final File f) throws IOException {
-        this(f, ZipEncodingHelper.UTF8);
-    }
-
+    private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
+        /* zip64 end of central dir        */
+        /* signature                       */ WORD
+        /* size of zip64 end of central    */
+        /* directory record                */ + DWORD
+        /* version made by                 */ + SHORT
+        /* version needed to extract       */ + SHORT
+        /* number of this disk             */ + WORD
+        /* number of the disk with the     */
+        /* start of the central directory  */ + WORD
+        /* total number of entries in the  */
+        /* central directory on this disk  */ + DWORD
+        /* total number of entries in the  */
+        /* central directory               */ + DWORD
+        /* size of the central directory   */ + DWORD;
     /**
-     * Opens the given path for reading, assuming "UTF8" for file names.
-     * @param path path to the archive.
-     * @throws IOException if an error occurs while reading the file.
-     * @since 1.22
+     * 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 start of the "Zip64 end of
+     * central directory record".
      */
-    public ZipFile(final Path path) throws IOException {
-        this(path, ZipEncodingHelper.UTF8);
-    }
-
-    /**
-     * Opens the given file for reading, assuming "UTF8".
+    private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
+            /* zip64 end of central dir        */
+            /* signature                       */ WORD
+            /* size of zip64 end of central    */
+            /* directory record                */ + DWORD
+            /* version made by                 */ + SHORT
+            /* version needed to extract       */ + SHORT
+            /* number of this disk             */ + WORD;
+    /**
+     * 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 "number of the disk
+     * with the start of the central directory".
+     */
+    private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
+            /* total number of entries in the  */
+            /* central directory on this disk  */ DWORD
+            /* total number of entries in the  */
+            /* central directory               */ + DWORD
+            /* size of the central directory   */ + DWORD;
+    /**
+     * Number of bytes in local file header up to the &quot;length of
+     * file name&quot; entry.
+     */
+    private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
+        /* local file header signature     */ WORD
+        /* version needed to extract       */ + SHORT
+        /* general purpose bit flag        */ + SHORT
+        /* compression method              */ + SHORT
+        /* last mod file time              */ + SHORT
+        /* last mod file date              */ + SHORT
+        /* crc-32                          */ + WORD
+        /* compressed size                 */ + WORD
+        /* uncompressed size               */ + (long) WORD;
+
+    /**
+     * close a zipfile quietly; throw no io fault, do nothing
+     * on a null parameter
+     * @param zipfile file to close, can be null
+     */
+    public static void closeQuietly(final ZipFile zipfile) {
+        IOUtils.closeQuietly(zipfile);
+    }
+    /**
+     * List of entries in the order they appear inside the central
+     * directory.
+     */
+    private final List<ZipArchiveEntry> entries =
+        new LinkedList<>();
+    /**
+     * Maps String to list of ZipArchiveEntrys, name -> actual entries.
+     */
+    private final Map<String, LinkedList<ZipArchiveEntry>> nameMap =
+        new HashMap<>(HASH_SIZE);
+
+    /**
+     * The encoding to use for file names and the file comment.
      *
-     * @param name name of the archive.
+     * <p>For a list of possible values see <a
+     * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
+     * Defaults to UTF-8.</p>
+     */
+    private final String encoding;
+
+    /**
+     * The zip encoding to use for file names and the file comment.
+     */
+    private final ZipEncoding zipEncoding;
+
+    /**
+     * File name of actual source.
+     */
+    private final String archiveName;
+
+    /**
+     * The actual data source.
+     */
+    private final SeekableByteChannel archive;
+
+    /**
+     * Whether to look for and use Unicode extra fields.
+     */
+    private final boolean useUnicodeExtraFields;
+
+    /**
+     * Whether the file is closed.
+     */
+    private volatile boolean closed = true;
+
+    /**
+     * Whether the zip archive is a split zip archive
+     */
+    private final boolean isSplitZipArchive;
+
+    // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
+    private final byte[] dwordBuf = new byte[DWORD];
+
+    private final byte[] wordBuf = new byte[WORD];
+
+    private final byte[] cfhBuf = new byte[CFH_LEN];
+
+    private final byte[] shortBuf = new byte[SHORT];
+
+    private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
+
+    private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
+
+    private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
+
+    private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
+
+    private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset;
+
+    private long centralDirectoryStartOffset;
+
+    private long firstLocalFileHeaderOffset;
+
+    /**
+     * Compares two ZipArchiveEntries based on their offset within the archive.
      *
-     * @throws IOException if an error occurs while reading the file.
+     * <p>Won't return any meaningful results if one of the entries
+     * isn't part of the archive at all.</p>
+     *
+     * @since 1.1
      */
-    public ZipFile(final String name) throws IOException {
-        this(new File(name).toPath(), ZipEncodingHelper.UTF8);
-    }
+    private final Comparator<ZipArchiveEntry> offsetComparator =
+        Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
+            .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);
 
     /**
-     * Opens the given file for reading, assuming the specified
-     * encoding for file names, scanning unicode extra fields.
+     * Opens the given file for reading, assuming "UTF8" for file names.
      *
-     * @param name name of the archive.
-     * @param encoding the encoding to use for file names, use null
-     * for the platform's default encoding
+     * @param f the archive.
      *
      * @throws IOException if an error occurs while reading the file.
      */
-    public ZipFile(final String name, final String encoding) throws IOException {
-        this(new File(name).toPath(), encoding, true);
+    public ZipFile(final File f) throws IOException {
+        this(f, ZipEncodingHelper.UTF8);
     }
 
     /**
      * Opens the given file for reading, assuming the specified
-     * encoding for file names and scanning for unicode extra fields.
+     * encoding for file names and scanning for Unicode extra fields.
      *
      * @param f the archive.
      * @param encoding the encoding to use for file names, use null
@@ -225,19 +469,6 @@ public class ZipFile implements Closeable {
         this(f.toPath(), encoding, true);
     }
 
-    /**
-     * Opens the given path for reading, assuming the specified
-     * encoding for file names and scanning for unicode extra fields.
-     * @param path path to the archive.
-     * @param encoding the encoding to use for file names, use null
-     * for the platform's default encoding
-     * @throws IOException if an error occurs while reading the file.
-     * @since 1.22
-     */
-    public ZipFile(final Path path, final String encoding) throws IOException {
-        this(path, encoding, true);
-    }
-
     /**
      * Opens the given file for reading, assuming the specified
      * encoding for file names.
@@ -255,27 +486,10 @@ public class ZipFile implements Closeable {
         this(f.toPath(), encoding, useUnicodeExtraFields, false);
     }
 
-    /**
-     * Opens the given path for reading, assuming the specified
-     * encoding for file names.
-     * @param path path to the archive.
-     * @param encoding the encoding to use for file names, use null
-     * for the platform's default encoding
-     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
-     * Extra Fields (if present) to set the file names.
-     * @throws IOException if an error occurs while reading the file.
-     * @since 1.22
-     */
-    public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields)
-            throws IOException {
-        this(path, encoding, useUnicodeExtraFields, false);
-    }
-
     /**
      * Opens the given file for reading, assuming the specified
      * encoding for file names.
      *
-     *
      * <p>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 when the archive is big. The {@code ignoreLocalFileHeader} parameter
      * can be set to {@code true} which restricts parsing to the central directory. Unfortunately the local file header
@@ -301,6 +515,49 @@ public class ZipFile implements Closeable {
              f.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
     }
 
+    /**
+     * Opens the given path for reading, assuming "UTF8" for file names.
+     *
+     * @param path path to the archive.
+     * @throws IOException if an error occurs while reading the file.
+     * @since 1.22
+     */
+    public ZipFile(final Path path) throws IOException {
+        this(path, ZipEncodingHelper.UTF8);
+    }
+
+    /**
+     * Opens the given path for reading, assuming the specified
+     * encoding for file names and scanning for Unicode extra fields.
+     *
+     * @param path path to the archive.
+     * @param encoding the encoding to use for file names, use null
+     * for the platform's default encoding
+     * @throws IOException if an error occurs while reading the file.
+     * @since 1.22
+     */
+    public ZipFile(final Path path, final String encoding) throws IOException {
+        this(path, encoding, true);
+    }
+
+
+    /**
+     * Opens the given path for reading, assuming the specified
+     * encoding for file names.
+     *
+     * @param path path to the archive.
+     * @param encoding the encoding to use for file names, use null
+     * for the platform's default encoding
+     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
+     * Extra Fields (if present) to set the file names.
+     * @throws IOException if an error occurs while reading the file.
+     * @since 1.22
+     */
+    public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields)
+            throws IOException {
+        this(path, encoding, useUnicodeExtraFields, false);
+    }
+
     /**
      * Opens the given path for reading, assuming the specified
      * encoding for file names.
@@ -310,6 +567,7 @@ public class ZipFile implements Closeable {
      * may contain information not present inside of the central directory which will not be available when the argument
      * is set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
      * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.</p>
+     *
      * @param path path to the archive.
      * @param encoding the encoding to use for file names, use null
      * for the platform's default encoding
@@ -454,12 +712,41 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * The encoding to use for file names and the file comment.
+     * Opens the given file for reading, assuming "UTF8".
      *
-     * @return null if using the platform's default character encoding.
+     * @param name name of the archive.
+     *
+     * @throws IOException if an error occurs while reading the file.
      */
-    public String getEncoding() {
-        return encoding;
+    public ZipFile(final String name) throws IOException {
+        this(new File(name).toPath(), ZipEncodingHelper.UTF8);
+    }
+
+    /**
+     * Opens the given file for reading, assuming the specified
+     * encoding for file names, scanning unicode extra fields.
+     *
+     * @param name name of the archive.
+     * @param encoding the encoding to use for file names, use null
+     * for the platform's default encoding
+     *
+     * @throws IOException if an error occurs while reading the file.
+     */
+    public ZipFile(final String name, final String encoding) throws IOException {
+        this(new File(name).toPath(), encoding, true);
+    }
+
+    /**
+     * Whether this class is able to read the given entry.
+     *
+     * <p>May return false if it is set up to use encryption or a
+     * compression method that hasn't been implemented yet.</p>
+     * @since 1.1
+     * @param ze the entry
+     * @return whether this class is able to read the given entry.
+     */
+    public boolean canReadEntryData(final ZipArchiveEntry ze) {
+        return ZipUtil.canHandleEntryData(ze);
     }
 
     /**
@@ -477,61 +764,111 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * close a zipfile quietly; throw no io fault, do nothing
-     * on a null parameter
-     * @param zipfile file to close, can be null
-     */
-    public static void closeQuietly(final ZipFile zipfile) {
-        IOUtils.closeQuietly(zipfile);
-    }
+     * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream.
+     * Compression and all other attributes will be as in this file.
+     * <p>This method transfers entries based on the central directory of the zip file.</p>
+     *
+     * @param target The zipArchiveOutputStream to write the entries to
+     * @param predicate A predicate that selects which entries to write
+     * @throws IOException on error
+     */
+    public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate)
+            throws IOException {
+        final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
+        while (src.hasMoreElements()) {
+            final ZipArchiveEntry entry = src.nextElement();
+            if (predicate.test( entry)) {
+                target.addRawArchiveEntry(entry, getRawInputStream(entry));
+            }
+        }
+    }
 
     /**
-     * Returns all entries.
-     *
-     * <p>Entries will be returned in the same order they appear
-     * within the archive's central directory.</p>
-     *
-     * @return all entries as {@link ZipArchiveEntry} instances
+     * Creates new BoundedInputStream, according to implementation of
+     * underlying archive channel.
      */
-    public Enumeration<ZipArchiveEntry> getEntries() {
-        return Collections.enumeration(entries);
+    private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
+        if (start < 0 || remaining < 0 || start + remaining < start) {
+            throw new IllegalArgumentException("Corrupted archive, stream boundaries"
+                + " are out of range");
+        }
+        return archive instanceof FileChannel ?
+            new BoundedFileChannelInputStream(start, remaining) :
+            new BoundedSeekableByteChannelInputStream(start, remaining, archive);
+    }
+
+    private void fillNameMap() {
+        entries.forEach(ze -> {
+            // entries is filled in populateFromCentralDirectory and
+            // never modified
+            final String name = ze.getName();
+            LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
+            entriesOfThatName.addLast(ze);
+        });
     }
 
     /**
-     * Returns all entries in physical order.
-     *
-     * <p>Entries will be returned in the same order their contents
-     * appear within the archive.</p>
+     * Ensures that the close method of this zipfile is called when
+     * there are no more references to it.
+     * @see #close()
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (!closed) {
+                System.err.println("Cleaning up unclosed ZipFile for archive "
+                                   + archiveName);
+                close();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Gets an InputStream for reading the content before the first local file header.
      *
-     * @return all entries as {@link ZipArchiveEntry} instances
+     * @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 header.
+     * @since 1.23
+     */
+    public InputStream getContentBeforeFirstLocalFileHeader() {
+        return firstLocalFileHeaderOffset == 0
+                ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
+    }
+
+    private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
+        final long s = ze.getDataOffset();
+        if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
+            setDataOffset(ze);
+            return ze.getDataOffset();
+        }
+        return s;
+    }
+
+    /**
+     * Gets the encoding to use for file names and the file comment.
      *
-     * @since 1.1
+     * @return null if using the platform's default character encoding.
      */
-    public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
-        final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
-        Arrays.sort(allEntries, offsetComparator);
-        return Collections.enumeration(Arrays.asList(allEntries));
+    public String getEncoding() {
+        return encoding;
     }
 
     /**
-     * Returns a named entry - or {@code null} if no entry by
-     * that name exists.
+     * Gets all entries.
      *
-     * <p>If multiple entries with the same name exist the first entry
-     * in the archive's central directory by that name is
-     * returned.</p>
+     * <p>Entries will be returned in the same order they appear
+     * within the archive's central directory.</p>
      *
-     * @param name name of the entry.
-     * @return the ZipArchiveEntry corresponding to the given name - or
-     * {@code null} if not present.
+     * @return all entries as {@link ZipArchiveEntry} instances
      */
-    public ZipArchiveEntry getEntry(final String name) {
-        final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
-        return entriesOfThatName != null ? entriesOfThatName.getFirst() : null;
+    public Enumeration<ZipArchiveEntry> getEntries() {
+        return Collections.enumeration(entries);
     }
 
     /**
-     * Returns all named entries in the same order they appear within
+     * Gets all named entries in the same order they appear within
      * the archive's central directory.
      *
      * @param name name of the entry.
@@ -546,7 +883,23 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * Returns all named entries in the same order their contents
+     * Gets all entries in physical order.
+     *
+     * <p>Entries will be returned in the same order their contents
+     * appear within the archive.</p>
+     *
+     * @return all entries as {@link ZipArchiveEntry} instances
+     *
+     * @since 1.1
+     */
+    public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
+        final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
+        Arrays.sort(allEntries, offsetComparator);
+        return Collections.enumeration(Arrays.asList(allEntries));
+    }
+
+    /**
+     * Gets all named entries in the same order their contents
      * appear within the archive.
      *
      * @param name name of the entry.
@@ -565,69 +918,34 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * Whether this class is able to read the given entry.
-     *
-     * <p>May return false if it is set up to use encryption or a
-     * compression method that hasn't been implemented yet.</p>
-     * @since 1.1
-     * @param ze the entry
-     * @return whether this class is able to read the given entry.
-     */
-    public boolean canReadEntryData(final ZipArchiveEntry ze) {
-        return ZipUtil.canHandleEntryData(ze);
-    }
-
-    /**
-     * Expose the raw stream of the archive entry (compressed form).
-     *
-     * <p>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.</p>
+     * Gets a named entry or {@code null} if no entry by
+     * that name exists.
      *
-     * <p>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 {@code true}
-     * in the constructor. An IOException can also be thrown from the body of the method
-     * if this lookup fails for some reason.</p>
+     * <p>If multiple entries with the same name exist the first entry
+     * in the archive's central directory by that name is
+     * returned.</p>
      *
-     * @param ze The entry to get the stream for
-     * @return The raw input stream containing (possibly) compressed data.
-     * @since 1.11
-     * @throws IOException if there is a problem reading data offset (added in version 1.22).
+     * @param name name of the entry.
+     * @return the ZipArchiveEntry corresponding to the given name - or
+     * {@code null} if not present.
      */
-    public InputStream getRawInputStream(final ZipArchiveEntry ze) throws IOException {
-        if (!(ze instanceof Entry)) {
-            return null;
-        }
-
-        final long start = getDataOffset(ze);
-        if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
-            return null;
-        }
-        return createBoundedInputStream(start, ze.getCompressedSize());
+    public ZipArchiveEntry getEntry(final String name) {
+        final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
+        return entriesOfThatName != null ? entriesOfThatName.getFirst() : null;
     }
 
-
     /**
-     * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream.
-     * Compression and all other attributes will be as in this file.
-     * <p>This method transfers entries based on the central directory of the zip file.</p>
+     * Gets the offset of the first local file header in the file.
      *
-     * @param target The zipArchiveOutputStream to write the entries to
-     * @param predicate A predicate that selects which entries to write
-     * @throws IOException on error
+     * @return the length of the content before the first local file header
+     * @since 1.23
      */
-    public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate)
-            throws IOException {
-        final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
-        while (src.hasMoreElements()) {
-            final ZipArchiveEntry entry = src.nextElement();
-            if (predicate.test( entry)) {
-                target.addRawArchiveEntry(entry, getRawInputStream(entry));
-            }
-        }
+    public long getFirstLocalFileHeaderOffset() {
+        return firstLocalFileHeaderOffset;
     }
 
     /**
-     * Returns an InputStream for reading the contents of the given entry.
+     * Gets an InputStream for reading the contents of the given entry.
      *
      * @param ze the entry to get the stream for.
      * @return a stream to read the entry from. The returned stream
@@ -699,11 +1017,36 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * <p>
-     * Convenience method to return the entry's content as a String if isUnixSymlink()
-     * returns true for it, otherwise returns null.
-     * </p>
+     * Gets the raw stream of the archive entry (compressed form).
+     *
+     * <p>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.</p>
      *
+     * <p>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 {@code true}
+     * in the constructor. An IOException can also be thrown from the body of the method
+     * if this lookup fails for some reason.</p>
+     *
+     * @param ze The entry to get the stream for
+     * @return The raw input stream containing (possibly) compressed data.
+     * @since 1.11
+     * @throws IOException if there is a problem reading data offset (added in version 1.22).
+     */
+    public InputStream getRawInputStream(final ZipArchiveEntry ze) throws IOException {
+        if (!(ze instanceof Entry)) {
+            return null;
+        }
+
+        final long start = getDataOffset(ze);
+        if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
+            return null;
+        }
+        return createBoundedInputStream(start, ze.getCompressedSize());
+    }
+
+    /**
+     * Gets the entry's content as a String if isUnixSymlink()
+     * returns true for it, otherwise returns null.
      * <p>This method assumes the symbolic link's file name uses the
      * same encoding that as been specified for this ZipFile.</p>
      *
@@ -722,85 +1065,20 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * Offset of the first local file header in the file.
+     * Reads the central directory of the given archive and populates
+     * the internal tables with ZipArchiveEntry instances.
      *
-     * @return the length of the content before the first local file header
-     * @since 1.23
-     */
-    public long getFirstLocalFileHeaderOffset() {
-        return firstLocalFileHeaderOffset;
-    }
-
-    /**
-     * Returns an InputStream for reading the content before the first local file header.
+     * <p>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 additional data to be read.</p>
      *
-     * @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 header.
-     * @since 1.23
+     * @return a map of zipentries that didn't have the language
+     * encoding flag set when read.
      */
-    public InputStream getContentBeforeFirstLocalFileHeader() {
-        return firstLocalFileHeaderOffset == 0
-                ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
-    }
-
-    /**
-     * Ensures that the close method of this zipfile is called when
-     * there are no more references to it.
-     * @see #close()
-     */
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (!closed) {
-                System.err.println("Cleaning up unclosed ZipFile for archive "
-                                   + archiveName);
-                close();
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
-    /**
-     * Length of a "central directory" entry structure without file
-     * name, extra fields or comment.
-     */
-    private static final int CFH_LEN =
-        /* version made by                 */ SHORT
-        /* version needed to extract       */ + SHORT
-        /* general purpose bit flag        */ + SHORT
-        /* compression method              */ + SHORT
-        /* last mod file time              */ + SHORT
-        /* last mod file date              */ + SHORT
-        /* crc-32                          */ + WORD
-        /* compressed size                 */ + WORD
-        /* uncompressed size               */ + WORD
-        /* file name length                 */ + SHORT
-        /* extra field length              */ + SHORT
-        /* file comment length             */ + SHORT
-        /* disk number start               */ + SHORT
-        /* internal file attributes        */ + SHORT
-        /* external file attributes        */ + WORD
-        /* relative offset of local header */ + WORD;
-
-    private static final long CFH_SIG =
-        ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
-
-    /**
-     * Reads the central directory of the given archive and populates
-     * the internal tables with ZipArchiveEntry instances.
-     *
-     * <p>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 additional data to be read.</p>
-     *
-     * @return a map of zipentries that didn't have the language
-     * encoding flag set when read.
-     */
-    private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory()
-        throws IOException {
-        final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag =
-            new HashMap<>();
+    private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory()
+        throws IOException {
+        final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag =
+            new HashMap<>();
 
         positionAtCentralDirectory();
         centralDirectoryStartOffset = archive.position();
@@ -809,371 +1087,19 @@ public class ZipFile implements Closeable {
         IOUtils.readFully(archive, wordBbuf);
         long sig = ZipLong.getValue(wordBuf);
 
-        if (sig != CFH_SIG && startsWithLocalFileHeader()) {
-            throw new IOException("Central directory is empty, can't expand"
-                                  + " corrupt archive.");
-        }
-
-        while (sig == CFH_SIG) {
-            readCentralDirectoryEntry(noUTF8Flag);
-            wordBbuf.rewind();
-            IOUtils.readFully(archive, wordBbuf);
-            sig = ZipLong.getValue(wordBuf);
-        }
-        return noUTF8Flag;
-    }
-
-    /**
-     * Reads an individual entry of the central directory, creats an
-     * ZipArchiveEntry from it and adds it to the global maps.
-     *
-     * @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 later.  The current entry may be
-     * added to this map.
-     */
-    private void
-        readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag)
-        throws IOException {
-        cfhBbuf.rewind();
-        IOUtils.readFully(archive, cfhBbuf);
-        int off = 0;
-        final Entry ze = new Entry();
-
-        final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
-        off += SHORT;
-        ze.setVersionMadeBy(versionMadeBy);
-        ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
-
-        ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
-        off += SHORT; // version required
-
-        final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
-        final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
-        final ZipEncoding entryEncoding =
-            hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
-        if (hasUTF8Flag) {
-            ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
-        }
-        ze.setGeneralPurposeBit(gpFlag);
-        ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
-
-        off += SHORT;
-
-        //noinspection MagicConstant
-        ze.setMethod(ZipShort.getValue(cfhBuf, off));
-        off += SHORT;
-
-        final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
-        ze.setTime(time);
-        off += WORD;
-
-        ze.setCrc(ZipLong.getValue(cfhBuf, off));
-        off += WORD;
-
-        long size = ZipLong.getValue(cfhBuf, off);
-        if (size < 0) {
-            throw new IOException("broken archive, entry with negative compressed size");
-        }
-        ze.setCompressedSize(size);
-        off += WORD;
-
-        size = ZipLong.getValue(cfhBuf, off);
-        if (size < 0) {
-            throw new IOException("broken archive, entry with negative size");
-        }
-        ze.setSize(size);
-        off += WORD;
-
-        final int fileNameLen = ZipShort.getValue(cfhBuf, off);
-        off += SHORT;
-        if (fileNameLen < 0) {
-            throw new IOException("broken archive, entry with negative fileNameLen");
-        }
-
-        final int extraLen = ZipShort.getValue(cfhBuf, off);
-        off += SHORT;
-        if (extraLen < 0) {
-            throw new IOException("broken archive, entry with negative extraLen");
-        }
-
-        final int commentLen = ZipShort.getValue(cfhBuf, off);
-        off += SHORT;
-        if (commentLen < 0) {
-            throw new IOException("broken archive, entry with negative commentLen");
-        }
-
-        ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
-        off += SHORT;
-
-        ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
-        off += SHORT;
-
-        ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
-        off += WORD;
-
-        final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
-        if (fileName.length < fileNameLen) {
-            throw new EOFException();
-        }
-        ze.setName(entryEncoding.decode(fileName), fileName);
-
-        // LFH offset,
-        ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
-        // data offset will be filled later
-        entries.add(ze);
-
-        final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
-        if (cdExtraData.length < extraLen) {
-            throw new EOFException();
-        }
-        try {
-            ze.setCentralDirectoryExtra(cdExtraData);
-        } catch (RuntimeException ex) {
-            final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
-            z.initCause(ex);
-            throw z;
-        }
-
-        setSizesAndOffsetFromZip64Extra(ze);
-        sanityCheckLFHOffset(ze);
-
-        final byte[] comment = IOUtils.readRange(archive, commentLen);
-        if (comment.length < commentLen) {
-            throw new EOFException();
-        }
-        ze.setComment(entryEncoding.decode(comment));
-
-        if (!hasUTF8Flag && useUnicodeExtraFields) {
-            noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
-        }
-
-        ze.setStreamContiguous(true);
-    }
-
-    private void sanityCheckLFHOffset(final ZipArchiveEntry ze) throws IOException {
-        if (ze.getDiskNumberStart() < 0) {
-            throw new IOException("broken archive, entry with negative disk number");
-        }
-        if (ze.getLocalHeaderOffset() < 0) {
-            throw new IOException("broken archive, entry with negative local file header offset");
-        }
-        if (isSplitZipArchive) {
-            if (ze.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
-                throw new IOException("local file header for " + ze.getName() + " starts on a later disk than central directory");
-            }
-            if (ze.getDiskNumberStart() == centralDirectoryStartDiskNumber
-                && ze.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
-                throw new IOException("local file header for " + ze.getName() + " starts after central directory");
-            }
-        } else if (ze.getLocalHeaderOffset() > centralDirectoryStartOffset) {
-            throw new IOException("local file header for " + ze.getName() + " starts after central directory");
-        }
-    }
-
-    /**
-     * 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 offset of the local file
-     * header.
-     *
-     * <p>Ensures the Zip64 extra either knows both compressed and
-     * uncompressed size or neither of both as the internal logic in
-     * ExtraFieldUtils forces the field to create local header data
-     * even if they are never used - and here a field with only one
-     * size would be invalid.</p>
-     */
-    private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry ze)
-        throws IOException {
-        final ZipExtraField extra =
-            ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
-        if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
-            throw new ZipException("archive contains unparseable zip64 extra field");
-        }
-        final Zip64ExtendedInformationExtraField z64 =
-            (Zip64ExtendedInformationExtraField) extra;
-        if (z64 != null) {
-            final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC;
-            final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC;
-            final boolean hasRelativeHeaderOffset =
-                ze.getLocalHeaderOffset() == ZIP64_MAGIC;
-            final boolean hasDiskStart = ze.getDiskNumberStart() == ZIP64_MAGIC_SHORT;
-            z64.reparseCentralDirectoryData(hasUncompressedSize,
-                                            hasCompressedSize,
-                                            hasRelativeHeaderOffset,
-                                            hasDiskStart);
-
-            if (hasUncompressedSize) {
-                final long size = z64.getSize().getLongValue();
-                if (size < 0) {
-                    throw new IOException("broken archive, entry with negative size");
-                }
-                ze.setSize(size);
-            } else if (hasCompressedSize) {
-                z64.setSize(new ZipEightByteInteger(ze.getSize()));
-            }
-
-            if (hasCompressedSize) {
-                final long size = z64.getCompressedSize().getLongValue();
-                if (size < 0) {
-                    throw new IOException("broken archive, entry with negative compressed size");
-                }
-                ze.setCompressedSize(size);
-            } else if (hasUncompressedSize) {
-                z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
-            }
-
-            if (hasRelativeHeaderOffset) {
-                ze.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
-            }
-
-            if (hasDiskStart) {
-                ze.setDiskNumberStart(z64.getDiskStartNumber().getValue());
-            }
-        }
-    }
-
-    /**
-     * Length of the "End of central directory record" - which is
-     * supposed to be the last structure of the archive - without file
-     * comment.
-     */
-    static final int MIN_EOCD_SIZE =
-        /* end of central dir signature    */ WORD
-        /* number of this disk             */ + SHORT
-        /* number of the disk with the     */
-        /* start of the central directory  */ + SHORT
-        /* total number of entries in      */
-        /* the central dir on this disk    */ + SHORT
-        /* total number of entries in      */
-        /* the central dir                 */ + SHORT
-        /* size of the central directory   */ + WORD
-        /* offset of start of central      */
-        /* directory with respect to       */
-        /* the starting disk number        */ + WORD
-        /* zipfile comment length          */ + SHORT;
-
-    /**
-     * Maximum length of the "End of central directory record" with a
-     * file comment.
-     */
-    private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
-        /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT;
-
-    /**
-     * 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 the "End of central directory
-     * record".
-     */
-    private static final int CFD_LENGTH_OFFSET =
-        /* end of central dir signature    */ WORD
-        /* number of this disk             */ + SHORT
-        /* number of the disk with the     */
-        /* start of the central directory  */ + SHORT
-        /* total number of entries in      */
-        /* the central dir on this disk    */ + SHORT
-        /* total number of entries in      */
-        /* the central dir                 */ + SHORT;
-
-    /**
-     * 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 the "End of central directory
-     * record".
-     */
-    private static final int CFD_DISK_OFFSET =
-            /* end of central dir signature    */ WORD
-            /* number of this disk             */ + SHORT;
-
-    /**
-     * 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 the disk with the start
-     * of the central directory".
-     */
-    private static final int CFD_LOCATOR_RELATIVE_OFFSET =
-            /* total number of entries in      */
-            /* the central dir on this disk    */ + SHORT
-            /* total number of entries in      */
-            /* the central dir                 */ + SHORT
-            /* size of the central directory   */ + WORD;
-
-    /**
-     * 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 all.
-     */
-    private static final int ZIP64_EOCDL_LENGTH =
-        /* zip64 end of central dir locator sig */ WORD
-        /* number of the disk with the start    */
-        /* start of the zip64 end of            */
-        /* central directory                    */ + WORD
-        /* relative offset of the zip64         */
-        /* end of central directory record      */ + DWORD
-        /* total number of disks                */ + WORD;
-
-    /**
-     * 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 to the start of the "Zip64 end of
-     * central directory locator".
-     */
-    private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
-        /* zip64 end of central dir locator sig */ WORD
-        /* number of the disk with the start    */
-        /* start of the zip64 end of            */
-        /* central directory                    */ + WORD;
-
-    /**
-     * 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 of the "Zip64 end of
-     * central directory record".
-     */
-    private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
-        /* zip64 end of central dir        */
-        /* signature                       */ WORD
-        /* size of zip64 end of central    */
-        /* directory record                */ + DWORD
-        /* version made by                 */ + SHORT
-        /* version needed to extract       */ + SHORT
-        /* number of this disk             */ + WORD
-        /* number of the disk with the     */
-        /* start of the central directory  */ + WORD
-        /* total number of entries in the  */
-        /* central directory on this disk  */ + DWORD
-        /* total number of entries in the  */
-        /* central directory               */ + DWORD
-        /* size of the central directory   */ + DWORD;
-
-    /**
-     * 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 start of the "Zip64 end of
-     * central directory record".
-     */
-    private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
-            /* zip64 end of central dir        */
-            /* signature                       */ WORD
-            /* size of zip64 end of central    */
-            /* directory record                */ + DWORD
-            /* version made by                 */ + SHORT
-            /* version needed to extract       */ + SHORT
-            /* number of this disk             */ + WORD;
-
-    /**
-     * 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 "number of the disk
-     * with the start of the central directory".
-     */
-    private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
-            /* total number of entries in the  */
-            /* central directory on this disk  */ DWORD
-            /* total number of entries in the  */
-            /* central directory               */ + DWORD
-            /* size of the central directory   */ + DWORD;
+        if (sig != CFH_SIG && startsWithLocalFileHeader()) {
+            throw new IOException("Central directory is empty, can't expand"
+                                  + " corrupt archive.");
+        }
+
+        while (sig == CFH_SIG) {
+            readCentralDirectoryEntry(noUTF8Flag);
+            wordBbuf.rewind();
+            IOUtils.readFully(archive, wordBbuf);
+            sig = ZipLong.getValue(wordBuf);
+        }
+        return noUTF8Flag;
+    }
 
     /**
      * Searches for either the &quot;Zip64 end of central directory
@@ -1205,6 +1131,47 @@ public class ZipFile implements Closeable {
         }
     }
 
+    /**
+     * Parses the &quot;End of central dir record&quot; and positions
+     * the stream at the first central directory record.
+     *
+     * Expects stream to be positioned at the beginning of the
+     * &quot;End of central dir record&quot;.
+     */
+    private void positionAtCentralDirectory32()
+        throws IOException {
+        long endOfCentralDirectoryRecordOffset = archive.position();
+        if (isSplitZipArchive) {
+            skipBytes(CFD_DISK_OFFSET);
+            shortBbuf.rewind();
+            IOUtils.readFully(archive, shortBbuf);
+            centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);
+
+            skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
+
+            wordBbuf.rewind();
+            IOUtils.readFully(archive, wordBbuf);
+            centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
+            ((ZipSplitReadOnlySeekableByteChannel) archive)
+                .position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
+        } else {
+            skipBytes(CFD_LENGTH_OFFSET);
+            wordBbuf.rewind();
+            IOUtils.readFully(archive, wordBbuf);
+            long centralDirectoryLength = ZipLong.getValue(wordBuf);
+
+            wordBbuf.rewind();
+            IOUtils.readFully(archive, wordBbuf);
+            centralDirectoryStartDiskNumber = 0;
+            centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
+
+            firstLocalFileHeaderOffset = Long.max(
+                    endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset,
+                    0L);
+            archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
+        }
+    }
+
     /**
      * Parses the &quot;Zip64 end of central directory locator&quot;,
      * finds the &quot;Zip64 end of central directory record&quot; using the
@@ -1266,47 +1233,6 @@ public class ZipFile implements Closeable {
         }
     }
 
-    /**
-     * Parses the &quot;End of central dir record&quot; and positions
-     * the stream at the first central directory record.
-     *
-     * Expects stream to be positioned at the beginning of the
-     * &quot;End of central dir record&quot;.
-     */
-    private void positionAtCentralDirectory32()
-        throws IOException {
-        long endOfCentralDirectoryRecordOffset = archive.position();
-        if (isSplitZipArchive) {
-            skipBytes(CFD_DISK_OFFSET);
-            shortBbuf.rewind();
-            IOUtils.readFully(archive, shortBbuf);
-            centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);
-
-            skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
-
-            wordBbuf.rewind();
-            IOUtils.readFully(archive, wordBbuf);
-            centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
-            ((ZipSplitReadOnlySeekableByteChannel) archive)
-                .position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
-        } else {
-            skipBytes(CFD_LENGTH_OFFSET);
-            wordBbuf.rewind();
-            IOUtils.readFully(archive, wordBbuf);
-            long centralDirectoryLength = ZipLong.getValue(wordBuf);
-
-            wordBbuf.rewind();
-            IOUtils.readFully(archive, wordBbuf);
-            centralDirectoryStartDiskNumber = 0;
-            centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
-
-            firstLocalFileHeaderOffset = Long.max(
-                    endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset,
-                    0L);
-            archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
-        }
-    }
-
     /**
      * Searches for the and positions the stream at the start of the
      * &quot;End of central dir record&quot;.
@@ -1321,76 +1247,132 @@ public class ZipFile implements Closeable {
     }
 
     /**
-     * Searches the archive backwards from minDistance to maxDistance
-     * for the given signature, positions the RandomaccessFile right
-     * at the signature if it has been found.
+     * Reads an individual entry of the central directory, creats an
+     * ZipArchiveEntry from it and adds it to the global maps.
+     *
+     * @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 later.  The current entry may be
+     * added to this map.
      */
-    private boolean tryToLocateSignature(final long minDistanceFromEnd,
-                                         final long maxDistanceFromEnd,
-                                         final byte[] sig) throws IOException {
-        boolean found = false;
-        long off = archive.size() - minDistanceFromEnd;
-        final long stopSearching =
-            Math.max(0L, archive.size() - maxDistanceFromEnd);
-        if (off >= 0) {
-            for (; off >= stopSearching; off--) {
-                archive.position(off);
-                try {
-                    wordBbuf.rewind();
-                    IOUtils.readFully(archive, wordBbuf);
-                    wordBbuf.flip();
-                } catch (final EOFException ex) { // NOSONAR
-                    break;
-                }
-                int curr = wordBbuf.get();
-                if (curr == sig[POS_0]) {
-                    curr = wordBbuf.get();
-                    if (curr == sig[POS_1]) {
-                        curr = wordBbuf.get();
-                        if (curr == sig[POS_2]) {
-                            curr = wordBbuf.get();
-                            if (curr == sig[POS_3]) {
-                                found = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
+    private void
+        readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag)
+        throws IOException {
+        cfhBbuf.rewind();
+        IOUtils.readFully(archive, cfhBbuf);
+        int off = 0;
+        final Entry ze = new Entry();
+
+        final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
+        off += SHORT;
+        ze.setVersionMadeBy(versionMadeBy);
+        ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
+
+        ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
+        off += SHORT; // version required
+
+        final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
+        final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
+        final ZipEncoding entryEncoding =
+            hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
+        if (hasUTF8Flag) {
+            ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
         }
-        if (found) {
-            archive.position(off);
+        ze.setGeneralPurposeBit(gpFlag);
+        ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
+
+        off += SHORT;
+
+        //noinspection MagicConstant
+        ze.setMethod(ZipShort.getValue(cfhBuf, off));
+        off += SHORT;
+
+        final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
+        ze.setTime(time);
+        off += WORD;
+
+        ze.setCrc(ZipLong.getValue(cfhBuf, off));
+        off += WORD;
+
+        long size = ZipLong.getValue(cfhBuf, off);
+        if (size < 0) {
+            throw new IOException("broken archive, entry with negative compressed size");
         }
-        return found;
-    }
+        ze.setCompressedSize(size);
+        off += WORD;
 
-    /**
-     * Skips the given number of bytes or throws an EOFException if
-     * skipping failed.
-     */
-    private void skipBytes(final int count) throws IOException {
-        final long currentPosition = archive.position();
-        final long newPosition = currentPosition + count;
-        if (newPosition > archive.size()) {
+        size = ZipLong.getValue(cfhBuf, off);
+        if (size < 0) {
+            throw new IOException("broken archive, entry with negative size");
+        }
+        ze.setSize(size);
+        off += WORD;
+
+        final int fileNameLen = ZipShort.getValue(cfhBuf, off);
+        off += SHORT;
+        if (fileNameLen < 0) {
+            throw new IOException("broken archive, entry with negative fileNameLen");
+        }
+
+        final int extraLen = ZipShort.getValue(cfhBuf, off);
+        off += SHORT;
+        if (extraLen < 0) {
+            throw new IOException("broken archive, entry with negative extraLen");
+        }
+
+        final int commentLen = ZipShort.getValue(cfhBuf, off);
+        off += SHORT;
+        if (commentLen < 0) {
+            throw new IOException("broken archive, entry with negative commentLen");
+        }
+
+        ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
+        off += SHORT;
+
+        ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
+        off += SHORT;
+
+        ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
+        off += WORD;
+
+        final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
+        if (fileName.length < fileNameLen) {
+            throw new EOFException();
+        }
+        ze.setName(entryEncoding.decode(fileName), fileName);
+
+        // LFH offset,
+        ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
+        // data offset will be filled later
+        entries.add(ze);
+
+        final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
+        if (cdExtraData.length < extraLen) {
+            throw new EOFException();
+        }
+        try {
+            ze.setCentralDirectoryExtra(cdExtraData);
+        } catch (RuntimeException ex) {
+            final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
+            z.initCause(ex);
+            throw z;
+        }
+
+        setSizesAndOffsetFromZip64Extra(ze);
+        sanityCheckLFHOffset(ze);
+
+        final byte[] comment = IOUtils.readRange(archive, commentLen);
+        if (comment.length < commentLen) {
             throw new EOFException();
         }
-        archive.position(newPosition);
-    }
+        ze.setComment(entryEncoding.decode(comment));
+
+        if (!hasUTF8Flag && useUnicodeExtraFields) {
+            noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
+        }
 
-    /**
-     * Number of bytes in local file header up to the &quot;length of
-     * file name&quot; entry.
-     */
-    private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
-        /* local file header signature     */ WORD
-        /* version needed to extract       */ + SHORT
-        /* general purpose bit flag        */ + SHORT
-        /* compression method              */ + SHORT
-        /* last mod file time              */ + SHORT
-        /* last mod file date              */ + SHORT
-        /* crc-32                          */ + WORD
-        /* compressed size                 */ + WORD
-        /* uncompressed size               */ + (long) WORD;
+        ze.setStreamContiguous(true);
+    }
 
     /**
      * Walks through all recorded entries and adds the data available
@@ -1430,14 +1412,24 @@ public class ZipFile implements Closeable {
         }
     }
 
-    private void fillNameMap() {
-        entries.forEach(ze -> {
-            // entries is filled in populateFromCentralDirectory and
-            // never modified
-            final String name = ze.getName();
-            LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
-            entriesOfThatName.addLast(ze);
-        });
+    private void sanityCheckLFHOffset(final ZipArchiveEntry ze) throws IOException {
+        if (ze.getDiskNumberStart() < 0) {
+            throw new IOException("broken archive, entry with negative disk number");
+        }
+        if (ze.getLocalHeaderOffset() < 0) {
+            throw new IOException("broken archive, entry with negative local file header offset");
+        }
+        if (isSplitZipArchive) {
+            if (ze.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
+                throw new IOException("local file header for " + ze.getName() + " starts on a later disk than central directory");
+            }
+            if (ze.getDiskNumberStart() == centralDirectoryStartDiskNumber
+                && ze.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
+                throw new IOException("local file header for " + ze.getName() + " starts after central directory");
+            }
+        } else if (ze.getLocalHeaderOffset() > centralDirectoryStartOffset) {
+            throw new IOException("local file header for " + ze.getName() + " starts after central directory");
+        }
     }
 
     private int[] setDataOffset(final ZipArchiveEntry ze) throws IOException {
@@ -1465,126 +1457,133 @@ public class ZipFile implements Closeable {
         return new int[] { fileNameLen, extraFieldLen };
     }
 
-    private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
-        final long s = ze.getDataOffset();
-        if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
-            setDataOffset(ze);
-            return ze.getDataOffset();
-        }
-        return s;
-    }
-
-    /**
-     * Checks whether the archive starts with a LFH.  If it doesn't,
-     * it may be an empty archive.
-     */
-    private boolean startsWithLocalFileHeader() throws IOException {
-        archive.position(firstLocalFileHeaderOffset);
-        wordBbuf.rewind();
-        IOUtils.readFully(archive, wordBbuf);
-        return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
-    }
-
     /**
-     * Creates new BoundedInputStream, according to implementation of
-     * underlying archive channel.
+     * 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 offset of the local file
+     * header.
+     *
+     * <p>Ensures the Zip64 extra either knows both compressed and
+     * uncompressed size or neither of both as the internal logic in
+     * ExtraFieldUtils forces the field to create local header data
+     * even if they are never used - and here a field with only one
+     * size would be invalid.</p>
      */
-    private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
-        if (start < 0 || remaining < 0 || start + remaining < start) {
-            throw new IllegalArgumentException("Corrupted archive, stream boundaries"
-                + " are out of range");
+    private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry ze)
+        throws IOException {
+        final ZipExtraField extra =
+            ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
+        if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
+            throw new ZipException("archive contains unparseable zip64 extra field");
         }
-        return archive instanceof FileChannel ?
-            new BoundedFileChannelInputStream(start, remaining) :
-            new BoundedSeekableByteChannelInputStream(start, remaining, archive);
-    }
+        final Zip64ExtendedInformationExtraField z64 =
+            (Zip64ExtendedInformationExtraField) extra;
+        if (z64 != null) {
+            final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC;
+            final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC;
+            final boolean hasRelativeHeaderOffset =
+                ze.getLocalHeaderOffset() == ZIP64_MAGIC;
+            final boolean hasDiskStart = ze.getDiskNumberStart() == ZIP64_MAGIC_SHORT;
+            z64.reparseCentralDirectoryData(hasUncompressedSize,
+                                            hasCompressedSize,
+                                            hasRelativeHeaderOffset,
+                                            hasDiskStart);
 
-    /**
-     * Lock-free implementation of BoundedInputStream. The
-     * implementation uses positioned reads on the underlying archive
-     * file channel and therefore performs significantly faster in
-     * concurrent environment.
-     */
-    private class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
-        private final FileChannel archive;
+            if (hasUncompressedSize) {
+                final long size = z64.getSize().getLongValue();
+                if (size < 0) {
+                    throw new IOException("broken archive, entry with negative size");
+                }
+                ze.setSize(size);
+            } else if (hasCompressedSize) {
+                z64.setSize(new ZipEightByteInteger(ze.getSize()));
+            }
 
-        BoundedFileChannelInputStream(final long start, final long remaining) {
-            super(start, remaining);
-            archive = (FileChannel) ZipFile.this.archive;
-        }
+            if (hasCompressedSize) {
+                final long size = z64.getCompressedSize().getLongValue();
+                if (size < 0) {
+                    throw new IOException("broken archive, entry with negative compressed size");
+                }
+                ze.setCompressedSize(size);
+            } else if (hasUncompressedSize) {
+                z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
+            }
 
-        @Override
-        protected int read(final long pos, final ByteBuffer buf) throws IOException {
-            final int read = archive.read(buf, pos);
-            buf.flip();
-            return read;
+            if (hasRelativeHeaderOffset) {
+                ze.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
+            }
+
+            if (hasDiskStart) {
+                ze.setDiskNumberStart(z64.getDiskStartNumber().getValue());
+            }
         }
     }
 
-    private static final class NameAndComment {
-        private final byte[] name;
-        private final byte[] comment;
-        private NameAndComment(final byte[] name, final byte[] comment) {
-            this.name = name;
-            this.comment = comment;
+    /**
+     * Skips the given number of bytes or throws an EOFException if
+     * skipping failed.
+     */
+    private void skipBytes(final int count) throws IOException {
+        final long currentPosition = archive.position();
+        final long newPosition = currentPosition + count;
+        if (newPosition > archive.size()) {
+            throw new EOFException();
         }
+        archive.position(newPosition);
     }
 
     /**
-     * Compares two ZipArchiveEntries based on their offset within the archive.
-     *
-     * <p>Won't return any meaningful results if one of the entries
-     * isn't part of the archive at all.</p>
-     *
-     * @since 1.1
+     * Checks whether the archive starts with a LFH.  If it doesn't,
+     * it may be an empty archive.
      */
-    private final Comparator<ZipArchiveEntry> offsetComparator =
-        Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
-            .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);
+    private boolean startsWithLocalFileHeader() throws IOException {
+        archive.position(firstLocalFileHeaderOffset);
+        wordBbuf.rewind();
+        IOUtils.readFully(archive, wordBbuf);
+        return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
+    }
 
     /**
-     * Extends ZipArchiveEntry to store the offset within the archive.
+     * Searches the archive backwards from minDistance to maxDistance
+     * for the given signature, positions the RandomaccessFile right
+     * at the signature if it has been found.
      */
-    private static class Entry extends ZipArchiveEntry {
-
-        Entry() {
-        }
-
-        @Override
-        public int hashCode() {
-            return 3 * super.hashCode()
-                + (int) getLocalHeaderOffset()+(int)(getLocalHeaderOffset()>>32);
-        }
-
-        @Override
-        public boolean equals(final Object other) {
-            if (super.equals(other)) {
-                // super.equals would return false if other were not an Entry
-                final Entry otherEntry = (Entry) other;
-                return getLocalHeaderOffset()
-                        == otherEntry.getLocalHeaderOffset()
-                    && super.getDataOffset()
-                        == otherEntry.getDataOffset()
-                    && super.getDiskNumberStart()
-                        == otherEntry.getDiskNumberStart();
+    private boolean tryToLocateSignature(final long minDistanceFromEnd,
+                                         final long maxDistanceFromEnd,
+                                         final byte[] sig) throws IOException {
+        boolean found = false;
+        long off = archive.size() - minDistanceFromEnd;
+        final long stopSearching =
+            Math.max(0L, archive.size() - maxDistanceFromEnd);
+        if (off >= 0) {
+            for (; off >= stopSearching; off--) {
+                archive.position(off);
+                try {
+                    wordBbuf.rewind();
+                    IOUtils.readFully(archive, wordBbuf);
+                    wordBbuf.flip();
+                } catch (final EOFException ex) { // NOSONAR
+                    break;
+                }
+                int curr = wordBbuf.get();
+                if (curr == sig[POS_0]) {
+                    curr = wordBbuf.get();
+                    if (curr == sig[POS_1]) {
+                        curr = wordBbuf.get();
+                        if (curr == sig[POS_2]) {
+                            curr = wordBbuf.get();
+                            if (curr == sig[POS_3]) {
+                                found = true;
+                                break;
+                            }
+                        }
+                    }
+                }
             }
-            return false;
-        }
-    }
-
-    private static class StoredStatisticsStream extends CountingInputStream implements InputStreamStatistics {
-        StoredStatisticsStream(final InputStream in) {
-            super(in);
-        }
-
-        @Override
-        public long getCompressedCount() {
-            return super.getBytesRead();
         }
-
-        @Override
-        public long getUncompressedCount() {
-            return getCompressedCount();
+        if (found) {
+            archive.position(off);
         }
+        return found;
     }
 }