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 static org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp.ACCESS_TIME_BIT;
22 import static org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp.CREATE_TIME_BIT;
23 import static org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp.MODIFY_TIME_BIT;
24 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
25 import static org.junit.jupiter.api.Assertions.assertEquals;
26 import static org.junit.jupiter.api.Assertions.assertFalse;
27 import static org.junit.jupiter.api.Assertions.assertNotEquals;
28 import static org.junit.jupiter.api.Assertions.assertNotNull;
29 import static org.junit.jupiter.api.Assertions.assertNull;
30 import static org.junit.jupiter.api.Assertions.assertThrows;
31 import static org.junit.jupiter.api.Assertions.assertTrue;
32
33 import java.io.File;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.nio.file.Files;
37 import java.nio.file.attribute.FileTime;
38 import java.text.SimpleDateFormat;
39 import java.util.Calendar;
40 import java.util.Date;
41 import java.util.Enumeration;
42 import java.util.TimeZone;
43 import java.util.zip.ZipException;
44
45 import org.apache.commons.compress.AbstractTest;
46 import org.junit.jupiter.api.AfterEach;
47 import org.junit.jupiter.api.BeforeEach;
48 import org.junit.jupiter.api.Test;
49 import org.junit.jupiter.api.io.TempDir;
50
51 class X5455_ExtendedTimestampTest {
52 private static final ZipShort X5455 = new ZipShort(0x5455);
53
54 private static final ZipLong ZERO_TIME = new ZipLong(0);
55 private static final ZipLong MAX_TIME_SECONDS = new ZipLong(Integer.MAX_VALUE);
56 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd/HH:mm:ss Z");
57
58 static {
59 DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
60 }
61
62
63
64
65
66
67 private static Date adjustFromGMTToExpectedOffset(final Date from) {
68 final Calendar cal = Calendar.getInstance();
69 cal.setTime(from);
70 cal.add(Calendar.MILLISECOND, cal.get(Calendar.ZONE_OFFSET));
71 if (cal.getTimeZone().inDaylightTime(from)) {
72 cal.add(Calendar.MILLISECOND, cal.get(Calendar.DST_OFFSET));
73 }
74 cal.add(Calendar.HOUR, 8);
75 return cal.getTime();
76 }
77
78 private static boolean isFlagSet(final byte data, final byte flag) {
79 return (data & flag) == flag;
80 }
81
82
83
84
85 private X5455_ExtendedTimestamp xf;
86
87 @TempDir
88 private File tmpDir;
89
90 @BeforeEach
91 public void before() {
92 xf = new X5455_ExtendedTimestamp();
93 }
94
95 private void parseReparse(final byte providedFlags, final ZipLong time, final byte expectedFlags, final byte[] expectedLocal,
96 final byte[] almostExpectedCentral) throws ZipException {
97
98
99 final byte[] expectedCentral = new byte[almostExpectedCentral.length];
100 System.arraycopy(almostExpectedCentral, 0, expectedCentral, 0, almostExpectedCentral.length);
101 expectedCentral[0] = expectedFlags;
102
103 xf.setModifyTime(time);
104 xf.setAccessTime(time);
105 xf.setCreateTime(time);
106 xf.setFlags(providedFlags);
107 byte[] result = xf.getLocalFileDataData();
108 assertArrayEquals(expectedLocal, result);
109
110
111 xf.parseFromLocalFileData(result, 0, result.length);
112 assertEquals(expectedFlags, xf.getFlags());
113 if (isFlagSet(expectedFlags, MODIFY_TIME_BIT)) {
114 assertTrue(xf.isBit0_modifyTimePresent());
115 assertEquals(time, xf.getModifyTime());
116 }
117 if (isFlagSet(expectedFlags, ACCESS_TIME_BIT)) {
118 assertTrue(xf.isBit1_accessTimePresent());
119 assertEquals(time, xf.getAccessTime());
120 }
121 if (isFlagSet(expectedFlags, CREATE_TIME_BIT)) {
122 assertTrue(xf.isBit2_createTimePresent());
123 assertEquals(time, xf.getCreateTime());
124 }
125
126
127 xf.setModifyTime(time);
128 xf.setAccessTime(time);
129 xf.setCreateTime(time);
130 xf.setFlags(providedFlags);
131 result = xf.getCentralDirectoryData();
132 assertArrayEquals(expectedCentral, result);
133
134
135 xf.parseFromCentralDirectoryData(result, 0, result.length);
136 assertEquals(expectedFlags, xf.getFlags());
137
138
139 if (isFlagSet(expectedFlags, MODIFY_TIME_BIT)) {
140 assertTrue(xf.isBit0_modifyTimePresent());
141 assertEquals(time, xf.getModifyTime());
142 }
143 }
144
145 private void parseReparse(final ZipLong time, final byte[] expectedLocal, final byte[] almostExpectedCentral) throws ZipException {
146 parseReparse(expectedLocal[0], time, expectedLocal[0], expectedLocal, almostExpectedCentral);
147 }
148
149 @AfterEach
150 public void removeTempFiles() {
151 if (tmpDir != null) {
152 AbstractTest.forceDelete(tmpDir);
153 }
154 }
155
156 @Test
157 void testBitsAreSetWithTime() {
158 xf.setModifyJavaTime(new Date(1111));
159 assertTrue(xf.isBit0_modifyTimePresent());
160 assertEquals(1, xf.getFlags());
161 xf.setAccessJavaTime(new Date(2222));
162 assertTrue(xf.isBit1_accessTimePresent());
163 assertEquals(3, xf.getFlags());
164 xf.setCreateJavaTime(new Date(3333));
165 assertTrue(xf.isBit2_createTimePresent());
166 assertEquals(7, xf.getFlags());
167 xf.setModifyJavaTime(null);
168 assertFalse(xf.isBit0_modifyTimePresent());
169 assertEquals(6, xf.getFlags());
170 xf.setAccessJavaTime(null);
171 assertFalse(xf.isBit1_accessTimePresent());
172 assertEquals(4, xf.getFlags());
173 xf.setCreateJavaTime(null);
174 assertFalse(xf.isBit2_createTimePresent());
175 assertEquals(0, xf.getFlags());
176 }
177
178 @Test
179 void testGetHeaderId() {
180 assertEquals(X5455, xf.getHeaderId());
181 }
182
183 @Test
184 void testGettersSetters() {
185
186
187 final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
188 cal.set(Calendar.YEAR, 2000);
189 cal.set(Calendar.MONTH, Calendar.JANUARY);
190 cal.set(Calendar.DATE, 1);
191 cal.set(Calendar.HOUR_OF_DAY, 0);
192 cal.set(Calendar.MINUTE, 0);
193 cal.set(Calendar.SECOND, 0);
194 cal.set(Calendar.MILLISECOND, 0);
195 final long timeMillis = cal.getTimeInMillis();
196 final ZipLong time = new ZipLong(timeMillis / 1000);
197
198
199
200 assertThrows(IllegalArgumentException.class, () -> xf.setModifyJavaTime(new Date(1000L * (MAX_TIME_SECONDS.getValue() + 1L))),
201 "Time too big for 32 bits!");
202
203
204 xf.setModifyTime(time);
205 assertEquals(time, xf.getModifyTime());
206 assertEquals(timeMillis, xf.getModifyJavaTime().getTime());
207 assertEquals(timeMillis, xf.getModifyFileTime().toMillis());
208 assertTrue(xf.isBit0_modifyTimePresent());
209 xf.setModifyJavaTime(new Date(timeMillis));
210 assertEquals(time, xf.getModifyTime());
211 assertEquals(timeMillis, xf.getModifyJavaTime().getTime());
212 assertEquals(timeMillis, xf.getModifyFileTime().toMillis());
213 assertTrue(xf.isBit0_modifyTimePresent());
214
215 xf.setModifyJavaTime(new Date(timeMillis + 123));
216 assertEquals(time, xf.getModifyTime());
217 assertEquals(timeMillis, xf.getModifyJavaTime().getTime());
218 assertEquals(timeMillis, xf.getModifyFileTime().toMillis());
219 assertTrue(xf.isBit0_modifyTimePresent());
220
221 xf.setModifyFileTime(FileTime.fromMillis(timeMillis + 123));
222 assertEquals(time, xf.getModifyTime());
223 assertEquals(timeMillis, xf.getModifyJavaTime().getTime());
224 assertEquals(timeMillis, xf.getModifyFileTime().toMillis());
225 assertTrue(xf.isBit0_modifyTimePresent());
226
227 xf.setModifyTime(null);
228 assertNull(xf.getModifyJavaTime());
229 assertNull(xf.getModifyFileTime());
230 assertFalse(xf.isBit0_modifyTimePresent());
231 xf.setModifyJavaTime(null);
232 assertNull(xf.getModifyTime());
233 assertNull(xf.getModifyFileTime());
234 assertFalse(xf.isBit0_modifyTimePresent());
235 xf.setModifyFileTime(null);
236 assertNull(xf.getModifyJavaTime());
237 assertNull(xf.getModifyTime());
238 assertFalse(xf.isBit0_modifyTimePresent());
239
240
241 xf.setAccessTime(time);
242 assertEquals(time, xf.getAccessTime());
243 assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
244 assertEquals(timeMillis, xf.getAccessFileTime().toMillis());
245 assertTrue(xf.isBit1_accessTimePresent());
246 xf.setAccessJavaTime(new Date(timeMillis));
247 assertEquals(time, xf.getAccessTime());
248 assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
249 assertEquals(timeMillis, xf.getAccessFileTime().toMillis());
250 assertTrue(xf.isBit1_accessTimePresent());
251
252 xf.setAccessJavaTime(new Date(timeMillis + 123));
253 assertEquals(time, xf.getAccessTime());
254 assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
255 assertEquals(timeMillis, xf.getAccessFileTime().toMillis());
256 assertTrue(xf.isBit1_accessTimePresent());
257
258 xf.setAccessFileTime(FileTime.fromMillis(timeMillis + 123));
259 assertEquals(time, xf.getAccessTime());
260 assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
261 assertEquals(timeMillis, xf.getAccessFileTime().toMillis());
262 assertTrue(xf.isBit1_accessTimePresent());
263
264 xf.setAccessTime(null);
265 assertNull(xf.getAccessJavaTime());
266 assertNull(xf.getAccessFileTime());
267 assertFalse(xf.isBit1_accessTimePresent());
268 xf.setAccessJavaTime(null);
269 assertNull(xf.getAccessTime());
270 assertNull(xf.getAccessFileTime());
271 assertFalse(xf.isBit1_accessTimePresent());
272 xf.setAccessFileTime(null);
273 assertNull(xf.getAccessJavaTime());
274 assertNull(xf.getAccessTime());
275 assertFalse(xf.isBit1_accessTimePresent());
276
277
278 xf.setCreateTime(time);
279 assertEquals(time, xf.getCreateTime());
280 assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
281 assertEquals(timeMillis, xf.getCreateFileTime().toMillis());
282 assertTrue(xf.isBit2_createTimePresent());
283 xf.setCreateJavaTime(new Date(timeMillis));
284 assertEquals(time, xf.getCreateTime());
285 assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
286 assertEquals(timeMillis, xf.getCreateFileTime().toMillis());
287 assertTrue(xf.isBit2_createTimePresent());
288
289 xf.setCreateJavaTime(new Date(timeMillis + 123));
290 assertEquals(time, xf.getCreateTime());
291 assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
292 assertEquals(timeMillis, xf.getCreateFileTime().toMillis());
293 assertTrue(xf.isBit2_createTimePresent());
294
295 xf.setCreateFileTime(FileTime.fromMillis(timeMillis + 123));
296 assertEquals(time, xf.getCreateTime());
297 assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
298 assertEquals(timeMillis, xf.getCreateFileTime().toMillis());
299 assertTrue(xf.isBit2_createTimePresent());
300
301 xf.setCreateTime(null);
302 assertNull(xf.getCreateJavaTime());
303 assertNull(xf.getCreateFileTime());
304 assertFalse(xf.isBit2_createTimePresent());
305 xf.setCreateJavaTime(null);
306 assertNull(xf.getCreateTime());
307 assertNull(xf.getCreateFileTime());
308 assertFalse(xf.isBit2_createTimePresent());
309 xf.setCreateFileTime(null);
310 assertNull(xf.getCreateJavaTime());
311 assertNull(xf.getCreateTime());
312 assertFalse(xf.isBit2_createTimePresent());
313
314
315 xf.setModifyTime(time);
316 xf.setAccessTime(time);
317 xf.setCreateTime(time);
318
319
320 xf.setFlags((byte) 0);
321 assertEquals(0, xf.getFlags());
322 assertFalse(xf.isBit0_modifyTimePresent());
323 assertFalse(xf.isBit1_accessTimePresent());
324 assertFalse(xf.isBit2_createTimePresent());
325
326 assertEquals(1, xf.getLocalFileDataLength().getValue());
327 assertEquals(1, xf.getCentralDirectoryLength().getValue());
328
329
330 xf.setFlags((byte) 1);
331 assertEquals(1, xf.getFlags());
332 assertTrue(xf.isBit0_modifyTimePresent());
333 assertFalse(xf.isBit1_accessTimePresent());
334 assertFalse(xf.isBit2_createTimePresent());
335
336 assertEquals(5, xf.getLocalFileDataLength().getValue());
337 assertEquals(5, xf.getCentralDirectoryLength().getValue());
338
339
340 xf.setFlags((byte) 2);
341 assertEquals(2, xf.getFlags());
342 assertFalse(xf.isBit0_modifyTimePresent());
343 assertTrue(xf.isBit1_accessTimePresent());
344 assertFalse(xf.isBit2_createTimePresent());
345
346 assertEquals(5, xf.getLocalFileDataLength().getValue());
347 assertEquals(1, xf.getCentralDirectoryLength().getValue());
348
349
350 xf.setFlags((byte) 4);
351 assertEquals(4, xf.getFlags());
352 assertFalse(xf.isBit0_modifyTimePresent());
353 assertFalse(xf.isBit1_accessTimePresent());
354 assertTrue(xf.isBit2_createTimePresent());
355
356 assertEquals(5, xf.getLocalFileDataLength().getValue());
357 assertEquals(1, xf.getCentralDirectoryLength().getValue());
358
359
360 xf.setFlags((byte) 7);
361 assertEquals(7, xf.getFlags());
362 assertTrue(xf.isBit0_modifyTimePresent());
363 assertTrue(xf.isBit1_accessTimePresent());
364 assertTrue(xf.isBit2_createTimePresent());
365
366 assertEquals(13, xf.getLocalFileDataLength().getValue());
367 assertEquals(5, xf.getCentralDirectoryLength().getValue());
368
369
370 xf.setFlags((byte) -1);
371 assertEquals(-1, xf.getFlags());
372 assertTrue(xf.isBit0_modifyTimePresent());
373 assertTrue(xf.isBit1_accessTimePresent());
374 assertTrue(xf.isBit2_createTimePresent());
375
376 assertEquals(13, xf.getLocalFileDataLength().getValue());
377 assertEquals(5, xf.getCentralDirectoryLength().getValue());
378 }
379
380 @Test
381 void testMisc() throws Exception {
382 assertNotEquals(xf, new Object());
383 assertTrue(xf.toString().startsWith("0x5455 Zip Extra Field"));
384 assertFalse(xf.toString().contains(" Modify:"));
385 assertFalse(xf.toString().contains(" Access:"));
386 assertFalse(xf.toString().contains(" Create:"));
387 Object o = xf.clone();
388 assertEquals(o.hashCode(), xf.hashCode());
389 assertEquals(xf, o);
390
391 xf.setModifyJavaTime(new Date(1111));
392 xf.setAccessJavaTime(new Date(2222));
393 xf.setCreateJavaTime(new Date(3333));
394 xf.setFlags((byte) 7);
395 assertNotEquals(xf, o);
396 assertTrue(xf.toString().startsWith("0x5455 Zip Extra Field"));
397 assertTrue(xf.toString().contains(" Modify:"));
398 assertTrue(xf.toString().contains(" Access:"));
399 assertTrue(xf.toString().contains(" Create:"));
400 o = xf.clone();
401 assertEquals(o.hashCode(), xf.hashCode());
402 assertEquals(xf, o);
403 }
404
405 @Test
406 void testParseReparse() throws ZipException {
407
408
409
410
411
412
413 final byte[] NULL_FLAGS = { 0 };
414 final byte[] AC_CENTRAL = { 2 };
415 final byte[] CR_CENTRAL = { 4 };
416
417 final byte[] MOD_ZERO = { 1, 0, 0, 0, 0 };
418 final byte[] MOD_MAX = { 1, -1, -1, -1, 0x7f };
419 final byte[] AC_ZERO = { 2, 0, 0, 0, 0 };
420 final byte[] AC_MAX = { 2, -1, -1, -1, 0x7f };
421 final byte[] CR_ZERO = { 4, 0, 0, 0, 0 };
422 final byte[] CR_MAX = { 4, -1, -1, -1, 0x7f };
423 final byte[] MOD_AC_ZERO = { 3, 0, 0, 0, 0, 0, 0, 0, 0 };
424 final byte[] MOD_AC_MAX = { 3, -1, -1, -1, 0x7f, -1, -1, -1, 0x7f };
425 final byte[] MOD_AC_CR_ZERO = { 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
426 final byte[] MOD_AC_CR_MAX = { 7, -1, -1, -1, 0x7f, -1, -1, -1, 0x7f, -1, -1, -1, 0x7f };
427
428 parseReparse(null, NULL_FLAGS, NULL_FLAGS);
429 parseReparse(ZERO_TIME, MOD_ZERO, MOD_ZERO);
430 parseReparse(MAX_TIME_SECONDS, MOD_MAX, MOD_MAX);
431 parseReparse(ZERO_TIME, AC_ZERO, AC_CENTRAL);
432 parseReparse(MAX_TIME_SECONDS, AC_MAX, AC_CENTRAL);
433 parseReparse(ZERO_TIME, CR_ZERO, CR_CENTRAL);
434 parseReparse(MAX_TIME_SECONDS, CR_MAX, CR_CENTRAL);
435 parseReparse(ZERO_TIME, MOD_AC_ZERO, MOD_ZERO);
436 parseReparse(MAX_TIME_SECONDS, MOD_AC_MAX, MOD_MAX);
437 parseReparse(ZERO_TIME, MOD_AC_CR_ZERO, MOD_ZERO);
438 parseReparse(MAX_TIME_SECONDS, MOD_AC_CR_MAX, MOD_MAX);
439
440
441
442 parseReparse((byte) 15, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
443 parseReparse((byte) 31, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
444 parseReparse((byte) 63, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
445 parseReparse((byte) 71, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
446 parseReparse((byte) 127, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
447 parseReparse((byte) -1, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
448 }
449
450 @Test
451 void testResetsFlagsWhenLocalFileArrayIsTooShort() throws Exception {
452 final byte[] local = { 7 };
453 xf.parseFromLocalFileData(local, 0, 1);
454 assertArrayEquals(new byte[1], xf.getLocalFileDataData());
455 }
456
457 @Test
458 void testSampleFile() throws Exception {
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481 final File archive = AbstractTest.getFile("COMPRESS-210_unix_time_zip_test.zip");
482
483 try (ZipFile zipFile = ZipFile.builder().setFile(archive).get()) {
484 final Enumeration<ZipArchiveEntry> en = zipFile.getEntries();
485
486
487
488 while (en.hasMoreElements()) {
489
490 final ZipArchiveEntry zae = en.nextElement();
491 if (zae.isDirectory()) {
492 continue;
493 }
494 final String name = zae.getName();
495 final int x = name.lastIndexOf('/');
496 final String yearString = name.substring(x + 1);
497 final int year;
498 try {
499 year = Integer.parseInt(yearString);
500 } catch (final NumberFormatException nfe) {
501
502 continue;
503 }
504
505 final X5455_ExtendedTimestamp xf = (X5455_ExtendedTimestamp) zae.getExtraField(X5455);
506 final Date rawZ = zae.getLastModifiedDate();
507 final Date m = xf.getModifyJavaTime();
508
509
510
511
512
513
514
515 final boolean zipTimeUsesExtendedTimestampCorrectly = rawZ.equals(m);
516 final boolean zipTimeUsesExtendedTimestampButUnsigned = year > 2037 && rawZ.getSeconds() == 1;
517 final boolean zipTimeUsesExtendedTimestamp = zipTimeUsesExtendedTimestampCorrectly || zipTimeUsesExtendedTimestampButUnsigned;
518
519 final Date z = zipTimeUsesExtendedTimestamp ? rawZ : adjustFromGMTToExpectedOffset(rawZ);
520 final Date a = xf.getAccessJavaTime();
521
522 final String zipTime = DATE_FORMAT.format(z);
523 final String modTime = DATE_FORMAT.format(m);
524 final String accTime = DATE_FORMAT.format(a);
525
526 switch (year) {
527 case 2109:
528
529 if (!zipTimeUsesExtendedTimestamp) {
530 assertEquals("1981-01-01/00:00:02 +0000", zipTime);
531 }
532 break;
533 default:
534 if (!zipTimeUsesExtendedTimestamp) {
535
536
537 if (year < 1980) {
538 assertEquals("1980-01-01/08:00:00 +0000", zipTime);
539 } else {
540 assertEquals(year + "-01-01/00:00:02 +0000", zipTime);
541 }
542 }
543
544 if (year < 2038) {
545 assertEquals(year + "-01-01/00:00:01 +0000", modTime);
546 assertEquals(year + "-01-01/00:00:03 +0000", accTime);
547 }
548 break;
549 }
550 }
551 }
552 }
553
554 @Test
555 void testWriteReadRoundtrip() throws IOException {
556 final File output = new File(tmpDir, "write_rewrite.zip");
557 final Calendar instance = Calendar.getInstance();
558 instance.clear();
559 instance.set(1997, 8, 24, 15, 10, 2);
560 final Date date = instance.getTime();
561 try (OutputStream out = Files.newOutputStream(output.toPath());
562 ZipArchiveOutputStream os = new ZipArchiveOutputStream(out)) {
563 final ZipArchiveEntry ze = new ZipArchiveEntry("foo");
564 xf.setModifyJavaTime(date);
565 xf.setFlags((byte) 1);
566 ze.addExtraField(xf);
567 os.putArchiveEntry(ze);
568 os.closeArchiveEntry();
569 }
570
571 try (ZipFile zf = ZipFile.builder().setFile(output).get()) {
572 final ZipArchiveEntry ze = zf.getEntry("foo");
573 final X5455_ExtendedTimestamp ext = (X5455_ExtendedTimestamp) ze.getExtraField(X5455);
574 assertNotNull(ext);
575 assertTrue(ext.isBit0_modifyTimePresent());
576 assertEquals(date, ext.getModifyJavaTime());
577 }
578 }
579 }