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