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