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  */
18 package org.apache.commons.compress.archivers.zip;
19 
20 import org.junit.After;
21 import org.junit.Before;
22 import org.junit.Test;
23 
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.text.SimpleDateFormat;
29 import java.util.Arrays;
30 import java.util.Calendar;
31 import java.util.Date;
32 import java.util.Enumeration;
33 import java.util.TimeZone;
34 import java.util.zip.ZipException;
35 
36 import static org.apache.commons.compress.AbstractTestCase.getFile;
37 import static org.apache.commons.compress.AbstractTestCase.mkdir;
38 import static org.apache.commons.compress.AbstractTestCase.rmdir;
39 import static org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp.ACCESS_TIME_BIT;
40 import static org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp.CREATE_TIME_BIT;
41 import static org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp.MODIFY_TIME_BIT;
42 import static org.junit.Assert.assertEquals;
43 import static org.junit.Assert.assertFalse;
44 import static org.junit.Assert.assertNotNull;
45 import static org.junit.Assert.assertNull;
46 import static org.junit.Assert.assertTrue;
47 import static org.junit.Assert.fail;
48 
49 public class X5455_ExtendedTimestampTest {
50     private final static ZipShort X5455 = new ZipShort(0x5455);
51 
52     private final static ZipLong ZERO_TIME = new ZipLong(0);
53     private final static ZipLong MAX_TIME_SECONDS = new ZipLong(Integer.MAX_VALUE);
54     private final static 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      * The extended field (xf) we are testing.
63      */
64     private X5455_ExtendedTimestamp xf;
65 
66     private File tmpDir;
67 
68     @Before
before()69     public void before() {
70         xf = new X5455_ExtendedTimestamp();
71     }
72 
73     @After
removeTempFiles()74     public void removeTempFiles() {
75         if (tmpDir != null) {
76             rmdir(tmpDir);
77         }
78     }
79 
80     @Test
testSampleFile()81     public void testSampleFile() throws Exception {
82 
83         /*
84         Contains entries with zipTime, accessTime, and modifyTime.
85         The file name tells you the year we tried to set the time to
86         (Jan 1st, Midnight, UTC).
87 
88         For example:
89 
90         COMPRESS-210_unix_time_zip_test/1999
91         COMPRESS-210_unix_time_zip_test/2000
92         COMPRESS-210_unix_time_zip_test/2108
93 
94         File's last-modified is 1st second after midnight.
95         Zip-time's 2-second granularity rounds that up to 2nd second.
96         File's last-access is 3rd second after midnight.
97 
98         So, from example above:
99 
100         1999's zip time:  Jan 1st, 1999-01-01/00:00:02
101         1999's mod time:  Jan 1st, 1999-01-01/00:00:01
102         1999's acc time:  Jan 1st, 1999-01-01/00:00:03
103 
104         Starting with a patch release of Java8, "zip time" actually
105         uses the extended time stamp field itself and should be the
106         same as "mod time".
107         http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/90df6756406f
108 
109         Starting with Java9 the parser for extended time stamps has
110         been fixed to use signed integers which was detected during
111         the triage of COMPRESS-416. Signed integers is the correct
112         format and Compress 1.15 has started to use signed integers as
113         well.
114          */
115 
116         final File archive = getFile("COMPRESS-210_unix_time_zip_test.zip");
117         ZipFile zf = null;
118 
119         try {
120             zf = new ZipFile(archive);
121             final Enumeration<ZipArchiveEntry> en = zf.getEntries();
122 
123             // We expect EVERY entry of this zip file
124             // to contain extra field 0x5455.
125             while (en.hasMoreElements()) {
126 
127                 final ZipArchiveEntry zae = en.nextElement();
128                 if (zae.isDirectory()) {
129                     continue;
130                 }
131                 final String name = zae.getName();
132                 final int x = name.lastIndexOf('/');
133                 final String yearString = name.substring(x + 1);
134                 int year;
135                 try {
136                     year = Integer.parseInt(yearString);
137                 } catch (final NumberFormatException nfe) {
138                     // setTime.sh, skip
139                     continue;
140                 }
141 
142                 final X5455_ExtendedTimestamp xf = (X5455_ExtendedTimestamp) zae.getExtraField(X5455);
143                 final Date rawZ = zae.getLastModifiedDate();
144                 final Date m = xf.getModifyJavaTime();
145 
146                 /*
147                   We must distinguish three cases:
148                   - Java has read the extended time field itself and agrees with us (Java9 or Java8 and years prior to
149                     2038)
150                   - Java has read the extended time field but found a year >= 2038 (Java8)
151                   - Java hasn't read the extended time field at all (Java7- or early Java8)
152                 */
153 
154                 final boolean zipTimeUsesExtendedTimestampCorrectly = rawZ.equals(m);
155                 final boolean zipTimeUsesExtendedTimestampButUnsigned = year > 2037 && rawZ.getSeconds() == 1;
156                 final boolean zipTimeUsesExtendedTimestamp = zipTimeUsesExtendedTimestampCorrectly
157                     || zipTimeUsesExtendedTimestampButUnsigned;
158 
159                 final Date z = zipTimeUsesExtendedTimestamp ? rawZ : adjustFromGMTToExpectedOffset(rawZ);
160                 final Date a = xf.getAccessJavaTime();
161 
162                 final String zipTime = DATE_FORMAT.format(z);
163                 final String modTime = DATE_FORMAT.format(m);
164                 final String accTime = DATE_FORMAT.format(a);
165 
166                 switch (year) {
167                 case 2109:
168                     // All three timestamps have overflowed by 2109.
169                     if (!zipTimeUsesExtendedTimestamp) {
170                         assertEquals("1981-01-01/00:00:02 +0000", zipTime);
171                     }
172                     break;
173                 default:
174                     if (!zipTimeUsesExtendedTimestamp) {
175                         // X5455 time is good from epoch (1970) to 2037.
176                         // Zip time is good from 1980 to 2107.
177                         if (year < 1980) {
178                             assertEquals("1980-01-01/08:00:00 +0000", zipTime);
179                         } else {
180                             assertEquals(year + "-01-01/00:00:02 +0000", zipTime);
181                         }
182                     }
183 
184                     if (year < 2038) {
185                         assertEquals(year + "-01-01/00:00:01 +0000", modTime);
186                         assertEquals(year + "-01-01/00:00:03 +0000", accTime);
187                     }
188                     break;
189                 }
190             }
191         } finally {
192             if (zf != null) {
193                 zf.close();
194             }
195         }
196     }
197 
198 
199     @Test
testMisc()200     public void testMisc() throws Exception {
201         assertFalse(xf.equals(new Object()));
202         assertTrue(xf.toString().startsWith("0x5455 Zip Extra Field"));
203         assertTrue(!xf.toString().contains(" Modify:"));
204         assertTrue(!xf.toString().contains(" Access:"));
205         assertTrue(!xf.toString().contains(" Create:"));
206         Object o = xf.clone();
207         assertEquals(o.hashCode(), xf.hashCode());
208         assertTrue(xf.equals(o));
209 
210         xf.setModifyJavaTime(new Date(1111));
211         xf.setAccessJavaTime(new Date(2222));
212         xf.setCreateJavaTime(new Date(3333));
213         xf.setFlags((byte) 7);
214         assertFalse(xf.equals(o));
215         assertTrue(xf.toString().startsWith("0x5455 Zip Extra Field"));
216         assertTrue(xf.toString().contains(" Modify:"));
217         assertTrue(xf.toString().contains(" Access:"));
218         assertTrue(xf.toString().contains(" Create:"));
219         o = xf.clone();
220         assertEquals(o.hashCode(), xf.hashCode());
221         assertTrue(xf.equals(o));
222     }
223 
224     @Test
testGettersSetters()225     public void testGettersSetters() {
226         // X5455 is concerned with time, so let's
227         // get a timestamp to play with (Jan 1st, 2000).
228         final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
229         cal.set(Calendar.YEAR, 2000);
230         cal.set(Calendar.MONTH, Calendar.JANUARY);
231         cal.set(Calendar.DATE, 1);
232         cal.set(Calendar.HOUR_OF_DAY, 0);
233         cal.set(Calendar.MINUTE, 0);
234         cal.set(Calendar.SECOND, 0);
235         cal.set(Calendar.MILLISECOND, 0);
236         final long timeMillis = cal.getTimeInMillis();
237         final ZipLong time = new ZipLong(timeMillis / 1000);
238 
239         // set too big
240         try {
241             // Java time is 1000 x larger (milliseconds).
242             xf.setModifyJavaTime(new Date(1000L * (MAX_TIME_SECONDS.getValue() + 1L)));
243             fail("Time too big for 32 bits!");
244         } catch (final IllegalArgumentException iae) {
245             // All is good.
246         }
247 
248         // get/set modify time
249         xf.setModifyTime(time);
250         assertEquals(time, xf.getModifyTime());
251         Date xfModifyJavaTime = xf.getModifyJavaTime();
252         assertEquals(timeMillis, xfModifyJavaTime.getTime());
253         xf.setModifyJavaTime(new Date(timeMillis));
254         assertEquals(time, xf.getModifyTime());
255         assertEquals(timeMillis, xf.getModifyJavaTime().getTime());
256         // Make sure milliseconds get zeroed out:
257         xf.setModifyJavaTime(new Date(timeMillis + 123));
258         assertEquals(time, xf.getModifyTime());
259         assertEquals(timeMillis, xf.getModifyJavaTime().getTime());
260         // Null
261         xf.setModifyTime(null);
262         assertNull(xf.getModifyJavaTime());
263         xf.setModifyJavaTime(null);
264         assertNull(xf.getModifyTime());
265 
266         // get/set access time
267         xf.setAccessTime(time);
268         assertEquals(time, xf.getAccessTime());
269         assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
270         xf.setAccessJavaTime(new Date(timeMillis));
271         assertEquals(time, xf.getAccessTime());
272         assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
273         // Make sure milliseconds get zeroed out:
274         xf.setAccessJavaTime(new Date(timeMillis + 123));
275         assertEquals(time, xf.getAccessTime());
276         assertEquals(timeMillis, xf.getAccessJavaTime().getTime());
277         // Null
278         xf.setAccessTime(null);
279         assertNull(xf.getAccessJavaTime());
280         xf.setAccessJavaTime(null);
281         assertNull(xf.getAccessTime());
282 
283         // get/set create time
284         xf.setCreateTime(time);
285         assertEquals(time, xf.getCreateTime());
286         assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
287         xf.setCreateJavaTime(new Date(timeMillis));
288         assertEquals(time, xf.getCreateTime());
289         assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
290         // Make sure milliseconds get zeroed out:
291         xf.setCreateJavaTime(new Date(timeMillis + 123));
292         assertEquals(time, xf.getCreateTime());
293         assertEquals(timeMillis, xf.getCreateJavaTime().getTime());
294         // Null
295         xf.setCreateTime(null);
296         assertNull(xf.getCreateJavaTime());
297         xf.setCreateJavaTime(null);
298         assertNull(xf.getCreateTime());
299 
300 
301         // initialize for flags
302         xf.setModifyTime(time);
303         xf.setAccessTime(time);
304         xf.setCreateTime(time);
305 
306         // get/set flags: 000
307         xf.setFlags((byte) 0);
308         assertEquals(0, xf.getFlags());
309         assertFalse(xf.isBit0_modifyTimePresent());
310         assertFalse(xf.isBit1_accessTimePresent());
311         assertFalse(xf.isBit2_createTimePresent());
312         // Local length=1, Central length=1 (flags only!)
313         assertEquals(1, xf.getLocalFileDataLength().getValue());
314         assertEquals(1, xf.getCentralDirectoryLength().getValue());
315 
316         // get/set flags: 001
317         xf.setFlags((byte) 1);
318         assertEquals(1, xf.getFlags());
319         assertTrue(xf.isBit0_modifyTimePresent());
320         assertFalse(xf.isBit1_accessTimePresent());
321         assertFalse(xf.isBit2_createTimePresent());
322         // Local length=5, Central length=5 (flags + mod)
323         assertEquals(5, xf.getLocalFileDataLength().getValue());
324         assertEquals(5, xf.getCentralDirectoryLength().getValue());
325 
326         // get/set flags: 010
327         xf.setFlags((byte) 2);
328         assertEquals(2, xf.getFlags());
329         assertFalse(xf.isBit0_modifyTimePresent());
330         assertTrue(xf.isBit1_accessTimePresent());
331         assertFalse(xf.isBit2_createTimePresent());
332         // Local length=5, Central length=1
333         assertEquals(5, xf.getLocalFileDataLength().getValue());
334         assertEquals(1, xf.getCentralDirectoryLength().getValue());
335 
336         // get/set flags: 100
337         xf.setFlags((byte) 4);
338         assertEquals(4, xf.getFlags());
339         assertFalse(xf.isBit0_modifyTimePresent());
340         assertFalse(xf.isBit1_accessTimePresent());
341         assertTrue(xf.isBit2_createTimePresent());
342         // Local length=5, Central length=1
343         assertEquals(5, xf.getLocalFileDataLength().getValue());
344         assertEquals(1, xf.getCentralDirectoryLength().getValue());
345 
346         // get/set flags: 111
347         xf.setFlags((byte) 7);
348         assertEquals(7, xf.getFlags());
349         assertTrue(xf.isBit0_modifyTimePresent());
350         assertTrue(xf.isBit1_accessTimePresent());
351         assertTrue(xf.isBit2_createTimePresent());
352         // Local length=13, Central length=5
353         assertEquals(13, xf.getLocalFileDataLength().getValue());
354         assertEquals(5, xf.getCentralDirectoryLength().getValue());
355 
356         // get/set flags: 11111111
357         xf.setFlags((byte) -1);
358         assertEquals(-1, xf.getFlags());
359         assertTrue(xf.isBit0_modifyTimePresent());
360         assertTrue(xf.isBit1_accessTimePresent());
361         assertTrue(xf.isBit2_createTimePresent());
362         // Local length=13, Central length=5
363         assertEquals(13, xf.getLocalFileDataLength().getValue());
364         assertEquals(5, xf.getCentralDirectoryLength().getValue());
365     }
366 
367     @Test
testGetHeaderId()368     public void testGetHeaderId() {
369         assertEquals(X5455, xf.getHeaderId());
370     }
371 
372     @Test
testParseReparse()373     public void testParseReparse() throws ZipException {
374         /*
375          * Recall the spec:
376          *
377          * 0x5455        Short       tag for this extra block type ("UT")
378          * TSize         Short       total data size for this block
379          * Flags         Byte        info bits
380          * (ModTime)     Long        time of last modification (UTC/GMT)
381          * (AcTime)      Long        time of last access (UTC/GMT)
382          * (CrTime)      Long        time of original creation (UTC/GMT)
383          */
384         final byte[] NULL_FLAGS = {0};
385         final byte[] AC_CENTRAL = {2}; // central data only contains the AC flag and no actual data
386         final byte[] CR_CENTRAL = {4}; // central data only dontains the CR flag and no actual data
387 
388         final byte[] MOD_ZERO = {1, 0, 0, 0, 0};
389         final byte[] MOD_MAX = {1, -1, -1, -1, 0x7f};
390         final byte[] AC_ZERO = {2, 0, 0, 0, 0};
391         final byte[] AC_MAX = {2, -1, -1, -1, 0x7f};
392         final byte[] CR_ZERO = {4, 0, 0, 0, 0};
393         final byte[] CR_MAX = {4, -1, -1, -1, 0x7f};
394         final byte[] MOD_AC_ZERO = {3, 0, 0, 0, 0, 0, 0, 0, 0};
395         final byte[] MOD_AC_MAX = {3, -1, -1, -1, 0x7f, -1, -1, -1, 0x7f};
396         final byte[] MOD_AC_CR_ZERO = {7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
397         final byte[] MOD_AC_CR_MAX = {7, -1, -1, -1, 0x7f, -1, -1, -1, 0x7f, -1, -1, -1, 0x7f};
398 
399         parseReparse(null, NULL_FLAGS, NULL_FLAGS);
400         parseReparse(ZERO_TIME, MOD_ZERO, MOD_ZERO);
401         parseReparse(MAX_TIME_SECONDS, MOD_MAX, MOD_MAX);
402         parseReparse(ZERO_TIME, AC_ZERO, AC_CENTRAL);
403         parseReparse(MAX_TIME_SECONDS, AC_MAX, AC_CENTRAL);
404         parseReparse(ZERO_TIME, CR_ZERO, CR_CENTRAL);
405         parseReparse(MAX_TIME_SECONDS, CR_MAX, CR_CENTRAL);
406         parseReparse(ZERO_TIME, MOD_AC_ZERO, MOD_ZERO);
407         parseReparse(MAX_TIME_SECONDS, MOD_AC_MAX, MOD_MAX);
408         parseReparse(ZERO_TIME, MOD_AC_CR_ZERO, MOD_ZERO);
409         parseReparse(MAX_TIME_SECONDS, MOD_AC_CR_MAX, MOD_MAX);
410 
411         // As far as the spec is concerned (December 2012) all of these flags
412         // are spurious versions of 7 (a.k.a. binary 00000111).
413         parseReparse((byte) 15, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
414         parseReparse((byte) 31, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
415         parseReparse((byte) 63, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
416         parseReparse((byte) 71, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
417         parseReparse((byte) 127, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
418         parseReparse((byte) -1, MAX_TIME_SECONDS, (byte) 7, MOD_AC_CR_MAX, MOD_MAX);
419     }
420 
421     @Test
testWriteReadRoundtrip()422     public void testWriteReadRoundtrip() throws IOException {
423         tmpDir = mkdir("X5455");
424         final File output = new File(tmpDir, "write_rewrite.zip");
425         final OutputStream out = new FileOutputStream(output);
426         final Date d = new Date(97, 8, 24, 15, 10, 2);
427         ZipArchiveOutputStream os = null;
428         try {
429             os = new ZipArchiveOutputStream(out);
430             final ZipArchiveEntry ze = new ZipArchiveEntry("foo");
431             xf.setModifyJavaTime(d);
432             xf.setFlags((byte) 1);
433             ze.addExtraField(xf);
434             os.putArchiveEntry(ze);
435             os.closeArchiveEntry();
436         } finally {
437             if (os != null) {
438                 os.close();
439             }
440         }
441         out.close();
442 
443         final ZipFile zf = new ZipFile(output);
444         final ZipArchiveEntry ze = zf.getEntry("foo");
445         final X5455_ExtendedTimestamp ext =
446             (X5455_ExtendedTimestamp) ze.getExtraField(X5455);
447         assertNotNull(ext);
448         assertTrue(ext.isBit0_modifyTimePresent());
449         assertEquals(d, ext.getModifyJavaTime());
450         zf.close();
451     }
452 
453     @Test
testBitsAreSetWithTime()454     public void testBitsAreSetWithTime() {
455         xf.setModifyJavaTime(new Date(1111));
456         assertTrue(xf.isBit0_modifyTimePresent());
457         assertEquals(1, xf.getFlags());
458         xf.setAccessJavaTime(new Date(2222));
459         assertTrue(xf.isBit1_accessTimePresent());
460         assertEquals(3, xf.getFlags());
461         xf.setCreateJavaTime(new Date(3333));
462         assertTrue(xf.isBit2_createTimePresent());
463         assertEquals(7, xf.getFlags());
464         xf.setModifyJavaTime(null);
465         assertFalse(xf.isBit0_modifyTimePresent());
466         assertEquals(6, xf.getFlags());
467         xf.setAccessJavaTime(null);
468         assertFalse(xf.isBit1_accessTimePresent());
469         assertEquals(4, xf.getFlags());
470         xf.setCreateJavaTime(null);
471         assertFalse(xf.isBit2_createTimePresent());
472         assertEquals(0, xf.getFlags());
473     }
474 
parseReparse( final ZipLong time, final byte[] expectedLocal, final byte[] almostExpectedCentral )475     private void parseReparse(
476             final ZipLong time,
477             final byte[] expectedLocal,
478             final byte[] almostExpectedCentral
479     ) throws ZipException {
480         parseReparse(expectedLocal[0], time, expectedLocal[0], expectedLocal, almostExpectedCentral);
481     }
482 
parseReparse( final byte providedFlags, final ZipLong time, final byte expectedFlags, final byte[] expectedLocal, final byte[] almostExpectedCentral )483     private void parseReparse(
484             final byte providedFlags,
485             final ZipLong time,
486             final byte expectedFlags,
487             final byte[] expectedLocal,
488             final byte[] almostExpectedCentral
489     ) throws ZipException {
490 
491         // We're responsible for expectedCentral's flags.  Too annoying to set in caller.
492         final byte[] expectedCentral = new byte[almostExpectedCentral.length];
493         System.arraycopy(almostExpectedCentral, 0, expectedCentral, 0, almostExpectedCentral.length);
494         expectedCentral[0] = expectedFlags;
495 
496         xf.setModifyTime(time);
497         xf.setAccessTime(time);
498         xf.setCreateTime(time);
499         xf.setFlags(providedFlags);
500         byte[] result = xf.getLocalFileDataData();
501         assertTrue(Arrays.equals(expectedLocal, result));
502 
503         // And now we re-parse:
504         xf.parseFromLocalFileData(result, 0, result.length);
505         assertEquals(expectedFlags, xf.getFlags());
506         if (isFlagSet(expectedFlags, MODIFY_TIME_BIT)) {
507             assertTrue(xf.isBit0_modifyTimePresent());
508             assertEquals(time, xf.getModifyTime());
509         }
510         if (isFlagSet(expectedFlags, ACCESS_TIME_BIT)) {
511             assertTrue(xf.isBit1_accessTimePresent());
512             assertEquals(time, xf.getAccessTime());
513         }
514         if (isFlagSet(expectedFlags, CREATE_TIME_BIT)) {
515             assertTrue(xf.isBit2_createTimePresent());
516             assertEquals(time, xf.getCreateTime());
517         }
518 
519         // Do the same as above, but with Central Directory data:
520         xf.setModifyTime(time);
521         xf.setAccessTime(time);
522         xf.setCreateTime(time);
523         xf.setFlags(providedFlags);
524         result = xf.getCentralDirectoryData();
525         assertTrue(Arrays.equals(expectedCentral, result));
526 
527         // And now we re-parse:
528         xf.parseFromCentralDirectoryData(result, 0, result.length);
529         assertEquals(expectedFlags, xf.getFlags());
530         // Central Directory never contains ACCESS or CREATE, but
531         // may contain MODIFY.
532         if (isFlagSet(expectedFlags, MODIFY_TIME_BIT)) {
533             assertTrue(xf.isBit0_modifyTimePresent());
534             assertEquals(time, xf.getModifyTime());
535         }
536     }
537 
isFlagSet(final byte data, final byte flag)538     private static boolean isFlagSet(final byte data, final byte flag) { return (data & flag) == flag; }
539 
540     /**
541      * InfoZIP seems to adjust the time stored inside the LFH and CD
542      * to GMT when writing ZIPs while java.util.zip.ZipEntry thinks it
543      * was in local time.
544      *
545      * The archive read in {@link #testSampleFile} has been created
546      * with GMT-8 so we need to adjust for the difference.
547      */
adjustFromGMTToExpectedOffset(final Date from)548     private static Date adjustFromGMTToExpectedOffset(final Date from) {
549         final Calendar cal = Calendar.getInstance();
550         cal.setTime(from);
551         cal.add(Calendar.MILLISECOND, cal.get(Calendar.ZONE_OFFSET));
552         if (cal.getTimeZone().inDaylightTime(from)) {
553             cal.add(Calendar.MILLISECOND, cal.get(Calendar.DST_OFFSET));
554         }
555         cal.add(Calendar.HOUR, 8);
556         return cal.getTime();
557     }
558 }
559