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 2024/02/10 20:40:27 UTC

(commons-compress) branch master updated: Drop reflection from ExtraFieldUtils static initialization (#480)

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 22a1d2d6a Drop reflection from ExtraFieldUtils static initialization (#480)
22a1d2d6a is described below

commit 22a1d2d6a9c1310141426d1298f3caa57cf8529e
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Sat Feb 10 21:40:23 2024 +0100

    Drop reflection from ExtraFieldUtils static initialization (#480)
    
    * Drop reflection from ExtraFieldUtils
    
    * [tests] fix IT (wrong assert usage)
    
    * [deprecation] deprecate ExtraFieldUtils#register which is not globally usable except in the single case you are in a flat leaf classloader case and you register the extra field support for the lifetime of the app [compress] is deployed into
    
    * [fixes] fix @since tags and javadoc build, plus ensure backward compat with extra fields implementation registration
---
 .../compress/archivers/zip/AsiExtraField.java      |   2 +-
 .../compress/archivers/zip/ExtraFieldUtils.java    |  65 +++++++----
 .../commons/compress/archivers/zip/JarMarker.java  |   2 +-
 .../archivers/zip/X0014_X509Certificates.java      |   4 +-
 .../archivers/zip/X0015_CertificateIdForFile.java  |   4 +-
 .../X0016_CertificateIdForCentralDirectory.java    |   4 +-
 .../zip/X0017_StrongEncryptionHeader.java          |   4 +-
 .../X0019_EncryptionRecipientCertificateList.java  |   4 +-
 .../compress/archivers/zip/X7875_NewUnix.java      |   2 +-
 .../compress/archivers/zip/ZipArchiveEntry.java    | 119 +++++++++++++++++++--
 .../archivers/zip/ZipArchiveInputStream.java       |  16 +++
 .../zip/ZipSplitReadOnlySeekableByteChannel.java   |   1 +
 .../compress/archivers/tar/FileTimesIT.java        |  38 +++----
 13 files changed, 209 insertions(+), 56 deletions(-)

diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/AsiExtraField.java b/src/main/java/org/apache/commons/compress/archivers/zip/AsiExtraField.java
index 856c4c918..57c7a7b8f 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/AsiExtraField.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/AsiExtraField.java
@@ -61,7 +61,7 @@ import java.util.zip.ZipException;
  */
 public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {
 
-    private static final ZipShort HEADER_ID = new ZipShort(0x756E);
+    static final ZipShort HEADER_ID = new ZipShort(0x756E);
     private static final int MIN_SIZE = WORD + SHORT + WORD + SHORT + SHORT;
 
     /**
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
index a90131b19..0a2c84260 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
@@ -16,11 +16,14 @@
  */
 package org.apache.commons.compress.archivers.zip;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 import java.util.zip.ZipException;
 
 /**
@@ -113,24 +116,25 @@ public class ExtraFieldUtils {
     /**
      * Static registry of known extra fields.
      */
-    private static final Map<ZipShort, Class<?>> IMPLEMENTATIONS;
+    private static final Map<ZipShort, Supplier<ZipExtraField>> IMPLEMENTATIONS;
 
     static {
-        IMPLEMENTATIONS = new ConcurrentHashMap<>();
-        register(AsiExtraField.class);
-        register(X5455_ExtendedTimestamp.class);
-        register(X7875_NewUnix.class);
-        register(JarMarker.class);
-        register(UnicodePathExtraField.class);
-        register(UnicodeCommentExtraField.class);
-        register(Zip64ExtendedInformationExtraField.class);
-        register(X000A_NTFS.class);
-        register(X0014_X509Certificates.class);
-        register(X0015_CertificateIdForFile.class);
-        register(X0016_CertificateIdForCentralDirectory.class);
-        register(X0017_StrongEncryptionHeader.class);
-        register(X0019_EncryptionRecipientCertificateList.class);
-        register(ResourceAlignmentExtraField.class);
+        IMPLEMENTATIONS = new ConcurrentHashMap<>(); // it can't be used at runtime by design so no need to be concurrent
+        // IMPLEMENTATIONS = new HashMap<>(); // see register() comment
+        IMPLEMENTATIONS.put(AsiExtraField.HEADER_ID, AsiExtraField::new);
+        IMPLEMENTATIONS.put(X5455_ExtendedTimestamp.HEADER_ID, X5455_ExtendedTimestamp::new);
+        IMPLEMENTATIONS.put(X7875_NewUnix.HEADER_ID, X7875_NewUnix::new);
+        IMPLEMENTATIONS.put(JarMarker.ID, JarMarker::new);
+        IMPLEMENTATIONS.put(UnicodePathExtraField.UPATH_ID, UnicodePathExtraField::new);
+        IMPLEMENTATIONS.put(UnicodeCommentExtraField.UCOM_ID, UnicodeCommentExtraField::new);
+        IMPLEMENTATIONS.put(Zip64ExtendedInformationExtraField.HEADER_ID, Zip64ExtendedInformationExtraField::new);
+        IMPLEMENTATIONS.put(X000A_NTFS.HEADER_ID, X000A_NTFS::new);
+        IMPLEMENTATIONS.put(X0014_X509Certificates.HEADER_ID, X0014_X509Certificates::new);
+        IMPLEMENTATIONS.put(X0015_CertificateIdForFile.HEADER_ID, X0015_CertificateIdForFile::new);
+        IMPLEMENTATIONS.put(X0016_CertificateIdForCentralDirectory.HEADER_ID, X0016_CertificateIdForCentralDirectory::new);
+        IMPLEMENTATIONS.put(X0017_StrongEncryptionHeader.HEADER_ID, X0017_StrongEncryptionHeader::new);
+        IMPLEMENTATIONS.put(X0019_EncryptionRecipientCertificateList.HEADER_ID, X0019_EncryptionRecipientCertificateList::new);
+        IMPLEMENTATIONS.put(ResourceAlignmentExtraField.ID, ResourceAlignmentExtraField::new);
     }
 
     static final ZipExtraField[] EMPTY_ZIP_EXTRA_FIELD_ARRAY = {};
@@ -163,9 +167,9 @@ public class ExtraFieldUtils {
      * @since 1.19
      */
     public static ZipExtraField createExtraFieldNoDefault(final ZipShort headerId) throws InstantiationException, IllegalAccessException {
-        final Class<?> c = IMPLEMENTATIONS.get(headerId);
-        if (c != null) {
-            return (ZipExtraField) c.newInstance();
+        final Supplier<ZipExtraField> provider = IMPLEMENTATIONS.get(headerId);
+        if (provider != null) {
+            return provider.get();
         }
         return null;
     }
@@ -379,11 +383,30 @@ public class ExtraFieldUtils {
      * </p>
      *
      * @param c the class to register
+     *
+     * @deprecated use {@link ZipArchiveInputStream#setExtraFieldSupport} instead
+     *             to not leak instances between archives and applications.
      */
+    @Deprecated // note: when dropping update registration to move to a HashMap (static init)
     public static void register(final Class<?> c) {
         try {
-            final ZipExtraField ze = (ZipExtraField) c.getConstructor().newInstance();
-            IMPLEMENTATIONS.put(ze.getHeaderId(), c);
+            final Constructor<? extends ZipExtraField> constructor = c
+                    .asSubclass(ZipExtraField.class)
+                    .getConstructor();
+            final ZipExtraField ze = constructor.newInstance();
+            IMPLEMENTATIONS.put(ze.getHeaderId(), () -> {
+                try {
+                    return constructor.newInstance();
+                } catch (final InstantiationException | IllegalAccessException e) {
+                    throw new IllegalStateException(e);
+                } catch (final InvocationTargetException e) {
+                    final Throwable cause = e.getTargetException();
+                    if (cause instanceof RuntimeException) {
+                        throw (RuntimeException) cause;
+                    }
+                    throw new IllegalStateException(cause);
+                }
+            });
         } catch (final ClassCastException cc) { // NOSONAR
             throw new IllegalArgumentException(c + " doesn't implement ZipExtraField"); // NOSONAR
         } catch (final InstantiationException ie) { // NOSONAR
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/JarMarker.java b/src/main/java/org/apache/commons/compress/archivers/zip/JarMarker.java
index 031dc00e1..093a940e9 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/JarMarker.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/JarMarker.java
@@ -27,7 +27,7 @@ import org.apache.commons.compress.utils.ByteUtils;
  */
 public final class JarMarker implements ZipExtraField {
 
-    private static final ZipShort ID = new ZipShort(0xCAFE);
+    static final ZipShort ID = new ZipShort(0xCAFE);
     private static final ZipShort NULL = new ZipShort(0);
     private static final JarMarker DEFAULT = new JarMarker();
 
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/X0014_X509Certificates.java b/src/main/java/org/apache/commons/compress/archivers/zip/X0014_X509Certificates.java
index aa4b985d5..93a690d05 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X0014_X509Certificates.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X0014_X509Certificates.java
@@ -44,8 +44,10 @@ package org.apache.commons.compress.archivers.zip;
  */
 public class X0014_X509Certificates extends PKWareExtraHeader {
 
+    static final ZipShort HEADER_ID = new ZipShort(0x0014);
+
     public X0014_X509Certificates() {
-        super(new ZipShort(0x0014));
+        super(HEADER_ID);
     }
 
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/X0015_CertificateIdForFile.java b/src/main/java/org/apache/commons/compress/archivers/zip/X0015_CertificateIdForFile.java
index 79c51925e..bd01381aa 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X0015_CertificateIdForFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X0015_CertificateIdForFile.java
@@ -47,12 +47,14 @@ import java.util.zip.ZipException;
  */
 public class X0015_CertificateIdForFile extends PKWareExtraHeader {
 
+    static final ZipShort HEADER_ID = new ZipShort(0x0015);
+
     private int rcount;
 
     private HashAlgorithm hashAlg;
 
     public X0015_CertificateIdForFile() {
-        super(new ZipShort(0x0015));
+        super(HEADER_ID);
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/X0016_CertificateIdForCentralDirectory.java b/src/main/java/org/apache/commons/compress/archivers/zip/X0016_CertificateIdForCentralDirectory.java
index 4af4bef54..43bd0da45 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X0016_CertificateIdForCentralDirectory.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X0016_CertificateIdForCentralDirectory.java
@@ -48,12 +48,14 @@ import java.util.zip.ZipException;
  */
 public class X0016_CertificateIdForCentralDirectory extends PKWareExtraHeader {
 
+    static final ZipShort HEADER_ID = new ZipShort(0x0016);
+
     private int rcount;
 
     private HashAlgorithm hashAlg;
 
     public X0016_CertificateIdForCentralDirectory() {
-        super(new ZipShort(0x0016));
+        super(HEADER_ID);
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java b/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
index fca05a6ac..d579949bd 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
@@ -257,6 +257,8 @@ import java.util.zip.ZipException;
  */
 public class X0017_StrongEncryptionHeader extends PKWareExtraHeader {
 
+    static final ZipShort HEADER_ID = new ZipShort(0x0017);
+
     private int format; // TODO written but not read
 
     private EncryptionAlgorithm algId;
@@ -279,7 +281,7 @@ public class X0017_StrongEncryptionHeader extends PKWareExtraHeader {
     private byte[] vCRC32;
 
     public X0017_StrongEncryptionHeader() {
-        super(new ZipShort(0x0017));
+        super(HEADER_ID);
     }
 
     private void assertDynamicLengthFits(final String what, final int dynamicLength, final int prefixLength, final int length) throws ZipException {
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/X0019_EncryptionRecipientCertificateList.java b/src/main/java/org/apache/commons/compress/archivers/zip/X0019_EncryptionRecipientCertificateList.java
index a3e5af807..9922a5847 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X0019_EncryptionRecipientCertificateList.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X0019_EncryptionRecipientCertificateList.java
@@ -50,8 +50,10 @@ package org.apache.commons.compress.archivers.zip;
  */
 public class X0019_EncryptionRecipientCertificateList extends PKWareExtraHeader {
 
+    static final ZipShort HEADER_ID = new ZipShort(0x0019);
+
     public X0019_EncryptionRecipientCertificateList() {
-        super(new ZipShort(0x0019));
+        super(HEADER_ID);
     }
 
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java b/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java
index e36b187bc..fee17e57c 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java
@@ -57,7 +57,7 @@ import org.apache.commons.compress.utils.ByteUtils;
  * @since 1.5
  */
 public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
-    private static final ZipShort HEADER_ID = new ZipShort(0x7875);
+    static final ZipShort HEADER_ID = new ZipShort(0x7875);
     private static final ZipShort ZERO = new ZipShort(0);
     private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
     private static final long serialVersionUID = 1L;
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
index 125437d04..8ddded61c 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java
@@ -30,6 +30,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 
@@ -265,6 +266,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
     private long dataOffset = OFFSET_UNKNOWN;
     private boolean isStreamContiguous;
     private NameSource nameSource = NameSource.NAME;
+    private final Function<ZipShort, ZipExtraField> extraFieldFactory;
 
     private CommentSource commentSource = CommentSource.COMMENT;
 
@@ -288,11 +290,13 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
      * will be stripped from the entry name.
      * </p>
      *
+     * @param extraFieldFactory custom lookup factory for extra fields or null
      * @param inputFile file to create the entry from
      * @param entryName name of the entry
+     * @since 1.26.0
      */
-    public ZipArchiveEntry(final File inputFile, final String entryName) {
-        this(inputFile.isDirectory() && !entryName.endsWith("/") ? entryName + "/" : entryName);
+    ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final File inputFile, final String entryName) {
+        this(extraFieldFactory, inputFile.isDirectory() && !entryName.endsWith("/") ? entryName + "/" : entryName);
         try {
             setAttributes(inputFile.toPath());
         } catch (final IOException e) { // NOSONAR
@@ -303,6 +307,21 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
         }
     }
 
+    /**
+     * Creates a new ZIP entry taking some information from the given file and using the provided name.
+     *
+     * <p>
+     * The name will be adjusted to end with a forward slash "/" if the file is a directory. If the file is not a directory a potential trailing forward slash
+     * will be stripped from the entry name.
+     * </p>
+     *
+     * @param inputFile file to create the entry from
+     * @param entryName name of the entry
+     */
+    public ZipArchiveEntry(final File inputFile, final String entryName) {
+        this(null, inputFile, entryName);
+    }
+
     /**
      * Creates a new ZIP entry with fields taken from the specified ZIP entry.
      *
@@ -314,11 +333,29 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
      * @throws ZipException on error
      */
     public ZipArchiveEntry(final java.util.zip.ZipEntry entry) throws ZipException {
+        this(null, entry);
+    }
+
+    /**
+     * Creates a new ZIP entry with fields taken from the specified ZIP entry.
+     *
+     * <p>
+     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
+     * </p>
+     *
+     * @param extraFieldFactory the extra field lookup factory.
+     * @param entry the entry to get fields from
+     * @throws ZipException on error
+     * @since 1.26.0
+     */
+    ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory,
+                    final java.util.zip.ZipEntry entry) throws ZipException {
         super(entry);
+        this.extraFieldFactory = extraFieldFactory;
         setName(entry.getName());
         final byte[] extra = entry.getExtra();
         if (extra != null) {
-            setExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT));
+            setExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT));
         } else {
             // initializes extra data to an empty byte array
             setExtra();
@@ -342,7 +379,27 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
      * @since 1.21
      */
     public ZipArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
-        this(Files.isDirectory(inputPath, options) && !entryName.endsWith("/") ? entryName + "/" : entryName);
+        this(null, inputPath, entryName, options);
+    }
+
+    /**
+     * Creates a new ZIP entry taking some information from the given path and using the provided name.
+     *
+     * <p>
+     * The name will be adjusted to end with a forward slash "/" if the file is a directory. If the file is not a directory a potential trailing forward slash
+     * will be stripped from the entry name.
+     * </p>
+     *
+     * @param extraFieldFactory custom lookup factory for extra fields or null
+     * @param inputPath path to create the entry from.
+     * @param entryName name of the entry.
+     * @param options   options indicating how symbolic links are handled.
+     * @throws IOException if an I/O error occurs.
+     * @since 1.26.0
+     */
+    ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory,
+                    final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
+        this(extraFieldFactory, Files.isDirectory(inputPath, options) && !entryName.endsWith("/") ? entryName + "/" : entryName);
         setAttributes(inputPath, options);
     }
 
@@ -354,9 +411,27 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
      * </p>
      *
      * @param name the name of the entry
+     * @since 1.26.0
      */
     public ZipArchiveEntry(final String name) {
+        this((Function<ZipShort, ZipExtraField>) null, name);
+    }
+
+    /**
+     * Creates a new ZIP entry with the specified name.
+     *
+     * <p>
+     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
+     * </p>
+     *
+     * @param extraFieldFactory custom lookup factory for extra fields or null
+     * @param name the name of the entry
+     * @since 1.26.0
+     */
+    ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory,
+                    final String name) {
         super(name);
+        this.extraFieldFactory = extraFieldFactory;
         setName(name);
     }
 
@@ -642,9 +717,9 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
             return getExtraFields(false);
         }
         final byte[] local = getExtra();
-        final List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(local, true, parsingBehavior)));
+        final List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(parseExtraFields(local, true, parsingBehavior)));
         final byte[] central = getCentralDirectoryExtra();
-        final List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(central, false, parsingBehavior)));
+        final List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(parseExtraFields(central, false, parsingBehavior)));
         final List<ZipExtraField> merged = new ArrayList<>();
         for (final ZipExtraField l : localFields) {
             ZipExtraField c;
@@ -1079,7 +1154,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
      */
     public void setCentralDirectoryExtra(final byte[] b) {
         try {
-            mergeExtraFields(ExtraFieldUtils.parse(b, false, ExtraFieldParsingMode.BEST_EFFORT), false);
+            mergeExtraFields(parseExtraFields(b, false, ExtraFieldParsingMode.BEST_EFFORT), false);
         } catch (final ZipException e) {
             // actually this is not possible as of Commons Compress 1.19
             throw new IllegalArgumentException(e.getMessage(), e); // NOSONAR
@@ -1157,7 +1232,7 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
     @Override
     public void setExtra(final byte[] extra) throws RuntimeException {
         try {
-            mergeExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT), true);
+            mergeExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT), true);
         } catch (final ZipException e) {
             // actually this is not possible as of Commons Compress 1.1
             throw new IllegalArgumentException("Error parsing extra fields for entry: " // NOSONAR
@@ -1456,4 +1531,32 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry implements ArchiveEn
             }
         }
     }
+
+    private ZipExtraField[] parseExtraFields(final byte[] data, final boolean local,
+                                             final ExtraFieldParsingBehavior parsingBehavior) throws ZipException {
+        if (extraFieldFactory != null) {
+            return ExtraFieldUtils.parse(data, local, new ExtraFieldParsingBehavior() {
+                @Override
+                public ZipExtraField createExtraField(final ZipShort headerId) throws ZipException, InstantiationException, IllegalAccessException {
+                    final ZipExtraField field = extraFieldFactory.apply(headerId);
+                    return field == null ? parsingBehavior.createExtraField(headerId) : field;
+                }
+
+                @Override
+                public ZipExtraField fill(final ZipExtraField field,
+                                          final byte[] data, final int off, final int len,
+                                          final boolean local) throws ZipException {
+                    return parsingBehavior.fill(field, data, off, len, local);
+                }
+
+                @Override
+                public ZipExtraField onUnparseableExtraField(
+                        final byte[] data, final int off, final int len,
+                        final boolean local, final int claimedLength) throws ZipException {
+                    return parsingBehavior.onUnparseableExtraField(data, off, len, local, claimedLength);
+                }
+            });
+        }
+        return ExtraFieldUtils.parse(data, local, parsingBehavior);
+    }
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
index 7b017bdee..d3b493fd6 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
@@ -33,6 +33,7 @@ import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.zip.CRC32;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
@@ -292,6 +293,11 @@ public class ZipArchiveInputStream extends ArchiveInputStream<ZipArchiveEntry> i
 
     private int entriesRead;
 
+    /**
+     * The factory for extra fields or null.
+     */
+    private Function<ZipShort, ZipExtraField> extraFieldSupport;
+
     /**
      * Constructs an instance using UTF-8 encoding
      *
@@ -360,6 +366,16 @@ public class ZipArchiveInputStream extends ArchiveInputStream<ZipArchiveEntry> i
         buf.limit(0);
     }
 
+    /**
+     * Enable custom extra fields factory.
+     * @param extraFieldSupport the lookup function based on extra field header id.
+     * @return the archive.
+     */
+    public ZipArchiveInputStream setExtraFieldSupport(final Function<ZipShort, ZipExtraField> extraFieldSupport) {
+        this.extraFieldSupport = extraFieldSupport;
+        return this;
+    }
+
     /**
      * Checks whether the current buffer contains the signature of a &quot;data descriptor&quot;, &quot;local file header&quot; or &quot;central directory
      * entry&quot;.
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java
index 29815d25a..43ea678d8 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java
@@ -197,6 +197,7 @@ public class ZipSplitReadOnlySeekableByteChannel extends MultiReadOnlySeekableBy
      *
      * @param paths the file paths to concatenate, note that the LAST FILE of files should be the LAST SEGMENT(.zip) and these files should be added in correct
      *              order (e.g.: .z01, .z02... .z99, .zip)
+     * @param openOptions the options to open paths (shared by all paths).
      * @return SeekableByteChannel that concatenates all provided files
      * @throws NullPointerException if files is null
      * @throws IOException          if opening a channel for one of the files fails
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/FileTimesIT.java b/src/test/java/org/apache/commons/compress/archivers/tar/FileTimesIT.java
index ed8d6c0ea..d5414a62a 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/FileTimesIT.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/FileTimesIT.java
@@ -52,7 +52,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-17T00:24:44.147126600Z"), e.getLastModifiedTime(), "mtime");
@@ -62,7 +62,7 @@ public class FileTimesIT extends AbstractTest {
             e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertEquals(toFileTime("2022-03-17T00:38:20.470751500Z"), e.getLastModifiedTime(), "mtime");
@@ -81,7 +81,7 @@ public class FileTimesIT extends AbstractTest {
                 TarArchiveInputStream tin = new TarArchiveInputStream(in)) {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-17T00:24:44.147126600Z"), e.getLastModifiedTime(), "mtime");
@@ -90,7 +90,7 @@ public class FileTimesIT extends AbstractTest {
             assertNull(e.getCreationTime(), "birthtime");
             assertGlobalHeaders(e);
             e = tin.getNextTarEntry();
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertEquals(toFileTime("2022-03-17T00:38:20.470751500Z"), e.getLastModifiedTime(), "mtime");
@@ -130,7 +130,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test-times.txt", e.getName());
+            assertEquals("test-times.txt", e.getName());
             assertEquals(toFileTime("2022-03-14T01:25:03Z"), e.getLastModifiedTime(), "mtime");
             assertNull(e.getLastAccessTime(), "atime");
             assertNull(e.getStatusChangeTime(), "ctime");
@@ -138,7 +138,7 @@ public class FileTimesIT extends AbstractTest {
             e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test-times.txt", e.getName());
+            assertEquals("test-times.txt", e.getName());
             assertEquals(toFileTime("2022-03-14T03:17:05Z"), e.getLastModifiedTime(), "mtime");
             assertEquals(toFileTime("2022-03-14T03:17:10Z"), e.getLastAccessTime(), "atime");
             assertEquals(toFileTime("2022-03-14T03:17:10Z"), e.getStatusChangeTime(), "ctime");
@@ -210,7 +210,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test-times.txt", e.getName());
+            assertEquals("test-times.txt", e.getName());
             assertEquals(toFileTime("2022-03-14T01:25:03Z"), e.getLastModifiedTime(), "mtime");
             assertNull(e.getLastAccessTime(), "atime");
             assertNull(e.getStatusChangeTime(), "ctime");
@@ -218,7 +218,7 @@ public class FileTimesIT extends AbstractTest {
             e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test-times.txt", e.getName());
+            assertEquals("test-times.txt", e.getName());
             assertEquals(toFileTime("2022-03-14T03:17:05Z"), e.getLastModifiedTime(), "mtime");
             assertEquals(toFileTime("2022-03-14T03:17:06Z"), e.getLastAccessTime(), "atime");
             assertEquals(toFileTime("2022-03-14T03:17:05Z"), e.getStatusChangeTime(), "ctime");
@@ -237,7 +237,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-17T00:24:44.147126600Z"), e.getLastModifiedTime(), "mtime");
@@ -247,7 +247,7 @@ public class FileTimesIT extends AbstractTest {
             e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertEquals(toFileTime("2022-03-17T00:38:20.470751500Z"), e.getLastModifiedTime(), "mtime");
@@ -286,7 +286,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-16T10:19:43.382883700Z"), e.getLastModifiedTime(), "mtime");
@@ -296,7 +296,7 @@ public class FileTimesIT extends AbstractTest {
             e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertEquals(toFileTime("2022-03-16T10:21:00.249238500Z"), e.getLastModifiedTime(), "mtime");
@@ -334,7 +334,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-17T00:24:44Z"), e.getLastModifiedTime(), "mtime");
@@ -343,7 +343,7 @@ public class FileTimesIT extends AbstractTest {
             assertNull(e.getCreationTime(), "birthtime");
             e = tin.getNextTarEntry();
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertEquals(toFileTime("2022-03-17T00:38:20Z"), e.getLastModifiedTime(), "mtime");
@@ -414,7 +414,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-17T00:24:44Z"), e.getLastModifiedTime(), "mtime");
@@ -422,7 +422,7 @@ public class FileTimesIT extends AbstractTest {
             assertEquals(toFileTime("2022-03-17T00:24:44Z"), e.getStatusChangeTime(), "ctime");
             assertNull(e.getCreationTime(), "birthtime");
             e = tin.getNextTarEntry();
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertTrue(e.getExtraPaxHeaders().isEmpty());
@@ -443,7 +443,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test-times.txt", e.getName());
+            assertEquals("test-times.txt", e.getName());
             assertEquals(toFileTime("2022-03-14T04:03:29Z"), e.getLastModifiedTime(), "mtime");
             assertEquals(toFileTime("2022-03-14T04:03:29Z"), e.getLastAccessTime(), "atime");
             assertEquals(toFileTime("2022-03-14T04:03:29Z"), e.getStatusChangeTime(), "ctime");
@@ -486,7 +486,7 @@ public class FileTimesIT extends AbstractTest {
             TarArchiveEntry e = tin.getNextTarEntry();
             assertNotNull(e);
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/", e.getName());
+            assertEquals("test/", e.getName());
             assertEquals(TarConstants.LF_DIR, e.getLinkFlag());
             assertTrue(e.isDirectory());
             assertEquals(toFileTime("2022-03-17T00:24:44.147126600Z"), e.getLastModifiedTime(), "mtime");
@@ -495,7 +495,7 @@ public class FileTimesIT extends AbstractTest {
             assertNull(e.getCreationTime(), "birthtime");
             e = tin.getNextTarEntry();
             assertTrue(e.getExtraPaxHeaders().isEmpty());
-            assertEquals("name", "test/test-times.txt", e.getName());
+            assertEquals("test/test-times.txt", e.getName());
             assertEquals(TarConstants.LF_NORMAL, e.getLinkFlag());
             assertTrue(e.isFile());
             assertEquals(toFileTime("2022-03-17T00:38:20.470751500Z"), e.getLastModifiedTime(), "mtime");