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