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