View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.archivers.zip;
18  
19  import 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       * 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.
62       *
63       * The archive read in {@link #testSampleFile} has been created with GMT-8, so we need to adjust for the difference.
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       * The extended field (xf) we are testing.
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          // We're responsible for expectedCentral's flags. Too annoying to set in caller.
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         // And now we re-parse:
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         // Do the same as above, but with Central Directory data:
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         // And now we re-parse:
133         xf.parseFromCentralDirectoryData(result, 0, result.length);
134         assertEquals(expectedFlags, xf.getFlags());
135         // Central Directory never contains ACCESS or CREATE, but
136         // may contain MODIFY.
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         // X5455 is concerned with time, so let's
184         // get a timestamp to play with (Jan 1st, 2000).
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         // set too big
197         // Java time is 1000 x larger (milliseconds).
198         assertThrows(IllegalArgumentException.class, () -> xf.setModifyJavaTime(new Date(1000L * (MAX_TIME_SECONDS.getValue() + 1L))),
199                 "Time too big for 32 bits!");
200 
201         // get/set modify time
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         // Make sure milliseconds get zeroed out:
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         // Make sure it works correctly for FileTime
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         // Null
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         // get/set access time
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         // Make sure milliseconds get zeroed out:
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         // Make sure it works correctly for FileTime
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         // Null
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         // get/set create time
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         // Make sure milliseconds get zeroed out:
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         // Make sure it works correctly for FileTime
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         // Null
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         // initialize for flags
313         xf.setModifyTime(time);
314         xf.setAccessTime(time);
315         xf.setCreateTime(time);
316 
317         // get/set flags: 000
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         // Local length=1, Central length=1 (flags only!)
324         assertEquals(1, xf.getLocalFileDataLength().getValue());
325         assertEquals(1, xf.getCentralDirectoryLength().getValue());
326 
327         // get/set flags: 001
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         // Local length=5, Central length=5 (flags + mod)
334         assertEquals(5, xf.getLocalFileDataLength().getValue());
335         assertEquals(5, xf.getCentralDirectoryLength().getValue());
336 
337         // get/set flags: 010
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         // Local length=5, Central length=1
344         assertEquals(5, xf.getLocalFileDataLength().getValue());
345         assertEquals(1, xf.getCentralDirectoryLength().getValue());
346 
347         // get/set flags: 100
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         // Local length=5, Central length=1
354         assertEquals(5, xf.getLocalFileDataLength().getValue());
355         assertEquals(1, xf.getCentralDirectoryLength().getValue());
356 
357         // get/set flags: 111
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         // Local length=13, Central length=5
364         assertEquals(13, xf.getLocalFileDataLength().getValue());
365         assertEquals(5, xf.getCentralDirectoryLength().getValue());
366 
367         // get/set flags: 11111111
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         // Local length=13, Central length=5
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          * Recall the spec:
407          *
408          * 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
409          * modification (UTC/GMT) (AcTime) Long time of last access (UTC/GMT) (CrTime) Long time of original creation (UTC/GMT)
410          */
411         final byte[] NULL_FLAGS = { 0 };
412         final byte[] AC_CENTRAL = { 2 }; // central data only contains the AC flag and no actual data
413         final byte[] CR_CENTRAL = { 4 }; // central data only contains the CR flag and no actual data
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         // As far as the spec is concerned (December 2012) all of these flags
439         // are spurious versions of 7 (a.k.a. binary 00000111).
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 }; // claims all three time values would be present, but they are not
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          * 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).
460          *
461          * For example:
462          *
463          * COMPRESS-210_unix_time_zip_test/1999 COMPRESS-210_unix_time_zip_test/2000 COMPRESS-210_unix_time_zip_test/2108
464          *
465          * 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
466          * after midnight.
467          *
468          * So, from example above:
469          *
470          * 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
471          *
472          * 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".
473          * https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/90df6756406f
474          *
475          * 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.
476          * Signed integers is the correct format and Compress 1.15 has started to use signed integers as well.
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             // We expect EVERY entry of this ZIP file
485             // to contain extra field 0x5455.
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                     // setTime.sh, skip
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                  * 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) -
509                  * 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
510                  * Java8)
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                     // All three timestamps have overflowed by 2109.
527                     if (!zipTimeUsesExtendedTimestamp) {
528                         assertEquals("1981-01-01/00:00:02 +0000", zipTime);
529                     }
530                     break;
531                 default:
532                     if (!zipTimeUsesExtendedTimestamp) {
533                         // X5455 time is good from epoch (1970) to 2037.
534                         // Zip time is good from 1980 to 2107.
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 }