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