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