ZipArchiveEntry.java

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

  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.nio.file.Files;
  21. import java.nio.file.LinkOption;
  22. import java.nio.file.Path;
  23. import java.nio.file.attribute.BasicFileAttributes;
  24. import java.nio.file.attribute.FileTime;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Date;
  28. import java.util.LinkedList;
  29. import java.util.List;
  30. import java.util.NoSuchElementException;
  31. import java.util.Objects;
  32. import java.util.function.Function;
  33. import java.util.zip.ZipEntry;
  34. import java.util.zip.ZipException;

  35. import org.apache.commons.compress.archivers.ArchiveEntry;
  36. import org.apache.commons.compress.archivers.EntryStreamOffsets;
  37. import org.apache.commons.compress.utils.ByteUtils;
  38. import org.apache.commons.io.file.attribute.FileTimes;

  39. /**
  40.  * Extension that adds better handling of extra fields and provides access to the internal and external file attributes.
  41.  *
  42.  * <p>
  43.  * The extra data is expected to follow the recommendation of <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:
  44.  * </p>
  45.  * <ul>
  46.  * <li>the extra byte array consists of a sequence of extra fields</li>
  47.  * <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>
  48.  * </ul>
  49.  *
  50.  * <p>
  51.  * 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.
  52.  * 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
  53.  * recommendation.
  54.  * </p>
  55.  *
  56.  * @NotThreadSafe
  57.  */
  58. public class ZipArchiveEntry extends ZipEntry implements ArchiveEntry, EntryStreamOffsets {

  59.     /**
  60.      * Indicates how the comment of this entry has been determined.
  61.      *
  62.      * @since 1.16
  63.      */
  64.     public enum CommentSource {
  65.         /**
  66.          * The comment has been read from the archive using the encoding of the archive specified when creating the {@link ZipArchiveInputStream} or
  67.          * {@link ZipFile} (defaults to the platform's default encoding).
  68.          */
  69.         COMMENT,
  70.         /**
  71.          * The comment has been read from an {@link UnicodeCommentExtraField Unicode Extra Field}.
  72.          */
  73.         UNICODE_EXTRA_FIELD
  74.     }

  75.     /**
  76.      * How to try to parse the extra fields.
  77.      *
  78.      * <p>
  79.      * Configures the behavior for:
  80.      * </p>
  81.      * <ul>
  82.      * <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>
  83.      * <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
  84.      * 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>
  85.      * </ul>
  86.      *
  87.      * @since 1.19
  88.      */
  89.     public enum ExtraFieldParsingMode implements ExtraFieldParsingBehavior {
  90.         /**
  91.          * 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
  92.          * {@link UnrecognizedExtraField}.
  93.          *
  94.          * <p>
  95.          * Wrap extra data that doesn't follow the recommended pattern in an {@link UnparseableExtraFieldData} instance.
  96.          * </p>
  97.          *
  98.          * <p>
  99.          * This is the default behavior starting with Commons Compress 1.19.
  100.          * </p>
  101.          */
  102.         BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ) {
  103.             @Override
  104.             public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) {
  105.                 return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
  106.             }
  107.         },
  108.         /**
  109.          * Try to parse as many extra fields as possible and wrap unknown extra fields in {@link UnrecognizedExtraField}.
  110.          *
  111.          * <p>
  112.          * Wrap extra data that doesn't follow the recommended pattern in an {@link UnparseableExtraFieldData} instance.
  113.          * </p>
  114.          *
  115.          * <p>
  116.          * Throw an exception if an extra field that is generally supported cannot be parsed.
  117.          * </p>
  118.          *
  119.          * <p>
  120.          * This used to be the default behavior prior to Commons Compress 1.19.
  121.          * </p>
  122.          */
  123.         STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ),
  124.         /**
  125.          * 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
  126.          * {@link UnrecognizedExtraField}.
  127.          *
  128.          * <p>
  129.          * Ignore extra data that doesn't follow the recommended pattern.
  130.          * </p>
  131.          */
  132.         ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP) {
  133.             @Override
  134.             public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) {
  135.                 return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
  136.             }
  137.         },
  138.         /**
  139.          * Try to parse as many extra fields as possible and wrap unknown extra fields in {@link UnrecognizedExtraField}.
  140.          *
  141.          * <p>
  142.          * Ignore extra data that doesn't follow the recommended pattern.
  143.          * </p>
  144.          *
  145.          * <p>
  146.          * Throw an exception if an extra field that is generally supported cannot be parsed.
  147.          * </p>
  148.          */
  149.         ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP),
  150.         /**
  151.          * Throw an exception if any of the recognized extra fields cannot be parsed or any extra field violates the recommended pattern.
  152.          */
  153.         DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW);

  154.         private static ZipExtraField fillAndMakeUnrecognizedOnError(final ZipExtraField field, final byte[] data, final int off, final int len,
  155.                 final boolean local) {
  156.             try {
  157.                 return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
  158.             } catch (final ZipException ex) {
  159.                 final UnrecognizedExtraField u = new UnrecognizedExtraField();
  160.                 u.setHeaderId(field.getHeaderId());
  161.                 if (local) {
  162.                     u.setLocalFileDataData(Arrays.copyOfRange(data, off, off + len));
  163.                 } else {
  164.                     u.setCentralDirectoryData(Arrays.copyOfRange(data, off, off + len));
  165.                 }
  166.                 return u;
  167.             }
  168.         }

  169.         private final ExtraFieldUtils.UnparseableExtraField onUnparseableData;

  170.         ExtraFieldParsingMode(final ExtraFieldUtils.UnparseableExtraField onUnparseableData) {
  171.             this.onUnparseableData = onUnparseableData;
  172.         }

  173.         @Override
  174.         public ZipExtraField createExtraField(final ZipShort headerId) {
  175.             return ExtraFieldUtils.createExtraField(headerId);
  176.         }

  177.         @Override
  178.         public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException {
  179.             return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
  180.         }

  181.         @Override
  182.         public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength)
  183.                 throws ZipException {
  184.             return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength);
  185.         }
  186.     }

  187.     /**
  188.      * Indicates how the name of this entry has been determined.
  189.      *
  190.      * @since 1.16
  191.      */
  192.     public enum NameSource {
  193.         /**
  194.          * The name has been read from the archive using the encoding of the archive specified when creating the {@link ZipArchiveInputStream} or
  195.          * {@link ZipFile} (defaults to the platform's default encoding).
  196.          */
  197.         NAME,
  198.         /**
  199.          * 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.
  200.          */
  201.         NAME_WITH_EFS_FLAG,
  202.         /**
  203.          * The name has been read from an {@link UnicodePathExtraField Unicode Extra Field}.
  204.          */
  205.         UNICODE_EXTRA_FIELD
  206.     }

  207.     private static final String ZIP_DIR_SEP = "/";

  208.     static final ZipArchiveEntry[] EMPTY_ARRAY = {};
  209.     static LinkedList<ZipArchiveEntry> EMPTY_LINKED_LIST = new LinkedList<>();

  210.     public static final int PLATFORM_UNIX = 3;
  211.     public static final int PLATFORM_FAT = 0;
  212.     public static final int CRC_UNKNOWN = -1;

  213.     private static final int SHORT_MASK = 0xFFFF;

  214.     private static final int SHORT_SHIFT = 16;

  215.     private static boolean canConvertToInfoZipExtendedTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) {
  216.         return FileTimes.isUnixTime(lastModifiedTime) && FileTimes.isUnixTime(lastAccessTime) && FileTimes.isUnixTime(creationTime);
  217.     }

  218.     private static boolean isDirectoryEntryName(final String entryName) {
  219.         return entryName.endsWith(ZIP_DIR_SEP);
  220.     }

  221.     private static String toDirectoryEntryName(final String entryName) {
  222.         return isDirectoryEntryName(entryName) ? entryName : entryName + ZIP_DIR_SEP;
  223.     }

  224.     private static String toEntryName(final File inputFile, final String entryName) {
  225.         return inputFile.isDirectory() ? toDirectoryEntryName(entryName) : entryName;
  226.     }

  227.     private static String toEntryName(final Path inputPath, final String entryName, final LinkOption... options) {
  228.         return Files.isDirectory(inputPath, options) ? toDirectoryEntryName(entryName) : entryName;
  229.     }

  230.     /**
  231.      * The {@link ZipEntry} base class only supports the compression methods STORED and DEFLATED. We override the field so that any compression
  232.      * methods can be used.
  233.      * <p>
  234.      * The default value -1 means that the method has not been specified.
  235.      * </p>
  236.      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93" >COMPRESS-93</a>
  237.      */
  238.     private int method = ZipMethod.UNKNOWN_CODE;

  239.     /**
  240.      * The {@link ZipEntry#setSize} method in the base class throws an IllegalArgumentException if the size is bigger than 2GB for Java versions
  241.      * &lt; 7 and even in Java 7+ if the implementation in java.util.zip doesn't support Zip64 itself (it is an optional feature).
  242.      * <p>
  243.      * We need to keep our own size information for Zip64 support.
  244.      * </p>
  245.      */
  246.     private long size = SIZE_UNKNOWN;
  247.     private int internalAttributes;
  248.     private int versionRequired;
  249.     private int versionMadeBy;
  250.     private int platform = PLATFORM_FAT;
  251.     private int rawFlag;
  252.     private long externalAttributes;
  253.     private int alignment;
  254.     private ZipExtraField[] extraFields;
  255.     private UnparseableExtraFieldData unparseableExtra;
  256.     private String name;
  257.     private byte[] rawName;
  258.     private GeneralPurposeBit generalPurposeBit = new GeneralPurposeBit();
  259.     private long localHeaderOffset = OFFSET_UNKNOWN;
  260.     private long dataOffset = OFFSET_UNKNOWN;
  261.     private boolean isStreamContiguous;

  262.     private NameSource nameSource = NameSource.NAME;

  263.     private final Function<ZipShort, ZipExtraField> extraFieldFactory;

  264.     private CommentSource commentSource = CommentSource.COMMENT;

  265.     private long diskNumberStart;

  266.     private boolean lastModifiedDateSet;

  267.     private long time = -1;

  268.     /**
  269.      */
  270.     protected ZipArchiveEntry() {
  271.         this("");
  272.     }

  273.     /**
  274.      * Creates a new ZIP entry taking some information from the given file and using the provided name.
  275.      *
  276.      * <p>
  277.      * 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
  278.      * will be stripped from the entry name.
  279.      * </p>
  280.      *
  281.      * @param inputFile file to create the entry from
  282.      * @param entryName name of the entry
  283.      */
  284.     public ZipArchiveEntry(final File inputFile, final String entryName) {
  285.         this(null, inputFile, entryName);
  286.     }

  287.     /**
  288.      * Creates a new ZIP entry taking some information from the given file and using the provided name.
  289.      *
  290.      * <p>
  291.      * 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
  292.      * will be stripped from the entry name.
  293.      * </p>
  294.      *
  295.      * @param extraFieldFactory custom lookup factory for extra fields or null
  296.      * @param inputFile file to create the entry from
  297.      * @param entryName name of the entry
  298.      */
  299.     private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final File inputFile, final String entryName) {
  300.         this(extraFieldFactory, toEntryName(inputFile, entryName));
  301.         try {
  302.             setAttributes(inputFile.toPath());
  303.         } catch (final IOException e) { // NOSONAR
  304.             if (inputFile.isFile()) {
  305.                 setSize(inputFile.length());
  306.             }
  307.             setTime(inputFile.lastModified());
  308.         }
  309.     }

  310.     /**
  311.      * Creates a new ZIP entry taking some information from the given path and using the provided name.
  312.      *
  313.      * <p>
  314.      * 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
  315.      * will be stripped from the entry name.
  316.      * </p>
  317.      *
  318.      * @param extraFieldFactory custom lookup factory for extra fields or null
  319.      * @param inputPath path to create the entry from.
  320.      * @param entryName name of the entry.
  321.      * @param options   options indicating how symbolic links are handled.
  322.      * @throws IOException if an I/O error occurs.
  323.      */
  324.     private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final Path inputPath, final String entryName,
  325.             final LinkOption... options) throws IOException {
  326.         this(extraFieldFactory, toEntryName(inputPath, entryName, options));
  327.         setAttributes(inputPath, options);
  328.     }

  329.     /**
  330.      * Creates a new ZIP entry with the specified name.
  331.      *
  332.      * <p>
  333.      * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
  334.      * </p>
  335.      *
  336.      * @param extraFieldFactory custom lookup factory for extra fields or null
  337.      * @param name the name of the entry
  338.      */
  339.     private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final String name) {
  340.         super(name);
  341.         this.extraFieldFactory = extraFieldFactory;
  342.         setName(name);
  343.     }

  344.     /**
  345.      * Creates a new ZIP entry with fields taken from the specified ZIP entry.
  346.      *
  347.      * <p>
  348.      * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
  349.      * </p>
  350.      *
  351.      * @param extraFieldFactory the extra field lookup factory.
  352.      * @param entry the entry to get fields from
  353.      * @throws ZipException on error
  354.      */
  355.     private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final ZipEntry entry) throws ZipException {
  356.         super(entry);
  357.         this.extraFieldFactory = extraFieldFactory;
  358.         setName(entry.getName());
  359.         final byte[] extra = entry.getExtra();
  360.         if (extra != null) {
  361.             setExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT));
  362.         } else {
  363.             // initializes extra data to an empty byte array
  364.             setExtra();
  365.         }
  366.         setMethod(entry.getMethod());
  367.         this.size = entry.getSize();
  368.     }

  369.     /**
  370.      * Creates a new ZIP entry taking some information from the given path and using the provided name.
  371.      *
  372.      * <p>
  373.      * 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
  374.      * will be stripped from the entry name.
  375.      * </p>
  376.      *
  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.      * @since 1.21
  382.      */
  383.     public ZipArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
  384.         this(null, inputPath, entryName, options);
  385.     }

  386.     /**
  387.      * Creates a new ZIP entry with the specified name.
  388.      *
  389.      * <p>
  390.      * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
  391.      * </p>
  392.      *
  393.      * @param name the name of the entry
  394.      * @since 1.26.0
  395.      */
  396.     public ZipArchiveEntry(final String name) {
  397.         this((Function<ZipShort, ZipExtraField>) null, name);
  398.     }

  399.     /**
  400.      * Creates a new ZIP entry with fields taken from the specified ZIP entry.
  401.      *
  402.      * <p>
  403.      * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
  404.      * </p>
  405.      *
  406.      * @param entry the entry to get fields from
  407.      * @throws ZipException on error
  408.      */
  409.     public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException {
  410.         this((ZipEntry) entry);
  411.         setInternalAttributes(entry.getInternalAttributes());
  412.         setExternalAttributes(entry.getExternalAttributes());
  413.         setExtraFields(entry.getAllExtraFieldsNoCopy());
  414.         setPlatform(entry.getPlatform());
  415.         final GeneralPurposeBit other = entry.getGeneralPurposeBit();
  416.         setGeneralPurposeBit(other == null ? null : (GeneralPurposeBit) other.clone());
  417.     }

  418.     /**
  419.      * Creates a new ZIP entry with fields taken from the specified ZIP entry.
  420.      *
  421.      * <p>
  422.      * Assumes the entry represents a directory if and only if the name ends with a forward slash "/".
  423.      * </p>
  424.      *
  425.      * @param entry the entry to get fields from
  426.      * @throws ZipException on error
  427.      */
  428.     public ZipArchiveEntry(final ZipEntry entry) throws ZipException {
  429.         this(null, entry);
  430.     }

  431.     /**
  432.      * Adds an extra field - replacing an already present extra field of the same type.
  433.      *
  434.      * <p>
  435.      * The new extra field will be the first one.
  436.      * </p>
  437.      *
  438.      * @param ze an extra field
  439.      */
  440.     public void addAsFirstExtraField(final ZipExtraField ze) {
  441.         if (ze instanceof UnparseableExtraFieldData) {
  442.             unparseableExtra = (UnparseableExtraFieldData) ze;
  443.         } else {
  444.             if (getExtraField(ze.getHeaderId()) != null) {
  445.                 internalRemoveExtraField(ze.getHeaderId());
  446.             }
  447.             final ZipExtraField[] copy = extraFields;
  448.             final int newLen = extraFields != null ? extraFields.length + 1 : 1;
  449.             extraFields = new ZipExtraField[newLen];
  450.             extraFields[0] = ze;
  451.             if (copy != null) {
  452.                 System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1);
  453.             }
  454.         }
  455.         setExtra();
  456.     }

  457.     /**
  458.      * Adds an extra field - replacing an already present extra field of the same type.
  459.      *
  460.      * <p>
  461.      * If no extra field of the same type exists, the field will be added as last field.
  462.      * </p>
  463.      *
  464.      * @param ze an extra field
  465.      */
  466.     public void addExtraField(final ZipExtraField ze) {
  467.         internalAddExtraField(ze);
  468.         setExtra();
  469.     }

  470.     private void addInfoZipExtendedTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) {
  471.         final X5455_ExtendedTimestamp infoZipTimestamp = new X5455_ExtendedTimestamp();
  472.         if (lastModifiedTime != null) {
  473.             infoZipTimestamp.setModifyFileTime(lastModifiedTime);
  474.         }
  475.         if (lastAccessTime != null) {
  476.             infoZipTimestamp.setAccessFileTime(lastAccessTime);
  477.         }
  478.         if (creationTime != null) {
  479.             infoZipTimestamp.setCreateFileTime(creationTime);
  480.         }
  481.         internalAddExtraField(infoZipTimestamp);
  482.     }

  483.     private void addNTFSTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) {
  484.         final X000A_NTFS ntfsTimestamp = new X000A_NTFS();
  485.         if (lastModifiedTime != null) {
  486.             ntfsTimestamp.setModifyFileTime(lastModifiedTime);
  487.         }
  488.         if (lastAccessTime != null) {
  489.             ntfsTimestamp.setAccessFileTime(lastAccessTime);
  490.         }
  491.         if (creationTime != null) {
  492.             ntfsTimestamp.setCreateFileTime(creationTime);
  493.         }
  494.         internalAddExtraField(ntfsTimestamp);
  495.     }

  496.     /**
  497.      * Overwrite clone.
  498.      *
  499.      * @return a cloned copy of this ZipArchiveEntry
  500.      */
  501.     @Override
  502.     public Object clone() {
  503.         final ZipArchiveEntry e = (ZipArchiveEntry) super.clone();

  504.         e.setInternalAttributes(getInternalAttributes());
  505.         e.setExternalAttributes(getExternalAttributes());
  506.         e.setExtraFields(getAllExtraFieldsNoCopy());
  507.         return e;
  508.     }

  509.     private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) {
  510.         return Arrays.copyOf(src, length);
  511.     }

  512.     @Override
  513.     public boolean equals(final Object obj) {
  514.         if (this == obj) {
  515.             return true;
  516.         }
  517.         if (obj == null || getClass() != obj.getClass()) {
  518.             return false;
  519.         }
  520.         final ZipArchiveEntry other = (ZipArchiveEntry) obj;
  521.         final String myName = getName();
  522.         final String otherName = other.getName();
  523.         if (!Objects.equals(myName, otherName)) {
  524.             return false;
  525.         }
  526.         String myComment = getComment();
  527.         String otherComment = other.getComment();
  528.         if (myComment == null) {
  529.             myComment = "";
  530.         }
  531.         if (otherComment == null) {
  532.             otherComment = "";
  533.         }
  534.         return Objects.equals(getLastModifiedTime(), other.getLastModifiedTime()) && Objects.equals(getLastAccessTime(), other.getLastAccessTime())
  535.                 && Objects.equals(getCreationTime(), other.getCreationTime()) && myComment.equals(otherComment)
  536.                 && getInternalAttributes() == other.getInternalAttributes() && getPlatform() == other.getPlatform()
  537.                 && getExternalAttributes() == other.getExternalAttributes() && getMethod() == other.getMethod() && getSize() == other.getSize()
  538.                 && getCrc() == other.getCrc() && getCompressedSize() == other.getCompressedSize()
  539.                 && Arrays.equals(getCentralDirectoryExtra(), other.getCentralDirectoryExtra())
  540.                 && Arrays.equals(getLocalFileDataExtra(), other.getLocalFileDataExtra()) && localHeaderOffset == other.localHeaderOffset
  541.                 && dataOffset == other.dataOffset && generalPurposeBit.equals(other.generalPurposeBit);
  542.     }

  543.     private ZipExtraField findMatching(final ZipShort headerId, final List<ZipExtraField> fs) {
  544.         return fs.stream().filter(f -> headerId.equals(f.getHeaderId())).findFirst().orElse(null);
  545.     }

  546.     private ZipExtraField findUnparseable(final List<ZipExtraField> fs) {
  547.         return fs.stream().filter(UnparseableExtraFieldData.class::isInstance).findFirst().orElse(null);
  548.     }

  549.     /**
  550.      * Gets currently configured alignment.
  551.      *
  552.      * @return alignment for this entry.
  553.      * @since 1.14
  554.      */
  555.     protected int getAlignment() {
  556.         return this.alignment;
  557.     }

  558.     private ZipExtraField[] getAllExtraFields() {
  559.         final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy();
  560.         return allExtraFieldsNoCopy == extraFields ? copyOf(allExtraFieldsNoCopy, allExtraFieldsNoCopy.length) : allExtraFieldsNoCopy;
  561.     }

  562.     /**
  563.      * Gets all extra fields, including unparseable ones.
  564.      *
  565.      * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method
  566.      */
  567.     private ZipExtraField[] getAllExtraFieldsNoCopy() {
  568.         if (extraFields == null) {
  569.             return getUnparseableOnly();
  570.         }
  571.         return unparseableExtra != null ? getMergedFields() : extraFields;
  572.     }

  573.     /**
  574.      * Retrieves the extra data for the central directory.
  575.      *
  576.      * @return the central directory extra data
  577.      */
  578.     public byte[] getCentralDirectoryExtra() {
  579.         return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy());
  580.     }

  581.     /**
  582.      * The source of the comment field value.
  583.      *
  584.      * @return source of the comment field value
  585.      * @since 1.16
  586.      */
  587.     public CommentSource getCommentSource() {
  588.         return commentSource;
  589.     }

  590.     @Override
  591.     public long getDataOffset() {
  592.         return dataOffset;
  593.     }

  594.     /**
  595.      * The number of the split segment this entry starts at.
  596.      *
  597.      * @return the number of the split segment this entry starts at.
  598.      * @since 1.20
  599.      */
  600.     public long getDiskNumberStart() {
  601.         return diskNumberStart;
  602.     }

  603.     /**
  604.      * Retrieves the external file attributes.
  605.      *
  606.      * <p>
  607.      * <b>Note</b>: {@link ZipArchiveInputStream} is unable to fill this field, you must use {@link ZipFile} if you want to read entries using this attribute.
  608.      * </p>
  609.      *
  610.      * @return the external file attributes
  611.      */
  612.     public long getExternalAttributes() {
  613.         return externalAttributes;
  614.     }

  615.     /**
  616.      * Gets an extra field by its header id.
  617.      *
  618.      * @param type the header id
  619.      * @return null if no such field exists.
  620.      */
  621.     public ZipExtraField getExtraField(final ZipShort type) {
  622.         if (extraFields != null) {
  623.             for (final ZipExtraField extraField : extraFields) {
  624.                 if (type.equals(extraField.getHeaderId())) {
  625.                     return extraField;
  626.                 }
  627.             }
  628.         }
  629.         return null;
  630.     }

  631.     /**
  632.      * Gets all extra fields that have been parsed successfully.
  633.      *
  634.      * <p>
  635.      * <b>Note</b>: The set of extra fields may be incomplete when {@link ZipArchiveInputStream} has been used as some extra fields use the central directory to
  636.      * store additional information.
  637.      * </p>
  638.      *
  639.      * @return an array of the extra fields
  640.      */
  641.     public ZipExtraField[] getExtraFields() {
  642.         return getParseableExtraFields();
  643.     }

  644.     /**
  645.      * Gets extra fields.
  646.      *
  647.      * @param includeUnparseable whether to also return unparseable extra fields as {@link UnparseableExtraFieldData} if such data exists.
  648.      * @return an array of the extra fields
  649.      *
  650.      * @since 1.1
  651.      */
  652.     public ZipExtraField[] getExtraFields(final boolean includeUnparseable) {
  653.         return includeUnparseable ? getAllExtraFields() : getParseableExtraFields();
  654.     }

  655.     /**
  656.      * Gets extra fields.
  657.      *
  658.      * @param parsingBehavior controls parsing of extra fields.
  659.      * @return an array of the extra fields
  660.      * @throws ZipException if parsing fails, can not happen if {@code
  661.      * parsingBehavior}  is {@link ExtraFieldParsingMode#BEST_EFFORT}.
  662.      * @since 1.19
  663.      */
  664.     public ZipExtraField[] getExtraFields(final ExtraFieldParsingBehavior parsingBehavior) throws ZipException {
  665.         if (parsingBehavior == ExtraFieldParsingMode.BEST_EFFORT) {
  666.             return getExtraFields(true);
  667.         }
  668.         if (parsingBehavior == ExtraFieldParsingMode.ONLY_PARSEABLE_LENIENT) {
  669.             return getExtraFields(false);
  670.         }
  671.         final byte[] local = getExtra();
  672.         final List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(parseExtraFields(local, true, parsingBehavior)));
  673.         final byte[] central = getCentralDirectoryExtra();
  674.         final List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(parseExtraFields(central, false, parsingBehavior)));
  675.         final List<ZipExtraField> merged = new ArrayList<>();
  676.         for (final ZipExtraField l : localFields) {
  677.             ZipExtraField c;
  678.             if (l instanceof UnparseableExtraFieldData) {
  679.                 c = findUnparseable(centralFields);
  680.             } else {
  681.                 c = findMatching(l.getHeaderId(), centralFields);
  682.             }
  683.             if (c != null) {
  684.                 final byte[] cd = c.getCentralDirectoryData();
  685.                 if (cd != null && cd.length > 0) {
  686.                     l.parseFromCentralDirectoryData(cd, 0, cd.length);
  687.                 }
  688.                 centralFields.remove(c);
  689.             }
  690.             merged.add(l);
  691.         }
  692.         merged.addAll(centralFields);
  693.         return merged.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY);
  694.     }

  695.     /**
  696.      * The "general purpose bit" field.
  697.      *
  698.      * @return the general purpose bit
  699.      * @since 1.1
  700.      */
  701.     public GeneralPurposeBit getGeneralPurposeBit() {
  702.         return generalPurposeBit;
  703.     }

  704.     /**
  705.      * Gets the internal file attributes.
  706.      *
  707.      * <p>
  708.      * <b>Note</b>: {@link ZipArchiveInputStream} is unable to fill this field, you must use {@link ZipFile} if you want to read entries using this attribute.
  709.      * </p>
  710.      *
  711.      * @return the internal file attributes
  712.      */
  713.     public int getInternalAttributes() {
  714.         return internalAttributes;
  715.     }

  716.     /**
  717.      * Wraps {@link ZipEntry#getTime} with a {@link Date} as the entry's last modified date.
  718.      *
  719.      * <p>
  720.      * Changes to the implementation of {@link ZipEntry#getTime()} leak through and the returned value may depend on your local time zone as well as
  721.      * your version of Java.
  722.      * </p>
  723.      */
  724.     @Override
  725.     public Date getLastModifiedDate() {
  726.         return new Date(getTime());
  727.     }

  728.     /**
  729.      * Gets the extra data for the local file data.
  730.      *
  731.      * @return the extra data for local file
  732.      */
  733.     public byte[] getLocalFileDataExtra() {
  734.         final byte[] extra = getExtra();
  735.         return extra != null ? extra : ByteUtils.EMPTY_BYTE_ARRAY;
  736.     }

  737.     /**
  738.      * Gets the local header offset.
  739.      *
  740.      * @return the local header offset.
  741.      * @since 1.24.0
  742.      */
  743.     public long getLocalHeaderOffset() {
  744.         return this.localHeaderOffset;
  745.     }

  746.     private ZipExtraField[] getMergedFields() {
  747.         final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
  748.         zipExtraFields[extraFields.length] = unparseableExtra;
  749.         return zipExtraFields;
  750.     }

  751.     /**
  752.      * Gets the compression method of this entry, or -1 if the compression method has not been specified.
  753.      *
  754.      * @return compression method
  755.      *
  756.      * @since 1.1
  757.      */
  758.     @Override
  759.     public int getMethod() {
  760.         return method;
  761.     }

  762.     /**
  763.      * Gets the name of the entry.
  764.      *
  765.      * <p>
  766.      * This method returns the raw name as it is stored inside of the archive.
  767.      * </p>
  768.      *
  769.      * @return the entry name
  770.      */
  771.     @Override
  772.     public String getName() {
  773.         return name == null ? super.getName() : name;
  774.     }

  775.     /**
  776.      * The source of the name field value.
  777.      *
  778.      * @return source of the name field value
  779.      * @since 1.16
  780.      */
  781.     public NameSource getNameSource() {
  782.         return nameSource;
  783.     }

  784.     private ZipExtraField[] getParseableExtraFields() {
  785.         final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy();
  786.         return parseableExtraFields == extraFields ? copyOf(parseableExtraFields, parseableExtraFields.length) : parseableExtraFields;
  787.     }

  788.     private ZipExtraField[] getParseableExtraFieldsNoCopy() {
  789.         if (extraFields == null) {
  790.             return ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY;
  791.         }
  792.         return extraFields;
  793.     }

  794.     /**
  795.      * Platform specification to put into the &quot;version made by&quot; part of the central file header.
  796.      *
  797.      * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} has been called, in which case PLATFORM_UNIX will be returned.
  798.      */
  799.     public int getPlatform() {
  800.         return platform;
  801.     }

  802.     /**
  803.      * The content of the flags field.
  804.      *
  805.      * @return content of the flags field
  806.      * @since 1.11
  807.      */
  808.     public int getRawFlag() {
  809.         return rawFlag;
  810.     }

  811.     /**
  812.      * Returns the raw bytes that made up the name before it has been converted using the configured or guessed encoding.
  813.      *
  814.      * <p>
  815.      * This method will return null if this instance has not been read from an archive.
  816.      * </p>
  817.      *
  818.      * @return the raw name bytes
  819.      * @since 1.2
  820.      */
  821.     public byte[] getRawName() {
  822.         if (rawName != null) {
  823.             return Arrays.copyOf(rawName, rawName.length);
  824.         }
  825.         return null;
  826.     }

  827.     /**
  828.      * Gets the uncompressed size of the entry data.
  829.      *
  830.      * <p>
  831.      * <b>Note</b>: {@link ZipArchiveInputStream} may create entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long as the entry hasn't been read
  832.      * completely.
  833.      * </p>
  834.      *
  835.      * @return the entry size
  836.      */
  837.     @Override
  838.     public long getSize() {
  839.         return size;
  840.     }

  841.     /**
  842.      * {@inheritDoc}
  843.      *
  844.      * <p>
  845.      * Override to work around bug <a href="https://bugs.openjdk.org/browse/JDK-8130914">JDK-8130914</a>
  846.      * </p>
  847.      *
  848.      * @return The last modification time of the entry in milliseconds since the epoch, or -1 if not specified
  849.      *
  850.      * @see #setTime(long)
  851.      * @see #setLastModifiedTime(FileTime)
  852.      */
  853.     @Override
  854.     public long getTime() {
  855.         if (lastModifiedDateSet) {
  856.             return getLastModifiedTime().toMillis();
  857.         }
  858.         return time != -1 ? time : super.getTime();
  859.     }

  860.     /**
  861.      * Gets the UNIX permission.
  862.      *
  863.      * @return the unix permissions
  864.      */
  865.     public int getUnixMode() {
  866.         return platform != PLATFORM_UNIX ? 0 : (int) (getExternalAttributes() >> SHORT_SHIFT & SHORT_MASK);
  867.     }

  868.     /**
  869.      * Gets up extra field data that couldn't be parsed correctly.
  870.      *
  871.      * @return null if no such field exists.
  872.      *
  873.      * @since 1.1
  874.      */
  875.     public UnparseableExtraFieldData getUnparseableExtraFieldData() {
  876.         return unparseableExtra;
  877.     }

  878.     private ZipExtraField[] getUnparseableOnly() {
  879.         return unparseableExtra == null ? ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY : new ZipExtraField[] { unparseableExtra };
  880.     }

  881.     /**
  882.      * Gets the "version made by" field.
  883.      *
  884.      * @return "version made by" field
  885.      * @since 1.11
  886.      */
  887.     public int getVersionMadeBy() {
  888.         return versionMadeBy;
  889.     }

  890.     /**
  891.      * Gets the "version required to expand" field.
  892.      *
  893.      * @return "version required to expand" field
  894.      * @since 1.11
  895.      */
  896.     public int getVersionRequired() {
  897.         return versionRequired;
  898.     }

  899.     /**
  900.      * Gets the hash code of the entry. This uses the name as the hash code.
  901.      *
  902.      * @return a hash code.
  903.      */
  904.     @Override
  905.     public int hashCode() {
  906.         // this method has severe consequences on performance. We cannot rely
  907.         // on the super.hashCode() method since super.getName() always return
  908.         // the empty string in the current implementation (there's no setter)
  909.         // so it is basically draining the performance of a hashmap lookup
  910.         return getName().hashCode();
  911.     }

  912.     private void internalAddExtraField(final ZipExtraField ze) {
  913.         if (ze instanceof UnparseableExtraFieldData) {
  914.             unparseableExtra = (UnparseableExtraFieldData) ze;
  915.         } else if (extraFields == null) {
  916.             extraFields = new ZipExtraField[] { ze };
  917.         } else {
  918.             if (getExtraField(ze.getHeaderId()) != null) {
  919.                 internalRemoveExtraField(ze.getHeaderId());
  920.             }
  921.             final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
  922.             zipExtraFields[zipExtraFields.length - 1] = ze;
  923.             extraFields = zipExtraFields;
  924.         }
  925.     }

  926.     private void internalRemoveExtraField(final ZipShort type) {
  927.         if (extraFields == null) {
  928.             return;
  929.         }
  930.         final List<ZipExtraField> newResult = new ArrayList<>();
  931.         for (final ZipExtraField extraField : extraFields) {
  932.             if (!type.equals(extraField.getHeaderId())) {
  933.                 newResult.add(extraField);
  934.             }
  935.         }
  936.         if (extraFields.length == newResult.size()) {
  937.             return;
  938.         }
  939.         extraFields = newResult.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY);
  940.     }

  941.     private void internalSetLastModifiedTime(final FileTime time) {
  942.         super.setLastModifiedTime(time);
  943.         this.time = time.toMillis();
  944.         lastModifiedDateSet = true;
  945.     }

  946.     /**
  947.      * Is this entry a directory?
  948.      *
  949.      * @return true if the entry is a directory
  950.      */
  951.     @Override
  952.     public boolean isDirectory() {
  953.         return isDirectoryEntryName(getName());
  954.     }

  955.     @Override
  956.     public boolean isStreamContiguous() {
  957.         return isStreamContiguous;
  958.     }

  959.     /**
  960.      * Returns true if this entry represents a unix symlink, in which case the entry's content contains the target path for the symlink.
  961.      *
  962.      * @since 1.5
  963.      * @return true if the entry represents a unix symlink, false otherwise.
  964.      */
  965.     public boolean isUnixSymlink() {
  966.         return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG;
  967.     }

  968.     /**
  969.      * 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
  970.      * from different locations inside the archive.
  971.      *
  972.      * @param f     the extra fields to merge
  973.      * @param local whether the new fields originate from local data
  974.      */
  975.     private void mergeExtraFields(final ZipExtraField[] f, final boolean local) {
  976.         if (extraFields == null) {
  977.             setExtraFields(f);
  978.         } else {
  979.             for (final ZipExtraField element : f) {
  980.                 final ZipExtraField existing;
  981.                 if (element instanceof UnparseableExtraFieldData) {
  982.                     existing = unparseableExtra;
  983.                 } else {
  984.                     existing = getExtraField(element.getHeaderId());
  985.                 }
  986.                 if (existing == null) {
  987.                     internalAddExtraField(element);
  988.                 } else {
  989.                     final byte[] b = local ? element.getLocalFileDataData() : element.getCentralDirectoryData();
  990.                     try {
  991.                         if (local) {
  992.                             existing.parseFromLocalFileData(b, 0, b.length);
  993.                         } else {
  994.                             existing.parseFromCentralDirectoryData(b, 0, b.length);
  995.                         }
  996.                     } catch (final ZipException ex) {
  997.                         // emulate ExtraFieldParsingMode.fillAndMakeUnrecognizedOnError
  998.                         final UnrecognizedExtraField u = new UnrecognizedExtraField();
  999.                         u.setHeaderId(existing.getHeaderId());
  1000.                         if (local) {
  1001.                             u.setLocalFileDataData(b);
  1002.                             u.setCentralDirectoryData(existing.getCentralDirectoryData());
  1003.                         } else {
  1004.                             u.setLocalFileDataData(existing.getLocalFileDataData());
  1005.                             u.setCentralDirectoryData(b);
  1006.                         }
  1007.                         internalRemoveExtraField(existing.getHeaderId());
  1008.                         internalAddExtraField(u);
  1009.                     }
  1010.                 }
  1011.             }
  1012.             setExtra();
  1013.         }
  1014.     }

  1015.     private ZipExtraField[] parseExtraFields(final byte[] data, final boolean local, final ExtraFieldParsingBehavior parsingBehavior) throws ZipException {
  1016.         if (extraFieldFactory != null) {
  1017.             return ExtraFieldUtils.parse(data, local, new ExtraFieldParsingBehavior() {
  1018.                 @Override
  1019.                 public ZipExtraField createExtraField(final ZipShort headerId) throws ZipException, InstantiationException, IllegalAccessException {
  1020.                     final ZipExtraField field = extraFieldFactory.apply(headerId);
  1021.                     return field == null ? parsingBehavior.createExtraField(headerId) : field;
  1022.                 }

  1023.                 @Override
  1024.                 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException {
  1025.                     return parsingBehavior.fill(field, data, off, len, local);
  1026.                 }

  1027.                 @Override
  1028.                 public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength)
  1029.                         throws ZipException {
  1030.                     return parsingBehavior.onUnparseableExtraField(data, off, len, local, claimedLength);
  1031.                 }
  1032.             });
  1033.         }
  1034.         return ExtraFieldUtils.parse(data, local, parsingBehavior);
  1035.     }

  1036.     /**
  1037.      * Remove an extra field.
  1038.      *
  1039.      * @param type the type of extra field to remove
  1040.      */
  1041.     public void removeExtraField(final ZipShort type) {
  1042.         if (getExtraField(type) == null) {
  1043.             throw new NoSuchElementException();
  1044.         }
  1045.         internalRemoveExtraField(type);
  1046.         setExtra();
  1047.     }

  1048.     /**
  1049.      * Removes unparseable extra field data.
  1050.      *
  1051.      * @since 1.1
  1052.      */
  1053.     public void removeUnparseableExtraFieldData() {
  1054.         if (unparseableExtra == null) {
  1055.             throw new NoSuchElementException();
  1056.         }
  1057.         unparseableExtra = null;
  1058.         setExtra();
  1059.     }

  1060.     private boolean requiresExtraTimeFields() {
  1061.         if (getLastAccessTime() != null || getCreationTime() != null) {
  1062.             return true;
  1063.         }
  1064.         return lastModifiedDateSet;
  1065.     }

  1066.     /**
  1067.      * Sets alignment for this entry.
  1068.      *
  1069.      * @param alignment requested alignment, 0 for default.
  1070.      * @since 1.14
  1071.      */
  1072.     public void setAlignment(final int alignment) {
  1073.         if ((alignment & alignment - 1) != 0 || alignment > 0xffff) {
  1074.             throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than " + 0xffff + " but is " + alignment);
  1075.         }
  1076.         this.alignment = alignment;
  1077.     }

  1078.     private void setAttributes(final Path inputPath, final LinkOption... options) throws IOException {
  1079.         final BasicFileAttributes attributes = Files.readAttributes(inputPath, BasicFileAttributes.class, options);
  1080.         if (attributes.isRegularFile()) {
  1081.             setSize(attributes.size());
  1082.         }
  1083.         super.setLastModifiedTime(attributes.lastModifiedTime());
  1084.         super.setCreationTime(attributes.creationTime());
  1085.         super.setLastAccessTime(attributes.lastAccessTime());
  1086.         setExtraTimeFields();
  1087.     }

  1088.     /**
  1089.      * Sets the central directory part of extra fields.
  1090.      *
  1091.      * @param b an array of bytes to be parsed into extra fields
  1092.      */
  1093.     public void setCentralDirectoryExtra(final byte[] b) {
  1094.         try {
  1095.             mergeExtraFields(parseExtraFields(b, false, ExtraFieldParsingMode.BEST_EFFORT), false);
  1096.         } catch (final ZipException e) {
  1097.             // actually this is not possible as of Commons Compress 1.19
  1098.             throw new IllegalArgumentException(e.getMessage(), e); // NOSONAR
  1099.         }
  1100.     }

  1101.     /**
  1102.      * Sets the source of the comment field value.
  1103.      *
  1104.      * @param commentSource source of the comment field value
  1105.      * @since 1.16
  1106.      */
  1107.     public void setCommentSource(final CommentSource commentSource) {
  1108.         this.commentSource = commentSource;
  1109.     }

  1110.     @Override
  1111.     public ZipEntry setCreationTime(final FileTime time) {
  1112.         super.setCreationTime(time);
  1113.         setExtraTimeFields();
  1114.         return this;
  1115.     }
  1116.     /*
  1117.      * (non-Javadoc)
  1118.      *
  1119.      * @see Object#equals(Object)
  1120.      */

  1121.     /**
  1122.      * Sets the data offset.
  1123.      *
  1124.      * @param dataOffset new value of data offset.
  1125.      */
  1126.     protected void setDataOffset(final long dataOffset) {
  1127.         this.dataOffset = dataOffset;
  1128.     }

  1129.     /**
  1130.      * The number of the split segment this entry starts at.
  1131.      *
  1132.      * @param diskNumberStart the number of the split segment this entry starts at.
  1133.      * @since 1.20
  1134.      */
  1135.     public void setDiskNumberStart(final long diskNumberStart) {
  1136.         this.diskNumberStart = diskNumberStart;
  1137.     }

  1138.     /**
  1139.      * Sets the external file attributes.
  1140.      *
  1141.      * @param value an {@code long} value
  1142.      */
  1143.     public void setExternalAttributes(final long value) {
  1144.         externalAttributes = value;
  1145.     }

  1146.     /**
  1147.      * Unfortunately {@link java.util.zip.ZipOutputStream} seems to access the extra data directly, so overriding getExtra doesn't help - we need to modify
  1148.      * super's data directly and on every update.
  1149.      */
  1150.     protected void setExtra() {
  1151.         // ZipEntry will update the time fields here, so we need to reprocess them afterwards
  1152.         super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy()));
  1153.         // Reprocess and overwrite the modifications made by ZipEntry#setExtra(byte[])
  1154.         updateTimeFieldsFromExtraFields();
  1155.     }

  1156.     /**
  1157.      * Parses the given bytes as extra field data and consumes any unparseable data as an {@link UnparseableExtraFieldData} instance.
  1158.      *
  1159.      * @param extra an array of bytes to be parsed into extra fields
  1160.      * @throws RuntimeException if the bytes cannot be parsed
  1161.      * @throws RuntimeException on error
  1162.      */
  1163.     @Override
  1164.     public void setExtra(final byte[] extra) throws RuntimeException {
  1165.         try {
  1166.             mergeExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT), true);
  1167.         } catch (final ZipException e) {
  1168.             // actually this is not possible as of Commons Compress 1.1
  1169.             throw new IllegalArgumentException("Error parsing extra fields for entry: " // NOSONAR
  1170.                     + getName() + " - " + e.getMessage(), e);
  1171.         }
  1172.     }

  1173.     /**
  1174.      * Replaces all currently attached extra fields with the new array.
  1175.      *
  1176.      * @param fields an array of extra fields
  1177.      */
  1178.     public void setExtraFields(final ZipExtraField[] fields) {
  1179.         unparseableExtra = null;
  1180.         final List<ZipExtraField> newFields = new ArrayList<>();
  1181.         if (fields != null) {
  1182.             for (final ZipExtraField field : fields) {
  1183.                 if (field instanceof UnparseableExtraFieldData) {
  1184.                     unparseableExtra = (UnparseableExtraFieldData) field;
  1185.                 } else {
  1186.                     newFields.add(field);
  1187.                 }
  1188.             }
  1189.         }
  1190.         extraFields = newFields.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY);
  1191.         setExtra();
  1192.     }

  1193.     private void setExtraTimeFields() {
  1194.         if (getExtraField(X5455_ExtendedTimestamp.HEADER_ID) != null) {
  1195.             internalRemoveExtraField(X5455_ExtendedTimestamp.HEADER_ID);
  1196.         }
  1197.         if (getExtraField(X000A_NTFS.HEADER_ID) != null) {
  1198.             internalRemoveExtraField(X000A_NTFS.HEADER_ID);
  1199.         }
  1200.         if (requiresExtraTimeFields()) {
  1201.             final FileTime lastModifiedTime = getLastModifiedTime();
  1202.             final FileTime lastAccessTime = getLastAccessTime();
  1203.             final FileTime creationTime = getCreationTime();
  1204.             if (canConvertToInfoZipExtendedTimestamp(lastModifiedTime, lastAccessTime, creationTime)) {
  1205.                 addInfoZipExtendedTimestamp(lastModifiedTime, lastAccessTime, creationTime);
  1206.             }
  1207.             addNTFSTimestamp(lastModifiedTime, lastAccessTime, creationTime);
  1208.         }
  1209.         setExtra();
  1210.     }

  1211.     /**
  1212.      * Sets the "general purpose bit" field.
  1213.      *
  1214.      * @param generalPurposeBit the general purpose bit
  1215.      * @since 1.1
  1216.      */
  1217.     public void setGeneralPurposeBit(final GeneralPurposeBit generalPurposeBit) {
  1218.         this.generalPurposeBit = generalPurposeBit;
  1219.     }

  1220.     /**
  1221.      * Sets the internal file attributes.
  1222.      *
  1223.      * @param internalAttributes an {@code int} value
  1224.      */
  1225.     public void setInternalAttributes(final int internalAttributes) {
  1226.         this.internalAttributes = internalAttributes;
  1227.     }

  1228.     @Override
  1229.     public ZipEntry setLastAccessTime(final FileTime fileTime) {
  1230.         super.setLastAccessTime(fileTime);
  1231.         setExtraTimeFields();
  1232.         return this;
  1233.     }

  1234.     @Override
  1235.     public ZipEntry setLastModifiedTime(final FileTime fileTime) {
  1236.         internalSetLastModifiedTime(fileTime);
  1237.         setExtraTimeFields();
  1238.         return this;
  1239.     }

  1240.     protected void setLocalHeaderOffset(final long localHeaderOffset) {
  1241.         this.localHeaderOffset = localHeaderOffset;
  1242.     }

  1243.     /**
  1244.      * Sets the compression method of this entry.
  1245.      *
  1246.      * @param method compression method
  1247.      *
  1248.      * @since 1.1
  1249.      */
  1250.     @Override
  1251.     public void setMethod(final int method) {
  1252.         if (method < 0) {
  1253.             throw new IllegalArgumentException("ZIP compression method can not be negative: " + method);
  1254.         }
  1255.         this.method = method;
  1256.     }

  1257.     /**
  1258.      * Sets the name of the entry.
  1259.      *
  1260.      * @param name the name to use
  1261.      */
  1262.     protected void setName(String name) {
  1263.         if (name != null && getPlatform() == PLATFORM_FAT && !name.contains(ZIP_DIR_SEP)) {
  1264.             name = name.replace('\\', '/');
  1265.         }
  1266.         this.name = name;
  1267.     }

  1268.     /**
  1269.      * Sets the name using the raw bytes and the string created from it by guessing or using the configured encoding.
  1270.      *
  1271.      * @param name    the name to use created from the raw bytes using the guessed or configured encoding
  1272.      * @param rawName the bytes originally read as name from the archive
  1273.      * @since 1.2
  1274.      */
  1275.     protected void setName(final String name, final byte[] rawName) {
  1276.         setName(name);
  1277.         this.rawName = rawName;
  1278.     }

  1279.     /**
  1280.      * Sets the source of the name field value.
  1281.      *
  1282.      * @param nameSource source of the name field value
  1283.      * @since 1.16
  1284.      */
  1285.     public void setNameSource(final NameSource nameSource) {
  1286.         this.nameSource = nameSource;
  1287.     }

  1288.     /**
  1289.      * Sets the platform (UNIX or FAT).
  1290.      *
  1291.      * @param platform an {@code int} value - 0 is FAT, 3 is UNIX
  1292.      */
  1293.     protected void setPlatform(final int platform) {
  1294.         this.platform = platform;
  1295.     }

  1296.     /**
  1297.      * Sets the content of the flags field.
  1298.      *
  1299.      * @param rawFlag content of the flags field
  1300.      * @since 1.11
  1301.      */
  1302.     public void setRawFlag(final int rawFlag) {
  1303.         this.rawFlag = rawFlag;
  1304.     }

  1305.     /**
  1306.      * Sets the uncompressed size of the entry data.
  1307.      *
  1308.      * @param size the uncompressed size in bytes
  1309.      * @throws IllegalArgumentException if the specified size is less than 0
  1310.      */
  1311.     @Override
  1312.     public void setSize(final long size) {
  1313.         if (size < 0) {
  1314.             throw new IllegalArgumentException("Invalid entry size");
  1315.         }
  1316.         this.size = size;
  1317.     }

  1318.     protected void setStreamContiguous(final boolean isStreamContiguous) {
  1319.         this.isStreamContiguous = isStreamContiguous;
  1320.     }

  1321.     /**
  1322.      * Sets the modification time of the entry.
  1323.      *
  1324.      * @param fileTime the entry modification time.
  1325.      * @since 1.21
  1326.      */
  1327.     public void setTime(final FileTime fileTime) {
  1328.         setTime(fileTime.toMillis());
  1329.     }

  1330.     /**
  1331.      *
  1332.      * {@inheritDoc}
  1333.      *
  1334.      * <p>
  1335.      * Override to work around bug <a href="https://bugs.openjdk.org/browse/JDK-8130914">JDK-8130914</a>
  1336.      * </p>
  1337.      *
  1338.      * @param timeEpochMillis The last modification time of the entry in milliseconds since the epoch.
  1339.      * @see #getTime()
  1340.      * @see #getLastModifiedTime()
  1341.      */
  1342.     @Override
  1343.     public void setTime(final long timeEpochMillis) {
  1344.         if (ZipUtil.isDosTime(timeEpochMillis)) {
  1345.             super.setTime(timeEpochMillis);
  1346.             this.time = timeEpochMillis;
  1347.             lastModifiedDateSet = false;
  1348.             setExtraTimeFields();
  1349.         } else {
  1350.             setLastModifiedTime(FileTime.fromMillis(timeEpochMillis));
  1351.         }
  1352.     }

  1353.     /**
  1354.      * Sets UNIX permissions in a way that is understood by Info-Zip's unzip command.
  1355.      *
  1356.      * @param mode an {@code int} value
  1357.      */
  1358.     public void setUnixMode(final int mode) {
  1359.         // CheckStyle:MagicNumberCheck OFF - no point
  1360.         setExternalAttributes(mode << SHORT_SHIFT
  1361.                 // MS-DOS read-only attribute
  1362.                 | ((mode & 0200) == 0 ? 1 : 0)
  1363.                 // MS-DOS directory flag
  1364.                 | (isDirectory() ? 0x10 : 0));
  1365.         // CheckStyle:MagicNumberCheck ON
  1366.         platform = PLATFORM_UNIX;
  1367.     }

  1368.     /**
  1369.      * Sets the "version made by" field.
  1370.      *
  1371.      * @param versionMadeBy "version made by" field
  1372.      * @since 1.11
  1373.      */
  1374.     public void setVersionMadeBy(final int versionMadeBy) {
  1375.         this.versionMadeBy = versionMadeBy;
  1376.     }

  1377.     /**
  1378.      * Sets the "version required to expand" field.
  1379.      *
  1380.      * @param versionRequired "version required to expand" field
  1381.      * @since 1.11
  1382.      */
  1383.     public void setVersionRequired(final int versionRequired) {
  1384.         this.versionRequired = versionRequired;
  1385.     }

  1386.     private void updateTimeFieldsFromExtraFields() {
  1387.         // Update times from X5455_ExtendedTimestamp field
  1388.         updateTimeFromExtendedTimestampField();
  1389.         // Update times from X000A_NTFS field, overriding X5455_ExtendedTimestamp if both are present
  1390.         updateTimeFromNtfsField();
  1391.     }

  1392.     /**
  1393.      * Workaround for the fact that, as of Java 17, {@link ZipEntry} does not properly modify the entry's {@code xdostime} field, only setting
  1394.      * {@code mtime}. While this is not strictly necessary, it's better to maintain the same behavior between this and the NTFS field.
  1395.      */
  1396.     private void updateTimeFromExtendedTimestampField() {
  1397.         final ZipExtraField extraField = getExtraField(X5455_ExtendedTimestamp.HEADER_ID);
  1398.         if (extraField instanceof X5455_ExtendedTimestamp) {
  1399.             final X5455_ExtendedTimestamp extendedTimestamp = (X5455_ExtendedTimestamp) extraField;
  1400.             if (extendedTimestamp.isBit0_modifyTimePresent()) {
  1401.                 final FileTime modifyTime = extendedTimestamp.getModifyFileTime();
  1402.                 if (modifyTime != null) {
  1403.                     internalSetLastModifiedTime(modifyTime);
  1404.                 }
  1405.             }
  1406.             if (extendedTimestamp.isBit1_accessTimePresent()) {
  1407.                 final FileTime accessTime = extendedTimestamp.getAccessFileTime();
  1408.                 if (accessTime != null) {
  1409.                     super.setLastAccessTime(accessTime);
  1410.                 }
  1411.             }
  1412.             if (extendedTimestamp.isBit2_createTimePresent()) {
  1413.                 final FileTime creationTime = extendedTimestamp.getCreateFileTime();
  1414.                 if (creationTime != null) {
  1415.                     super.setCreationTime(creationTime);
  1416.                 }
  1417.             }
  1418.         }
  1419.     }

  1420.     /**
  1421.      * Workaround for the fact that, as of Java 17, {@link ZipEntry} parses NTFS timestamps with a maximum precision of microseconds, which is
  1422.      * lower than the 100ns precision provided by this extra field.
  1423.      */
  1424.     private void updateTimeFromNtfsField() {
  1425.         final ZipExtraField extraField = getExtraField(X000A_NTFS.HEADER_ID);
  1426.         if (extraField instanceof X000A_NTFS) {
  1427.             final X000A_NTFS ntfsTimestamp = (X000A_NTFS) extraField;
  1428.             final FileTime modifyTime = ntfsTimestamp.getModifyFileTime();
  1429.             if (modifyTime != null) {
  1430.                 internalSetLastModifiedTime(modifyTime);
  1431.             }
  1432.             final FileTime accessTime = ntfsTimestamp.getAccessFileTime();
  1433.             if (accessTime != null) {
  1434.                 super.setLastAccessTime(accessTime);
  1435.             }
  1436.             final FileTime creationTime = ntfsTimestamp.getCreateFileTime();
  1437.             if (creationTime != null) {
  1438.                 super.setCreationTime(creationTime);
  1439.             }
  1440.         }
  1441.     }
  1442. }