View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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       * InfoZIP seems to adjust the time stored inside the LFH and CD to GMT when writing ZIPs while java.util.zip.ZipEntry thinks it was in local time.
64       *
65       * The archive read in {@link #testSampleFile} has been created with GMT-8, so we need to adjust for the difference.
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       * The extended field (xf) we are testing.
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          // We're responsible for expectedCentral's flags. Too annoying to set in caller.
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         // And now we re-parse:
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         // Do the same as above, but with Central Directory data:
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         // And now we re-parse:
135         xf.parseFromCentralDirectoryData(result, 0, result.length);
136         assertEquals(expectedFlags, xf.getFlags());
137         // Central Directory never contains ACCESS or CREATE, but
138         // may contain MODIFY.
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         // X5455 is concerned with time, so let's
186         // get a timestamp to play with (Jan 1st, 2000).
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         // set too big
199         // Java time is 1000 x larger (milliseconds).
200         assertThrows(IllegalArgumentException.class, () -> xf.setModifyJavaTime(new Date(1000L * (MAX_TIME_SECONDS.getValue() + 1L))),
201                 "Time too big for 32 bits!");
202 
203         // get/set modify time
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         // Make sure milliseconds get zeroed out:
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         // Make sure it works correctly for FileTime
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         // Null
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         // get/set access time
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         // Make sure milliseconds get zeroed out:
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         // Make sure it works correctly for FileTime
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         // Null
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         // get/set create time
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         // Make sure milliseconds get zeroed out:
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         // Make sure it works correctly for FileTime
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         // Null
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         // initialize for flags
315         xf.setModifyTime(time);
316         xf.setAccessTime(time);
317         xf.setCreateTime(time);
318 
319         // get/set flags: 000
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         // Local length=1, Central length=1 (flags only!)
326         assertEquals(1, xf.getLocalFileDataLength().getValue());
327         assertEquals(1, xf.getCentralDirectoryLength().getValue());
328 
329         // get/set flags: 001
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         // Local length=5, Central length=5 (flags + mod)
336         assertEquals(5, xf.getLocalFileDataLength().getValue());
337         assertEquals(5, xf.getCentralDirectoryLength().getValue());
338 
339         // get/set flags: 010
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         // Local length=5, Central length=1
346         assertEquals(5, xf.getLocalFileDataLength().getValue());
347         assertEquals(1, xf.getCentralDirectoryLength().getValue());
348 
349         // get/set flags: 100
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         // Local length=5, Central length=1
356         assertEquals(5, xf.getLocalFileDataLength().getValue());
357         assertEquals(1, xf.getCentralDirectoryLength().getValue());
358 
359         // get/set flags: 111
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         // Local length=13, Central length=5
366         assertEquals(13, xf.getLocalFileDataLength().getValue());
367         assertEquals(5, xf.getCentralDirectoryLength().getValue());
368 
369         // get/set flags: 11111111
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         // Local length=13, Central length=5
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          * Recall the spec:
409          *
410          * 0x5455 Short tag for this extra block type ("UT") TSize Short total data size for this block Flags Byte info bits (ModTime) Long time of last
411          * modification (UTC/GMT) (AcTime) Long time of last access (UTC/GMT) (CrTime) Long time of original creation (UTC/GMT)
412          */
413         final byte[] NULL_FLAGS = { 0 };
414         final byte[] AC_CENTRAL = { 2 }; // central data only contains the AC flag and no actual data
415         final byte[] CR_CENTRAL = { 4 }; // central data only contains the CR flag and no actual data
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         // As far as the spec is concerned (December 2012) all of these flags
441         // are spurious versions of 7 (a.k.a. binary 00000111).
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 }; // claims all three time values would be present, but they are not
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          * Contains entries with zipTime, accessTime, and modifyTime. The file name tells you the year we tried to set the time to (Jan 1st, Midnight, UTC).
462          *
463          * For example:
464          *
465          * COMPRESS-210_unix_time_zip_test/1999 COMPRESS-210_unix_time_zip_test/2000 COMPRESS-210_unix_time_zip_test/2108
466          *
467          * File's last-modified is 1st second after midnight. Zip-time's 2-second granularity rounds that up to 2nd second. File's last-access is 3rd second
468          * after midnight.
469          *
470          * So, from example above:
471          *
472          * 1999's zip time: Jan 1st, 1999-01-01/00:00:02 1999's mod time: Jan 1st, 1999-01-01/00:00:01 1999's acc time: Jan 1st, 1999-01-01/00:00:03
473          *
474          * Starting with a patch release of Java8, "zip time" actually uses the extended time stamp field itself and should be the same as "mod time".
475          * https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/90df6756406f
476          *
477          * Starting with Java9 the parser for extended time stamps has been fixed to use signed integers which was detected during the triage of COMPRESS-416.
478          * Signed integers is the correct format and Compress 1.15 has started to use signed integers as well.
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             // We expect EVERY entry of this ZIP file
487             // to contain extra field 0x5455.
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                     // setTime.sh, skip
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                  * We must distinguish three cases: - Java has read the extended time field itself and agrees with us (Java9 or Java8 and years prior to 2038) -
511                  * Java has read the extended time field but found a year >= 2038 (Java8) - Java hasn't read the extended time field at all (Java7- or early
512                  * Java8)
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                     // All three timestamps have overflowed by 2109.
529                     if (!zipTimeUsesExtendedTimestamp) {
530                         assertEquals("1981-01-01/00:00:02 +0000", zipTime);
531                     }
532                     break;
533                 default:
534                     if (!zipTimeUsesExtendedTimestamp) {
535                         // X5455 time is good from epoch (1970) to 2037.
536                         // Zip time is good from 1980 to 2107.
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 }