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 * < 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 "version made by" 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 }
1573