    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.archivers.zip;
    020
    021import java.io.File;
    022import java.io.IOException;
    023import java.nio.file.Files;
    024import java.nio.file.LinkOption;
    025import java.nio.file.Path;
    026import java.nio.file.attribute.BasicFileAttributes;
    027import java.nio.file.attribute.FileTime;
    028import java.util.ArrayList;
    029import java.util.Arrays;
    030import java.util.Date;
    031import java.util.LinkedList;
    032import java.util.List;
    033import java.util.NoSuchElementException;
    034import java.util.Objects;
    035import java.util.function.Function;
    036import java.util.zip.ZipEntry;
    037import java.util.zip.ZipException;
    038
    039import org.apache.commons.compress.archivers.ArchiveEntry;
    040import org.apache.commons.compress.archivers.EntryStreamOffsets;
    041import org.apache.commons.compress.utils.ByteUtils;
    042import org.apache.commons.io.file.attribute.FileTimes;
    043import org.apache.commons.lang3.ArrayUtils;
    044
    045/**
    046 * Extension that adds better handling of extra fields and provides access to the internal and external file attributes.
    047 *
    048 * <p>
    049 * The extra data is expected to follow the recommendation of <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:
    050 * </p>
    051 * <ul>
    052 * <li>the extra byte array consists of a sequence of extra fields</li>
    053 * <li>each extra fields starts by a two byte header id followed by a two byte sequence holding the length of the remainder of data.</li>
    054 * </ul>
    055 *
    056 * <p>
    057 * Any extra data that cannot be parsed by the rules above will be consumed as "unparseable" extra data and treated differently by the methods of this class.
    058 * Versions prior to Apache Commons Compress 1.1 would have thrown an exception if any attempt was made to read or write extra data not conforming to the
    059 * recommendation.
    060 * </p>
    061 *
    062 * @NotThreadSafe
    063 */
    064public class ZipArchiveEntry extends ZipEntry implements ArchiveEntry, EntryStreamOffsets {
    065
    066    /**
    067     * Enumerates how the comment of this entry has been determined.
    068     *
    069     * @since 1.16
    070     */
    071    public enum CommentSource {
    072
    073        /**
    074         * The comment has been read from the archive using the encoding of the archive specified when creating the {@link ZipArchiveInputStream} or
    075         * {@link ZipFile} (defaults to the platform's default encoding).
    076         */
    077        COMMENT,
    078
    079        /**
    080         * The comment has been read from an {@link UnicodeCommentExtraField Unicode Extra Field}.
    081         */
    082        UNICODE_EXTRA_FIELD
    083    }
    084
    085    /**
    086     * Enumerates how to try to parse the extra fields.
    087     *
    088     * <p>
    089     * Configures the behavior for:
    090     * </p>
    091     * <ul>
    092     * <li>What shall happen if the extra field content doesn't follow the recommended pattern of two-byte id followed by a two-byte length?</li>
    093     * <li>What shall happen if an extra field is generally supported by Commons Compress but its content cannot be parsed correctly? This may for example
    094     * happen if the archive is corrupt, it triggers a bug in Commons Compress or the extra field uses a version not (yet) supported by Commons Compress.</li>
    095     * </ul>
    096     *
    097     * @since 1.19
    098     */
    099    public enum ExtraFieldParsingMode implements ExtraFieldParsingBehavior {
    100
    101        /**
    102         * Try to parse as many extra fields as possible and wrap unknown extra fields as well as supported extra fields that cannot be parsed in
    103         * {@link UnrecognizedExtraField}.
    104         *
    105         * <p>
    106         * Wrap extra data that doesn't follow the recommended pattern in an {@link UnparseableExtraFieldData} instance.
    107         * </p>
    108         *
    109         * <p>
    110         * This is the default behavior starting with Commons Compress 1.19.
    111         * </p>
    112         */
    113        BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ) {
    114            @Override
    115            public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) {
    116                return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
    117            }
    118        },
    119
    120        /**
    121         * Try to parse as many extra fields as possible and wrap unknown extra fields in {@link UnrecognizedExtraField}.
    122         *
    123         * <p>
    124         * Wrap extra data that doesn't follow the recommended pattern in an {@link UnparseableExtraFieldData} instance.
    125         * </p>
    126         *
    127         * <p>
    128         * Throw an exception if an extra field that is generally supported cannot be parsed.
    129         * </p>
    130         *
    131         * <p>
    132         * This used to be the default behavior prior to Commons Compress 1.19.
    133         * </p>
    134         */
    135        STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ),
    136
    137        /**
    138         * Try to parse as many extra fields as possible and wrap unknown extra fields as well as supported extra fields that cannot be parsed in
    139         * {@link UnrecognizedExtraField}.
    140         *
    141         * <p>
    142         * Ignore extra data that doesn't follow the recommended pattern.
    143         * </p>
    144         */
    145        ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP) {
    146            @Override
    147            public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) {
    148                return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
    149            }
    150        },
    151
    152        /**
    153         * Try to parse as many extra fields as possible and wrap unknown extra fields in {@link UnrecognizedExtraField}.
    154         *
    155         * <p>
    156         * Ignore extra data that doesn't follow the recommended pattern.
    157         * </p>
    158         *
    159         * <p>
    160         * Throw an exception if an extra field that is generally supported cannot be parsed.
    161         * </p>
    162         */
    163        ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP),
    164
    165        /**
    166         * Throw an exception if any of the recognized extra fields cannot be parsed or any extra field violates the recommended pattern.
    167         */
    168        DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW);
    169
    170        private static ZipExtraField fillAndMakeUnrecognizedOnError(final ZipExtraField field, final byte[] data, final int off, final int len,
    171                final boolean local) {
    172            try {
    173                return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
    174            } catch (final ZipException ex) {
    175                final UnrecognizedExtraField u = new UnrecognizedExtraField();
    176                u.setHeaderId(field.getHeaderId());
    177                if (local) {
    178                    u.setLocalFileDataData(Arrays.copyOfRange(data, off, off + len));
    179                } else {
    180                    u.setCentralDirectoryData(Arrays.copyOfRange(data, off, off + len));
    181                }
    182                return u;
    183            }
    184        }
    185
    186        private final ExtraFieldUtils.UnparseableExtraField onUnparseableData;
    187
    188        ExtraFieldParsingMode(final ExtraFieldUtils.UnparseableExtraField onUnparseableData) {
    189            this.onUnparseableData = onUnparseableData;
    190        }
    191
    192        @Override
    193        public ZipExtraField createExtraField(final ZipShort headerId) {
    194            return ExtraFieldUtils.createExtraField(headerId);
    195        }
    196
    197        @Override
    198        public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException {
    199            return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
    200        }
    201
    202        @Override
    203        public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength)
    204                throws ZipException {
    205            return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength);
    206        }
    207    }
    208
    209    /**
    210     * Indicates how the name of this entry has been determined.
    211     *
    212     * @since 1.16
    213     */
    214    public enum NameSource {
    215
    216        /**
    217         * The name has been read from the archive using the encoding of the archive specified when creating the {@link ZipArchiveInputStream} or
    218         * {@link ZipFile} (defaults to the platform's default encoding).
    219         */
    220        NAME,
    221
    222        /**
    223         * The name has been read from the archive and the archive specified the EFS flag which indicates the name has been encoded as UTF-8.
    224         */
    225        NAME_WITH_EFS_FLAG,
    226
    227        /**
    228         * The name has been read from an {@link UnicodePathExtraField Unicode Extra Field}.
    229         */
    230        UNICODE_EXTRA_FIELD
    231    }
    232
    233    private static final String ZIP_DIR_SEP = "/";
    234
    235    static final ZipArchiveEntry[] EMPTY_ARRAY = {};
    236    static LinkedList<ZipArchiveEntry> EMPTY_LINKED_LIST = new LinkedList<>();
    237
    238    /**
    239     * Platform is UNIX.
    240     */
    241    public static final int PLATFORM_UNIX = 3;
    242
    243    /**
    244     * Platform is FAT.
    245     */
    246    public static final int PLATFORM_FAT = 0;
    247
    248    /**
    249     * Platform is unknown.
    250     */
    251    public static final int CRC_UNKNOWN = -1;
    252
    253    private static final int SHORT_MASK = 0xFFFF;
    254
    255    private static final int SHORT_SHIFT = 16;
    256
    257    private static boolean canConvertToInfoZipExtendedTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) {
    258        return FileTimes.isUnixTime(lastModifiedTime) && FileTimes.isUnixTime(lastAccessTime) && FileTimes.isUnixTime(creationTime);
    259    }
    260
    261    private static boolean isDirectoryEntryName(final String entryName) {
    262        return entryName.endsWith(ZIP_DIR_SEP);
    263    }
    264
    265    private static String toDirectoryEntryName(final String entryName) {
    266        return isDirectoryEntryName(entryName) ? entryName : entryName + ZIP_DIR_SEP;
    267    }
    268
    269    private static String toEntryName(final File inputFile, final String entryName) {
    270        return inputFile.isDirectory() ? toDirectoryEntryName(entryName) : entryName;
    271    }
    272
    273    private static String toEntryName(final Path inputPath, final String entryName, final LinkOption... options) {
    274        return Files.isDirectory(inputPath, options) ? toDirectoryEntryName(entryName) : entryName;
    275    }
    276
    277    /**
    278     * The {@link ZipEntry} base class only supports the compression methods STORED and DEFLATED. We override the field so that any compression
    279     * methods can be used.
    280     * <p>
    281     * The default value -1 means that the method has not been specified.
    282     * </p>
    283     * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93">COMPRESS-93</a>
    284     */
    285    private int method = ZipMethod.UNKNOWN_CODE;
    286
    287    /**
    288     * The {@link ZipEntry#setSize} method in the base class throws an IllegalArgumentException if the size is bigger than 2GB for Java versions
    289     * &lt; 7 and even in Java 7+ if the implementation in java.util.zip doesn't support Zip64 itself (it is an optional feature).
    290     * <p>
    291     * We need to keep our own size information for Zip64 support.
    292     * </p>
    293     */
    294    private long size = SIZE_UNKNOWN;
    295    private int internalAttributes;
    296    private int versionRequired;
    297    private int versionMadeBy;
    298    private int platform = PLATFORM_FAT;
    299    private int rawFlag;
    300    private long externalAttributes;
    301    private int alignment;
    302    private ZipExtraField[] extraFields;
    303    private UnparseableExtraFieldData unparseableExtra;
    304    private String name;
    305    private byte[] rawName;
    306    private GeneralPurposeBit generalPurposeBit = new GeneralPurposeBit();
    307    private long localHeaderOffset = OFFSET_UNKNOWN;
    308    private long dataOffset = OFFSET_UNKNOWN;
    309    private boolean isStreamContiguous;
    310
    311    private NameSource nameSource = NameSource.NAME;
    312
    313    private final Function<ZipShort, ZipExtraField> extraFieldFactory;
    314
    315    private CommentSource commentSource = CommentSource.COMMENT;
    316
    317    private long diskNumberStart;
    318
    319    private boolean lastModifiedDateSet;
    320
    321    private long time = -1;
    322
    323    /**
    324     */
    325    protected ZipArchiveEntry() {
    326        this("");
    327    }
    328
    329    /**
    330     * Creates a new ZIP entry taking some information from the given file and using the provided name.
    331     *
    332     * <p>
    333     * 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
    334     * will be stripped from the entry name.
    335     * </p>
    336     *
    337     * @param inputFile file to create the entry from
    338     * @param entryName name of the entry
    339     */
    340    public ZipArchiveEntry(final File inputFile, final String entryName) {
    341        this(null, inputFile, entryName);
    342    }
    343
    344    /**
    345     * Creates a new ZIP entry taking some information from the given file and using the provided name.
    346     *
    347     * <p>
    348     * 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
    349     * will be stripped from the entry name.
    350     * </p>
    351     *
    352     * @param extraFieldFactory custom lookup factory for extra fields or null
    353     * @param inputFile file to create the entry from
    354     * @param entryName name of the entry
    355     */
    356    private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final File inputFile, final String entryName) {
    357        this(extraFieldFactory, toEntryName(inputFile, entryName));
    358        try {
    359            setAttributes(inputFile.toPath());
    360        } catch (final IOException e) { // NOSONAR
    361            if (inputFile.isFile()) {
    362                setSize(inputFile.length());
    363            }
    364            setTime(inputFile.lastModified());
    365        }
    366    }
    367
    368    /**
    369     * Creates a new ZIP entry taking some information from the given path and using the provided name.
    370     *
    371     * <p>
    372     * 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
    373     * will be stripped from the entry name.
    374     * </p>
    375     *
    376     * @param extraFieldFactory custom lookup factory for extra fields or null
    377     * @param inputPath path to create the entry from.
    378     * @param entryName name of the entry.
    379     * @param options   options indicating how symbolic links are handled.
    380     * @throws IOException if an I/O error occurs.
    381     */
    382    private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final Path inputPath, final String entryName,
    383            final LinkOption... options) throws IOException {
    384        this(extraFieldFactory, toEntryName(inputPath, entryName, options));
    385        setAttributes(inputPath, options);
    386    }
    387
    388    /**
    389     * Creates a new ZIP entry with the specified name.
    390     *
    391     * <p>
    392     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
    393     * </p>
    394     *
    395     * @param extraFieldFactory custom lookup factory for extra fields or null
    396     * @param name the name of the entry
    397     */
    398    private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final String name) {
    399        super(name);
    400        this.extraFieldFactory = extraFieldFactory;
    401        setName(name);
    402    }
    403
    404    /**
    405     * Creates a new ZIP entry with fields taken from the specified ZIP entry.
    406     *
    407     * <p>
    408     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
    409     * </p>
    410     *
    411     * @param extraFieldFactory the extra field lookup factory.
    412     * @param entry the entry to get fields from
    413     * @throws ZipException on error
    414     */
    415    private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final ZipEntry entry) throws ZipException {
    416        super(entry);
    417        this.extraFieldFactory = extraFieldFactory;
    418        setName(entry.getName());
    419        final byte[] extra = entry.getExtra();
    420        if (extra != null) {
    421            setExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT));
    422        } else {
    423            // initializes extra data to an empty byte array
    424            setExtra();
    425        }
    426        setMethod(entry.getMethod());
    427        this.size = entry.getSize();
    428    }
    429
    430    /**
    431     * Creates a new ZIP entry taking some information from the given path and using the provided name.
    432     *
    433     * <p>
    434     * 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
    435     * will be stripped from the entry name.
    436     * </p>
    437     *
    438     * @param inputPath path to create the entry from.
    439     * @param entryName name of the entry.
    440     * @param options   options indicating how symbolic links are handled.
    441     * @throws IOException if an I/O error occurs.
    442     * @since 1.21
    443     */
    444    public ZipArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
    445        this(null, inputPath, entryName, options);
    446    }
    447
    448    /**
    449     * Creates a new ZIP entry with the specified name.
    450     *
    451     * <p>
    452     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
    453     * </p>
    454     *
    455     * @param name the name of the entry
    456     * @since 1.26.0
    457     */
    458    public ZipArchiveEntry(final String name) {
    459        this((Function<ZipShort, ZipExtraField>) null, name);
    460    }
    461
    462    /**
    463     * Creates a new ZIP entry with fields taken from the specified ZIP entry.
    464     *
    465     * <p>
    466     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
    467     * </p>
    468     *
    469     * @param entry the entry to get fields from
    470     * @throws ZipException on error
    471     */
    472    public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException {
    473        this((ZipEntry) entry);
    474        setInternalAttributes(entry.getInternalAttributes());
    475        setExternalAttributes(entry.getExternalAttributes());
    476        setExtraFields(entry.getAllExtraFieldsNoCopy());
    477        setPlatform(entry.getPlatform());
    478        final GeneralPurposeBit other = entry.getGeneralPurposeBit();
    479        setGeneralPurposeBit(other == null ? null : (GeneralPurposeBit) other.clone());
    480    }
    481
    482    /**
    483     * Creates a new ZIP entry with fields taken from the specified ZIP entry.
    484     *
    485     * <p>
    486     * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
    487     * </p>
    488     *
    489     * @param entry the entry to get fields from
    490     * @throws ZipException on error
    491     */
    492    public ZipArchiveEntry(final ZipEntry entry) throws ZipException {
    493        this(null, entry);
    494    }
    495
    496    /**
    497     * Adds an extra field - replacing an already present extra field of the same type.
    498     *
    499     * <p>
    500     * The new extra field will be the first one.
    501     * </p>
    502     *
    503     * @param ze an extra field
    504     */
    505    public void addAsFirstExtraField(final ZipExtraField ze) {
    506        if (ze instanceof UnparseableExtraFieldData) {
    507            unparseableExtra = (UnparseableExtraFieldData) ze;
    508        } else {
    509            if (getExtraField(ze.getHeaderId()) != null) {
    510                internalRemoveExtraField(ze.getHeaderId());
    511            }
    512            final ZipExtraField[] copy = extraFields;
    513            final int newLen = ArrayUtils.getLength(extraFields) + 1;
    514            extraFields = new ZipExtraField[newLen];
    515            extraFields[0] = ze;
    516            if (copy != null) {
    517                System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1);
    518            }
    519        }
    520        setExtra();
    521    }
    522
    523    /**
    524     * Adds an extra field - replacing an already present extra field of the same type.
    525     *
    526     * <p>
    527     * If no extra field of the same type exists, the field will be added as last field.
    528     * </p>
    529     *
    530     * @param ze an extra field
    531     */
    532    public void addExtraField(final ZipExtraField ze) {
    533        internalAddExtraField(ze);
    534        setExtra();
    535    }
    536
    537    private void addInfoZipExtendedTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) {
    538        final X5455_ExtendedTimestamp infoZipTimestamp = new X5455_ExtendedTimestamp();
    539        if (lastModifiedTime != null) {
    540            infoZipTimestamp.setModifyFileTime(lastModifiedTime);
    541        }
    542        if (lastAccessTime != null) {
    543            infoZipTimestamp.setAccessFileTime(lastAccessTime);
    544        }
    545        if (creationTime != null) {
    546            infoZipTimestamp.setCreateFileTime(creationTime);
    547        }
    548        internalAddExtraField(infoZipTimestamp);
    549    }
    550
    551    private void addNTFSTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) {
    552        final X000A_NTFS ntfsTimestamp = new X000A_NTFS();
    553        if (lastModifiedTime != null) {
    554            ntfsTimestamp.setModifyFileTime(lastModifiedTime);
    555        }
    556        if (lastAccessTime != null) {
    557            ntfsTimestamp.setAccessFileTime(lastAccessTime);
    558        }
    559        if (creationTime != null) {
    560            ntfsTimestamp.setCreateFileTime(creationTime);
    561        }
    562        internalAddExtraField(ntfsTimestamp);
    563    }
    564
    565    /**
    566     * Overwrite clone.
    567     *
    568     * @return a cloned copy of this ZipArchiveEntry
    569     */
    570    @Override
    571    public Object clone() {
    572        final ZipArchiveEntry e = (ZipArchiveEntry) super.clone();
    573
    574        e.setInternalAttributes(getInternalAttributes());
    575        e.setExternalAttributes(getExternalAttributes());
    576        e.setExtraFields(getAllExtraFieldsNoCopy());
    577        return e;
    578    }
    579
    580    private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) {
    581        return Arrays.copyOf(src, length);
    582    }
    583
    584    @Override
    585    public boolean equals(final Object obj) {
    586        if (this == obj) {
    587            return true;
    588        }
    589        if (obj == null || getClass() != obj.getClass()) {
    590            return false;
    591        }
    592        final ZipArchiveEntry other = (ZipArchiveEntry) obj;
    593        final String myName = getName();
    594        final String otherName = other.getName();
    595        if (!Objects.equals(myName, otherName)) {
    596            return false;
    597        }
    598        String myComment = getComment();
    599        String otherComment = other.getComment();
    600        if (myComment == null) {
    601            myComment = "";
    602        }
    603        if (otherComment == null) {
    604            otherComment = "";
    605        }
    606        return Objects.equals(getLastModifiedTime(), other.getLastModifiedTime()) && Objects.equals(getLastAccessTime(), other.getLastAccessTime())
    607                && Objects.equals(getCreationTime(), other.getCreationTime()) && myComment.equals(otherComment)
    608                && getInternalAttributes() == other.getInternalAttributes() && getPlatform() == other.getPlatform()
    609                && getExternalAttributes() == other.getExternalAttributes() && getMethod() == other.getMethod() && getSize() == other.getSize()
    610                && getCrc() == other.getCrc() && getCompressedSize() == other.getCompressedSize()
    611                && Arrays.equals(getCentralDirectoryExtra(), other.getCentralDirectoryExtra())
    612                && Arrays.equals(getLocalFileDataExtra(), other.getLocalFileDataExtra()) && localHeaderOffset == other.localHeaderOffset
    613                && dataOffset == other.dataOffset && generalPurposeBit.equals(other.generalPurposeBit);
    614    }
    615
    616    private ZipExtraField findMatching(final ZipShort headerId, final List<ZipExtraField> fs) {
    617        return fs.stream().filter(f -> headerId.equals(f.getHeaderId())).findFirst().orElse(null);
    618    }
    619
    620    private ZipExtraField findUnparseable(final List<ZipExtraField> fs) {
    621        return fs.stream().filter(UnparseableExtraFieldData.class::isInstance).findFirst().orElse(null);
    622    }
    623
    624    /**
    625     * Gets currently configured alignment.
    626     *
    627     * @return alignment for this entry.
    628     * @since 1.14
    629     */
    630    protected int getAlignment() {
    631        return this.alignment;
    632    }
    633
    634    private ZipExtraField[] getAllExtraFields() {
    635        final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy();
    636        return allExtraFieldsNoCopy == extraFields ? copyOf(allExtraFieldsNoCopy, allExtraFieldsNoCopy.length) : allExtraFieldsNoCopy;
    637    }
    638
    639    /**
    640     * Gets all extra fields, including unparseable ones.
    641     *
    642     * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method
    643     */
    644    private ZipExtraField[] getAllExtraFieldsNoCopy() {
    645        if (extraFields == null) {
    646            return getUnparseableOnly();
    647        }
    648        return unparseableExtra != null ? getMergedFields() : extraFields;
    649    }
    650
    651    /**
    652     * Gets the extra data for the central directory.
    653     *
    654     * @return the central directory extra data
    655     */
    656    public byte[] getCentralDirectoryExtra() {
    657        return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy());
    658    }
    659
    660    /**
    661     * * Gets the source of the comment field value.
    662     *
    663     * @return source of the comment field value
    664     * @since 1.16
    665     */
    666    public CommentSource getCommentSource() {
    667        return commentSource;
    668    }
    669
    670    @Override
    671    public long getDataOffset() {
    672        return dataOffset;
    673    }
    674
    675    /**
    676     * Gets the number of the split segment this entry starts at.
    677     *
    678     * @return the number of the split segment this entry starts at.
    679     * @since 1.20
    680     */
    681    public long getDiskNumberStart() {
    682        return diskNumberStart;
    683    }
    684
    685    /**
    686     * Gets the external file attributes.
    687     *
    688     * <p>
    689     * <strong>Note</strong>: {@link ZipArchiveInputStream} is unable to fill this field, you must use {@link ZipFile} if you want to read entries using this
    690     * attribute.
    691     * </p>
    692     *
    693     * @return the external file attributes
    694     */
    695    public long getExternalAttributes() {
    696        return externalAttributes;
    697    }
    698
    699    /**
    700     * Gets an extra field by its header id.
    701     *
    702     * @param type the header id
    703     * @return null if no such field exists.
    704     */
    705    public ZipExtraField getExtraField(final ZipShort type) {
    706        if (extraFields != null) {
    707            for (final ZipExtraField extraField : extraFields) {
    708                if (type.equals(extraField.getHeaderId())) {
    709                    return extraField;
    710                }
    711            }
    712        }
    713        return null;
    714    }
    715
    716    /**
    717     * Gets all extra fields that have been parsed successfully.
    718     *
    719     * <p>
    720     * <strong>Note</strong>: The set of extra fields may be incomplete when {@link ZipArchiveInputStream} has been used as some extra fields use the central
    721     * directory to store additional information.
    722     * </p>
    723     *
    724     * @return an array of the extra fields
    725     */
    726    public ZipExtraField[] getExtraFields() {
    727        return getParseableExtraFields();
    728    }
    729
    730    /**
    731     * Gets extra fields.
    732     *
    733     * @param includeUnparseable whether to also return unparseable extra fields as {@link UnparseableExtraFieldData} if such data exists.
    734     * @return an array of the extra fields
    735     * @since 1.1
    736     */
    737    public ZipExtraField[] getExtraFields(final boolean includeUnparseable) {
    738        return includeUnparseable ? getAllExtraFields() : getParseableExtraFields();
    739    }
    740
    741    /**
    742     * Gets extra fields.
    743     *
    744     * @param parsingBehavior controls parsing of extra fields.
    745     * @return an array of the extra fields
    746     * @throws ZipException if parsing fails, cannot happen if {@code
    747     * parsingBehavior}  is {@link ExtraFieldParsingMode#BEST_EFFORT}.
    748     * @since 1.19
    749     */
    750    public ZipExtraField[] getExtraFields(final ExtraFieldParsingBehavior parsingBehavior) throws ZipException {
    751        if (parsingBehavior == ExtraFieldParsingMode.BEST_EFFORT) {
    752            return getExtraFields(true);
    753        }
    754        if (parsingBehavior == ExtraFieldParsingMode.ONLY_PARSEABLE_LENIENT) {
    755            return getExtraFields(false);
    756        }
    757        final byte[] local = getExtra();
    758        final List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(parseExtraFields(local, true, parsingBehavior)));
    759        final byte[] central = getCentralDirectoryExtra();
    760        final List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(parseExtraFields(central, false, parsingBehavior)));
    761        final List<ZipExtraField> merged = new ArrayList<>();
    762        for (final ZipExtraField l : localFields) {
    763            final ZipExtraField c;
    764            if (l instanceof UnparseableExtraFieldData) {
    765                c = findUnparseable(centralFields);
    766            } else {
    767                c = findMatching(l.getHeaderId(), centralFields);
    768            }
    769            if (c != null) {
    770                final byte[] cd = c.getCentralDirectoryData();
    771                if (!ArrayUtils.isEmpty(cd)) {
    772                    l.parseFromCentralDirectoryData(cd, 0, cd.length);
    773                }
    774                centralFields.remove(c);
    775            }
    776            merged.add(l);
    777        }
    778        merged.addAll(centralFields);
    779        return merged.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY);
    780    }
    781
    782    /**
    783     * The "general purpose bit" field.
    784     *
    785     * @return the general purpose bit
    786     * @since 1.1
    787     */
    788    public GeneralPurposeBit getGeneralPurposeBit() {
    789        return generalPurposeBit;
    790    }
    791
    792    /**
    793     * Gets the internal file attributes.
    794     *
    795     * <p>
    796     * <strong>Note</strong>: {@link ZipArchiveInputStream} is unable to fill this field, you must use {@link ZipFile} if you want to read entries using this
    797     * attribute.
    798     * </p>
    799     *
    800     * @return the internal file attributes
    801     */
    802    public int getInternalAttributes() {
    803        return internalAttributes;
    804    }
    805
    806    /**
    807     * Wraps {@link ZipEntry#getTime} with a {@link Date} as the entry's last modified date.
    808     *
    809     * <p>
    810     * Changes to the implementation of {@link ZipEntry#getTime()} leak through and the returned value may depend on your local time zone as well as
    811     * your version of Java.
    812     * </p>
    813     */
    814    @Override
    815    public Date getLastModifiedDate() {
    816        return new Date(getTime());
    817    }
    818
    819    /**
    820     * Gets the extra data for the local file data.
    821     *
    822     * @return the extra data for local file
    823     */
    824    public byte[] getLocalFileDataExtra() {
    825        final byte[] extra = getExtra();
    826        return extra != null ? extra : ByteUtils.EMPTY_BYTE_ARRAY;
    827    }
    828
    829    /**
    830     * Gets the local header offset.
    831     *
    832     * @return the local header offset.
    833     * @since 1.24.0
    834     */
    835    public long getLocalHeaderOffset() {
    836        return this.localHeaderOffset;
    837    }
    838
    839    private ZipExtraField[] getMergedFields() {
    840        final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
    841        zipExtraFields[extraFields.length] = unparseableExtra;
    842        return zipExtraFields;
    843    }
    844
    845    /**
    846     * Gets the compression method of this entry, or -1 if the compression method has not been specified.
    847     *
    848     * @return compression method
    849     * @since 1.1
    850     */
    851    @Override
    852    public int getMethod() {
    853        return method;
    854    }
    855
    856    /**
    857     * Gets the name of the entry.
    858     *
    859     * <p>
    860     * This method returns the raw name as it is stored inside of the archive.
    861     * </p>
    862     *
    863     * @return the entry name
    864     */
    865    @Override
    866    public String getName() {
    867        return name == null ? super.getName() : name;
    868    }
    869
    870    /**
    871     * The source of the name field value.
    872     *
    873     * @return source of the name field value
    874     * @since 1.16
    875     */
    876    public NameSource getNameSource() {
    877        return nameSource;
    878    }
    879
    880    private ZipExtraField[] getParseableExtraFields() {
    881        final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy();
    882        return parseableExtraFields == extraFields ? copyOf(parseableExtraFields, parseableExtraFields.length) : parseableExtraFields;
    883    }
    884
    885    private ZipExtraField[] getParseableExtraFieldsNoCopy() {
    886        if (extraFields == null) {
    887            return ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY;
    888        }
    889        return extraFields;
    890    }
    891
    892    /**
    893     * Platform specification to put into the &quot;version made by&quot; part of the central file header.
    894     *
    895     * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} has been called, in which case PLATFORM_UNIX will be returned.
    896     */
    897    public int getPlatform() {
    898        return platform;
    899    }
    900
    901    /**
    902     * The content of the flags field.
    903     *
    904     * @return content of the flags field
    905     * @since 1.11
    906     */
    907    public int getRawFlag() {
    908        return rawFlag;
    909    }
    910
    911    /**
    912     * Returns the raw bytes that made up the name before it has been converted using the configured or guessed encoding.
    913     *
    914     * <p>
    915     * This method will return null if this instance has not been read from an archive.
    916     * </p>
    917     *
    918     * @return the raw name bytes
    919     * @since 1.2
    920     */
    921    public byte[] getRawName() {
    922        if (rawName != null) {
    923            return Arrays.copyOf(rawName, rawName.length);
    924        }
    925        return null;
    926    }
    927
    928    /**
    929     * Gets the uncompressed size of the entry data.
    930     *
    931     * <p>
    932     * <strong>Note</strong>: {@link ZipArchiveInputStream} may create entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long as the entry hasn't been
    933     * read completely.
    934     * </p>
    935     *
    936     * @return the entry size
    937     */
    938    @Override
    939    public long getSize() {
    940        return size;
    941    }
    942
    943    /**
    944     * {@inheritDoc}
    945     *
    946     * <p>
    947     * Override to work around bug <a href="https://bugs.openjdk.org/browse/JDK-8130914">JDK-8130914</a>
    948     * </p>
    949     *
    950     * @return The last modification time of the entry in milliseconds since the epoch, or -1 if not specified
    951     * @see #setTime(long)
    952     * @see #setLastModifiedTime(FileTime)
    953     */
    954    @Override
    955    public long getTime() {
    956        if (lastModifiedDateSet) {
    957            return getLastModifiedTime().toMillis();
    958        }
    959        return time != -1 ? time : super.getTime();
    960    }
    961
    962    /**
    963     * Gets the Unix permission.
    964     *
    965     * @return the Unix permissions.
    966     */
    967    public int getUnixMode() {
    968        return platform != PLATFORM_UNIX ? 0 : (int) (getExternalAttributes() >> SHORT_SHIFT & SHORT_MASK);
    969    }
    970
    971    /**
    972     * Gets up extra field data that couldn't be parsed correctly.
    973     *
    974     * @return null if no such field exists.
    975     * @since 1.1
    976     */
    977    public UnparseableExtraFieldData getUnparseableExtraFieldData() {
    978        return unparseableExtra;
    979    }
    980
    981    private ZipExtraField[] getUnparseableOnly() {
    982        return unparseableExtra == null ? ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY : new ZipExtraField[] { unparseableExtra };
    983    }
    984
    985    /**
    986     * Gets the "version made by" field.
    987     *
    988     * @return "version made by" field
    989     * @since 1.11
    990     */
    991    public int getVersionMadeBy() {
    992        return versionMadeBy;
    993    }
    994
    995    /**
    996     * Gets the "version required to expand" field.
    997     *
    998     * @return "version required to expand" field
    999     * @since 1.11
    1000     */
    1001    public int getVersionRequired() {
    1002        return versionRequired;
    1003    }
    1004
    1005    /**
    1006     * Gets the hash code of the entry. This uses the name as the hash code.
    1007     *
    1008     * @return a hash code.
    1009     */
    1010    @Override
    1011    public int hashCode() {
    1012        // this method has severe consequences on performance. We cannot rely
    1013        // on the super.hashCode() method since super.getName() always return
    1014        // the empty string in the current implementation (there's no setter)
    1015        // so it is basically draining the performance of a hashmap lookup
    1016        return getName().hashCode();
    1017    }
    1018
    1019    private void internalAddExtraField(final ZipExtraField ze) {
    1020        if (ze instanceof UnparseableExtraFieldData) {
    1021            unparseableExtra = (UnparseableExtraFieldData) ze;
    1022        } else if (extraFields == null) {
    1023            extraFields = new ZipExtraField[] { ze };
    1024        } else {
    1025            if (getExtraField(ze.getHeaderId()) != null) {
    1026                internalRemoveExtraField(ze.getHeaderId());
    1027            }
    1028            final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
    1029            zipExtraFields[zipExtraFields.length - 1] = ze;
    1030            extraFields = zipExtraFields;
    1031        }
    1032    }
    1033
    1034    private void internalRemoveExtraField(final ZipShort type) {
    1035        if (extraFields == null) {
    1036            return;
    1037        }
    1038        final List<ZipExtraField> newResult = new ArrayList<>();
    1039        for (final ZipExtraField extraField : extraFields) {
    1040            if (!type.equals(extraField.getHeaderId())) {
    1041                newResult.add(extraField);
    1042            }
    1043        }
    1044        if (extraFields.length == newResult.size()) {
    1045            return;
    1046        }
    1047        extraFields = newResult.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY);
    1048    }
    1049
    1050    private void internalSetLastModifiedTime(final FileTime time) {
    1051        super.setLastModifiedTime(time);
    1052        this.time = time.toMillis();
    1053        lastModifiedDateSet = true;
    1054    }
    1055
    1056    /**
    1057     * Tests whether this entry is a directory.
    1058     *
    1059     * @return true if the entry is a directory.
    1060     */
    1061    @Override
    1062    public boolean isDirectory() {
    1063        return isDirectoryEntryName(getName());
    1064    }
    1065
    1066    @Override
    1067    public boolean isStreamContiguous() {
    1068        return isStreamContiguous;
    1069    }
    1070
    1071    /**
    1072     * Tests whether this entry represents a Unix symlink, in which case the entry's content contains the target path for the symlink.
    1073     *
    1074     * @return true if the entry represents a Unix symlink, false otherwise.
    1075     * @since 1.5
    1076     */
    1077    public boolean isUnixSymlink() {
    1078        return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG;
    1079    }
    1080
    1081    /**
    1082     * If there are no extra fields, use the given fields as new extra data - otherwise merge the fields assuming the existing fields and the new fields stem
    1083     * from different locations inside the archive.
    1084     *
    1085     * @param f     the extra fields to merge
    1086     * @param local whether the new fields originate from local data
    1087     */
    1088    private void mergeExtraFields(final ZipExtraField[] f, final boolean local) {
    1089        if (extraFields == null) {
    1090            setExtraFields(f);
    1091        } else {
    1092            for (final ZipExtraField element : f) {
    1093                final ZipExtraField existing;
    1094                if (element instanceof UnparseableExtraFieldData) {
    1095                    existing = unparseableExtra;
    1096                } else {
    1097                    existing = getExtraField(element.getHeaderId());
    1098                }
    1099                if (existing == null) {
    1100                    internalAddExtraField(element);
    1101                } else {
    1102                    final byte[] b = local ? element.getLocalFileDataData() : element.getCentralDirectoryData();
    1103                    try {
    1104                        if (local) {
    1105                            existing.parseFromLocalFileData(b, 0, b.length);
    1106                        } else {
    1107                            existing.parseFromCentralDirectoryData(b, 0, b.length);
    1108                        }
    1109                    } catch (final ZipException ex) {
    1110                        // emulate ExtraFieldParsingMode.fillAndMakeUnrecognizedOnError
    1111                        final UnrecognizedExtraField u = new UnrecognizedExtraField();
    1112                        u.setHeaderId(existing.getHeaderId());
    1113                        if (local) {
    1114                            u.setLocalFileDataData(b);
    1115                            u.setCentralDirectoryData(existing.getCentralDirectoryData());
    1116                        } else {
    1117                            u.setLocalFileDataData(existing.getLocalFileDataData());
    1118                            u.setCentralDirectoryData(b);
    1119                        }
    1120                        internalRemoveExtraField(existing.getHeaderId());
    1121                        internalAddExtraField(u);
    1122                    }
    1123                }
    1124            }
    1125            setExtra();
    1126        }
    1127    }
    1128
    1129    private ZipExtraField[] parseExtraFields(final byte[] data, final boolean local, final ExtraFieldParsingBehavior parsingBehavior) throws ZipException {
    1130        if (extraFieldFactory != null) {
    1131            return ExtraFieldUtils.parse(data, local, new ExtraFieldParsingBehavior() {
    1132                @Override
    1133                public ZipExtraField createExtraField(final ZipShort headerId) throws ZipException, InstantiationException, IllegalAccessException {
    1134                    final ZipExtraField field = extraFieldFactory.apply(headerId);
    1135                    return field == null ? parsingBehavior.createExtraField(headerId) : field;
    1136                }
    1137
    1138                @Override
    1139                public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException {
    1140                    return parsingBehavior.fill(field, data, off, len, local);
    1141                }
    1142
    1143                @Override
    1144                public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength)
    1145                        throws ZipException {
    1146                    return parsingBehavior.onUnparseableExtraField(data, off, len, local, claimedLength);
    1147                }
    1148            });
    1149        }
    1150        return ExtraFieldUtils.parse(data, local, parsingBehavior);
    1151    }
    1152
    1153    /**
    1154     * Remove an extra field.
    1155     *
    1156     * @param type the type of extra field to remove
    1157     */
    1158    public void removeExtraField(final ZipShort type) {
    1159        if (getExtraField(type) == null) {
    1160            throw new NoSuchElementException();
    1161        }
    1162        internalRemoveExtraField(type);
    1163        setExtra();
    1164    }
    1165
    1166    /**
    1167     * Removes unparseable extra field data.
    1168     *
    1169     * @since 1.1
    1170     */
    1171    public void removeUnparseableExtraFieldData() {
    1172        if (unparseableExtra == null) {
    1173            throw new NoSuchElementException();
    1174        }
    1175        unparseableExtra = null;
    1176        setExtra();
    1177    }
    1178
    1179    private boolean requiresExtraTimeFields() {
    1180        if (getLastAccessTime() != null || getCreationTime() != null) {
    1181            return true;
    1182        }
    1183        return lastModifiedDateSet;
    1184    }
    1185
    1186    /**
    1187     * Sets alignment for this entry.
    1188     *
    1189     * @param alignment requested alignment, 0 for default.
    1190     * @since 1.14
    1191     */
    1192    public void setAlignment(final int alignment) {
    1193        if ((alignment & alignment - 1) != 0 || alignment > 0xffff) {
    1194            throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than " + 0xffff + " but is " + alignment);
    1195        }
    1196        this.alignment = alignment;
    1197    }
    1198
    1199    private void setAttributes(final Path inputPath, final LinkOption... options) throws IOException {
    1200        final BasicFileAttributes attributes = Files.readAttributes(inputPath, BasicFileAttributes.class, options);
    1201        if (attributes.isRegularFile()) {
    1202            setSize(attributes.size());
    1203        }
    1204        super.setLastModifiedTime(attributes.lastModifiedTime());
    1205        super.setCreationTime(attributes.creationTime());
    1206        super.setLastAccessTime(attributes.lastAccessTime());
    1207        setExtraTimeFields();
    1208    }
    1209
    1210    /**
    1211     * Sets the central directory part of extra fields.
    1212     *
    1213     * @param b an array of bytes to be parsed into extra fields
    1214     */
    1215    public void setCentralDirectoryExtra(final byte[] b) {
    1216        try {
    1217            mergeExtraFields(parseExtraFields(b, false, ExtraFieldParsingMode.BEST_EFFORT), false);
    1218        } catch (final ZipException e) {
    1219            // actually this is not possible as of Commons Compress 1.19
    1220            throw new IllegalArgumentException(e.getMessage(), e); // NOSONAR
    1221        }
    1222    }
    1223
    1224    /**
    1225     * Sets the source of the comment field value.
    1226     *
    1227     * @param commentSource source of the comment field value
    1228     * @since 1.16
    1229     */
    1230    public void setCommentSource(final CommentSource commentSource) {
    1231        this.commentSource = commentSource;
    1232    }
    1233
    1234    @Override
    1235    public ZipEntry setCreationTime(final FileTime time) {
    1236        super.setCreationTime(time);
    1237        setExtraTimeFields();
    1238        return this;
    1239    }
    1240    /*
    1241     * (non-Javadoc)
    1242     *
    1243     * @see Object#equals(Object)
    1244     */
    1245
    1246    /**
    1247     * Sets the data offset.
    1248     *
    1249     * @param dataOffset new value of data offset.
    1250     */
    1251    protected void setDataOffset(final long dataOffset) {
    1252        this.dataOffset = dataOffset;
    1253    }
    1254
    1255    /**
    1256     * The number of the split segment this entry starts at.
    1257     *
    1258     * @param diskNumberStart the number of the split segment this entry starts at.
    1259     * @since 1.20
    1260     */
    1261    public void setDiskNumberStart(final long diskNumberStart) {
    1262        this.diskNumberStart = diskNumberStart;
    1263    }
    1264
    1265    /**
    1266     * Sets the external file attributes.
    1267     *
    1268     * @param value an {@code long} value
    1269     */
    1270    public void setExternalAttributes(final long value) {
    1271        externalAttributes = value;
    1272    }
    1273
    1274    /**
    1275     * Unfortunately {@link java.util.zip.ZipOutputStream} seems to access the extra data directly, so overriding getExtra doesn't help - we need to modify
    1276     * super's data directly and on every update.
    1277     */
    1278    protected void setExtra() {
    1279        // ZipEntry will update the time fields here, so we need to reprocess them afterwards
    1280        super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy()));
    1281        // Reprocess and overwrite the modifications made by ZipEntry#setExtra(byte[])
    1282        updateTimeFieldsFromExtraFields();
    1283    }
    1284
    1285    /**
    1286     * Parses the given bytes as extra field data and consumes any unparseable data as an {@link UnparseableExtraFieldData} instance.
    1287     *
    1288     * @param extra an array of bytes to be parsed into extra fields
    1289     * @throws RuntimeException if the bytes cannot be parsed
    1290     * @throws RuntimeException on error
    1291     */
    1292    @Override
    1293    public void setExtra(final byte[] extra) throws RuntimeException {
    1294        try {
    1295            mergeExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT), true);
    1296        } catch (final ZipException e) {
    1297            // actually this is not possible as of Commons Compress 1.1
    1298            throw new IllegalArgumentException("Error parsing extra fields for entry: " // NOSONAR
    1299                    + getName() + " - " + e.getMessage(), e);
    1300        }
    1301    }
    1302
    1303    /**
    1304     * Replaces all currently attached extra fields with the new array.
    1305     *
    1306     * @param fields an array of extra fields
    1307     */
    1308    public void setExtraFields(final ZipExtraField[] fields) {
    1309        unparseableExtra = null;
    1310        final List<ZipExtraField> newFields = new ArrayList<>();
    1311        if (fields != null) {
    1312            for (final ZipExtraField field : fields) {
    1313                if (field instanceof UnparseableExtraFieldData) {
    1314                    unparseableExtra = (UnparseableExtraFieldData) field;
    1315                } else {
    1316                    newFields.add(field);
    1317                }
    1318            }
    1319        }
    1320        extraFields = newFields.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY);
    1321        setExtra();
    1322    }
    1323
    1324    private void setExtraTimeFields() {
    1325        if (getExtraField(X5455_ExtendedTimestamp.HEADER_ID) != null) {
    1326            internalRemoveExtraField(X5455_ExtendedTimestamp.HEADER_ID);
    1327        }
    1328        if (getExtraField(X000A_NTFS.HEADER_ID) != null) {
    1329            internalRemoveExtraField(X000A_NTFS.HEADER_ID);
    1330        }
    1331        if (requiresExtraTimeFields()) {
    1332            final FileTime lastModifiedTime = getLastModifiedTime();
    1333            final FileTime lastAccessTime = getLastAccessTime();
    1334            final FileTime creationTime = getCreationTime();
    1335            if (canConvertToInfoZipExtendedTimestamp(lastModifiedTime, lastAccessTime, creationTime)) {
    1336                addInfoZipExtendedTimestamp(lastModifiedTime, lastAccessTime, creationTime);
    1337            }
    1338            addNTFSTimestamp(lastModifiedTime, lastAccessTime, creationTime);
    1339        }
    1340        setExtra();
    1341    }
    1342
    1343    /**
    1344     * Sets the "general purpose bit" field.
    1345     *
    1346     * @param generalPurposeBit the general purpose bit
    1347     * @since 1.1
    1348     */
    1349    public void setGeneralPurposeBit(final GeneralPurposeBit generalPurposeBit) {
    1350        this.generalPurposeBit = generalPurposeBit;
    1351    }
    1352
    1353    /**
    1354     * Sets the internal file attributes.
    1355     *
    1356     * @param internalAttributes an {@code int} value
    1357     */
    1358    public void setInternalAttributes(final int internalAttributes) {
    1359        this.internalAttributes = internalAttributes;
    1360    }
    1361
    1362    @Override
    1363    public ZipEntry setLastAccessTime(final FileTime fileTime) {
    1364        super.setLastAccessTime(fileTime);
    1365        setExtraTimeFields();
    1366        return this;
    1367    }
    1368
    1369    @Override
    1370    public ZipEntry setLastModifiedTime(final FileTime fileTime) {
    1371        internalSetLastModifiedTime(fileTime);
    1372        setExtraTimeFields();
    1373        return this;
    1374    }
    1375
    1376    /**
    1377     * Sets the local header offset.
    1378     *
    1379     * @param localHeaderOffset the local header offset.
    1380     */
    1381    protected void setLocalHeaderOffset(final long localHeaderOffset) {
    1382        this.localHeaderOffset = localHeaderOffset;
    1383    }
    1384
    1385    /**
    1386     * Sets the compression method of this entry.
    1387     *
    1388     * @param method compression method
    1389     * @since 1.1
    1390     */
    1391    @Override
    1392    public void setMethod(final int method) {
    1393        if (method < 0) {
    1394            throw new IllegalArgumentException("ZIP compression method cannot be negative: " + method);
    1395        }
    1396        this.method = method;
    1397    }
    1398
    1399    /**
    1400     * Sets the name of the entry.
    1401     *
    1402     * @param name the name to use
    1403     */
    1404    protected void setName(String name) {
    1405        if (name != null && getPlatform() == PLATFORM_FAT && !name.contains(ZIP_DIR_SEP)) {
    1406            name = name.replace('\\', '/');
    1407        }
    1408        this.name = name;
    1409    }
    1410
    1411    /**
    1412     * Sets the name using the raw bytes and the string created from it by guessing or using the configured encoding.
    1413     *
    1414     * @param name    the name to use created from the raw bytes using the guessed or configured encoding
    1415     * @param rawName the bytes originally read as name from the archive
    1416     * @since 1.2
    1417     */
    1418    protected void setName(final String name, final byte[] rawName) {
    1419        setName(name);
    1420        this.rawName = rawName;
    1421    }
    1422
    1423    /**
    1424     * Sets the source of the name field value.
    1425     *
    1426     * @param nameSource source of the name field value
    1427     * @since 1.16
    1428     */
    1429    public void setNameSource(final NameSource nameSource) {
    1430        this.nameSource = nameSource;
    1431    }
    1432
    1433    /**
    1434     * Sets the platform (Unix or FAT).
    1435     *
    1436     * @param platform an {@code int} value - 0 is FAT, 3 is Unix
    1437     */
    1438    protected void setPlatform(final int platform) {
    1439        this.platform = platform;
    1440    }
    1441
    1442    /**
    1443     * Sets the content of the flags field.
    1444     *
    1445     * @param rawFlag content of the flags field
    1446     * @since 1.11
    1447     */
    1448    public void setRawFlag(final int rawFlag) {
    1449        this.rawFlag = rawFlag;
    1450    }
    1451
    1452    /**
    1453     * Sets the uncompressed size of the entry data.
    1454     *
    1455     * @param size the uncompressed size in bytes
    1456     * @throws IllegalArgumentException if the specified size is less than 0
    1457     */
    1458    @Override
    1459    public void setSize(final long size) {
    1460        if (size < 0) {
    1461            throw new IllegalArgumentException("Invalid entry size");
    1462        }
    1463        this.size = size;
    1464    }
    1465
    1466    /**
    1467     * Sets whether the stream is contiguous, that is, not split among several archive parts, interspersed with control blocks, and so on.
    1468     *
    1469     * @param isStreamContiguous whether the stream is contiguous
    1470     */
    1471    protected void setStreamContiguous(final boolean isStreamContiguous) {
    1472        this.isStreamContiguous = isStreamContiguous;
    1473    }
    1474
    1475    /**
    1476     * Sets the modification time of the entry.
    1477     *
    1478     * @param fileTime the entry modification time.
    1479     * @since 1.21
    1480     */
    1481    public void setTime(final FileTime fileTime) {
    1482        setTime(fileTime.toMillis());
    1483    }
    1484
    1485    /**
    1486     *
    1487     * {@inheritDoc}
    1488     *
    1489     * <p>
    1490     * Override to work around bug <a href="https://bugs.openjdk.org/browse/JDK-8130914">JDK-8130914</a>
    1491     * </p>
    1492     *
    1493     * @param timeEpochMillis The last modification time of the entry in milliseconds since the epoch.
    1494     * @see #getTime()
    1495     * @see #getLastModifiedTime()
    1496     */
    1497    @Override
    1498    public void setTime(final long timeEpochMillis) {
    1499        if (ZipUtil.isDosTime(timeEpochMillis)) {
    1500            super.setTime(timeEpochMillis);
    1501            this.time = timeEpochMillis;
    1502            lastModifiedDateSet = false;
    1503            setExtraTimeFields();
    1504        } else {
    1505            setLastModifiedTime(FileTime.fromMillis(timeEpochMillis));
    1506        }
    1507    }
    1508
    1509    /**
    1510     * Sets Unix permissions in a way that is understood by Info-Zip's unzip command.
    1511     *
    1512     * @param mode an {@code int} value
    1513     */
    1514    public void setUnixMode(final int mode) {
    1515        // CheckStyle:MagicNumberCheck OFF - no point
    1516        setExternalAttributes(mode << SHORT_SHIFT
    1517                // MS-DOS read-only attribute
    1518                | ((mode & 0200) == 0 ? 1 : 0)
    1519                // MS-DOS directory flag
    1520                | (isDirectory() ? 0x10 : 0));
    1521        // CheckStyle:MagicNumberCheck ON
    1522        platform = PLATFORM_UNIX;
    1523    }
    1524
    1525    /**
    1526     * Sets the "version made by" field.
    1527     *
    1528     * @param versionMadeBy "version made by" field
    1529     * @since 1.11
    1530     */
    1531    public void setVersionMadeBy(final int versionMadeBy) {
    1532        this.versionMadeBy = versionMadeBy;
    1533    }
    1534
    1535    /**
    1536     * Sets the "version required to expand" field.
    1537     *
    1538     * @param versionRequired "version required to expand" field
    1539     * @since 1.11
    1540     */
    1541    public void setVersionRequired(final int versionRequired) {
    1542        this.versionRequired = versionRequired;
    1543    }
    1544
    1545    private void updateTimeFieldsFromExtraFields() {
    1546        // Update times from X5455_ExtendedTimestamp field
    1547        updateTimeFromExtendedTimestampField();
    1548        // Update times from X000A_NTFS field, overriding X5455_ExtendedTimestamp if both are present
    1549        updateTimeFromNtfsField();
    1550    }
    1551
    1552    /**
    1553     * Workaround for the fact that, as of Java 17, {@link ZipEntry} does not properly modify the entry's {@code xdostime} field, only setting
    1554     * {@code mtime}. While this is not strictly necessary, it's better to maintain the same behavior between this and the NTFS field.
    1555     */
    1556    private void updateTimeFromExtendedTimestampField() {
    1557        final ZipExtraField extraField = getExtraField(X5455_ExtendedTimestamp.HEADER_ID);
    1558        if (extraField instanceof X5455_ExtendedTimestamp) {
    1559            final X5455_ExtendedTimestamp extendedTimestamp = (X5455_ExtendedTimestamp) extraField;
    1560            if (extendedTimestamp.isBit0_modifyTimePresent()) {
    1561                final FileTime modifyTime = extendedTimestamp.getModifyFileTime();
    1562                if (modifyTime != null) {
    1563                    internalSetLastModifiedTime(modifyTime);
    1564                }
    1565            }
    1566            if (extendedTimestamp.isBit1_accessTimePresent()) {
    1567                final FileTime accessTime = extendedTimestamp.getAccessFileTime();
    1568                if (accessTime != null) {
    1569                    super.setLastAccessTime(accessTime);
    1570                }
    1571            }
    1572            if (extendedTimestamp.isBit2_createTimePresent()) {
    1573                final FileTime creationTime = extendedTimestamp.getCreateFileTime();
    1574                if (creationTime != null) {
    1575                    super.setCreationTime(creationTime);
    1576                }
    1577            }
    1578        }
    1579    }
    1580
    1581    /**
    1582     * Workaround for the fact that, as of Java 17, {@link ZipEntry} parses NTFS timestamps with a maximum precision of microseconds, which is
    1583     * lower than the 100ns precision provided by this extra field.
    1584     */
    1585    private void updateTimeFromNtfsField() {
    1586        final ZipExtraField extraField = getExtraField(X000A_NTFS.HEADER_ID);
    1587        if (extraField instanceof X000A_NTFS) {
    1588            final X000A_NTFS ntfsTimestamp = (X000A_NTFS) extraField;
    1589            final FileTime modifyTime = ntfsTimestamp.getModifyFileTime();
    1590            if (modifyTime != null) {
    1591                internalSetLastModifiedTime(modifyTime);
    1592            }
    1593            final FileTime accessTime = ntfsTimestamp.getAccessFileTime();
    1594            if (accessTime != null) {
    1595                super.setLastAccessTime(accessTime);
    1596            }
    1597            final FileTime creationTime = ntfsTimestamp.getCreateFileTime();
    1598            if (creationTime != null) {
    1599                super.setCreationTime(creationTime);
    1600            }
    1601        }
    1602    }
    1603}