1 /* 2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 4759491 6303183 7012868 8015666 8023713 8068790 8076641 8075526 8130914 27 * 8161942 8206389 28 * @summary Test ZOS and ZIS timestamp in extra field correctly 29 */ 30 package test.java.util.zip; 31 import java.io.*; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.nio.file.attribute.BasicFileAttributeView; 36 import java.nio.file.attribute.FileOwnerAttributeView; 37 import java.nio.file.attribute.FileTime; 38 import java.nio.file.attribute.PosixFilePermission; 39 import java.time.Instant; 40 import java.util.Arrays; 41 import java.util.Set; 42 import java.util.TimeZone; 43 import java.util.concurrent.TimeUnit; 44 import java.util.zip.ZipEntry; 45 import java.util.zip.ZipFile; 46 import java.util.zip.ZipInputStream; 47 import java.util.zip.ZipOutputStream; 48 49 50 51 public class TestExtraTime { 52 main(String[] args)53 public static void main(String[] args) throws Throwable{ 54 // Android-removed: file is used for lastModified timestamp only. 55 /* 56 File src = new File(System.getProperty("test.src", "."), "TestExtraTime.java"); 57 if (!src.exists()) { 58 return; 59 } 60 */ 61 TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai"); 62 for (byte[] extra : new byte[][] { null, new byte[] {1, 2, 3}}) { 63 // ms-dos 1980 epoch problem 64 test0(FileTime.from(10, TimeUnit.MILLISECONDS), null, null, null, extra); 65 // negative epoch time 66 test0(FileTime.from(-100, TimeUnit.DAYS), null, null, null, extra); 67 // Android-removed: file is used for lastModified timestamp only. 68 // long time = src.lastModified(); 69 long time = 1676851259000L; // 20 Feb 2023 00:00 GMT 70 test(FileTime.from(time, TimeUnit.MILLISECONDS), 71 FileTime.from(time + 300000, TimeUnit.MILLISECONDS), 72 FileTime.from(time - 300000, TimeUnit.MILLISECONDS), 73 tz, extra); 74 75 // now 76 time = Instant.now().toEpochMilli(); 77 test(FileTime.from(time, TimeUnit.MILLISECONDS), 78 FileTime.from(time + 300000, TimeUnit.MILLISECONDS), 79 FileTime.from(time - 300000, TimeUnit.MILLISECONDS), 80 tz, extra); 81 82 // unix 2038 83 time = 0x80000000L; 84 test(FileTime.from(time, TimeUnit.SECONDS), 85 FileTime.from(time, TimeUnit.SECONDS), 86 FileTime.from(time, TimeUnit.SECONDS), 87 tz, extra); 88 89 // mtime < unix 2038 90 time = 0x7fffffffL; 91 test(FileTime.from(time, TimeUnit.SECONDS), 92 FileTime.from(time + 30000, TimeUnit.SECONDS), 93 FileTime.from(time + 30000, TimeUnit.SECONDS), 94 tz, extra); 95 } 96 97 testNullHandling(); 98 testTagOnlyHandling(); 99 testTimeConversions(); 100 testNullMtime(); 101 } 102 test(FileTime mtime, FileTime atime, FileTime ctime, TimeZone tz, byte[] extra)103 static void test(FileTime mtime, FileTime atime, FileTime ctime, 104 TimeZone tz, byte[] extra) throws Throwable { 105 test0(mtime, null, null, null, extra); 106 test0(mtime, null, null, tz, extra); // non-default tz 107 test0(mtime, atime, null, null, extra); 108 test0(mtime, null, ctime, null, extra); 109 test0(mtime, atime, ctime, null, extra); 110 test0(mtime, atime, null, tz, extra); 111 test0(mtime, null, ctime, tz, extra); 112 test0(mtime, atime, ctime, tz, extra); 113 } 114 test0(FileTime mtime, FileTime atime, FileTime ctime, TimeZone tz, byte[] extra)115 static void test0(FileTime mtime, FileTime atime, FileTime ctime, 116 TimeZone tz, byte[] extra) throws Throwable { 117 System.out.printf("--------------------%nTesting: [%s]/[%s]/[%s]%n", 118 mtime, atime, ctime); 119 TimeZone tz0 = TimeZone.getDefault(); 120 if (tz != null) { 121 TimeZone.setDefault(tz); 122 } 123 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 124 ZipOutputStream zos = new ZipOutputStream(baos); 125 ZipEntry ze = new ZipEntry("TestExtraTime.java"); 126 ze.setExtra(extra); 127 ze.setLastModifiedTime(mtime); 128 if (atime != null) 129 ze.setLastAccessTime(atime); 130 if (ctime != null) 131 ze.setCreationTime(ctime); 132 zos.putNextEntry(ze); 133 zos.write(new byte[] { 1,2 ,3, 4}); 134 135 // append an extra entry to help check if the length and data 136 // of the extra field are being correctly written (in previous 137 // entry). 138 if (extra != null) { 139 ze = new ZipEntry("TestExtraEntry"); 140 zos.putNextEntry(ze); 141 } 142 zos.close(); 143 if (tz != null) { 144 TimeZone.setDefault(tz0); 145 } 146 // ZipInputStream 147 ZipInputStream zis = new ZipInputStream( 148 new ByteArrayInputStream(baos.toByteArray())); 149 ze = zis.getNextEntry(); 150 zis.close(); 151 check(mtime, atime, ctime, ze, extra); 152 153 // ZipFile 154 // Android-changed: create file in a temp dir, 'test.dir' is not available. 155 // Path zpath = Paths.get(System.getProperty("test.dir", "."), 156 // "TestExtraTime.zip"); 157 Path zpath = Files.createTempDirectory("TestExtraTime").resolve("TestExtraTime.zip"); 158 Path zparent = zpath.getParent(); 159 if (zparent != null && !Files.isWritable(zparent)) { 160 System.err.format("zpath %s parent %s is not writable%n", 161 zpath, zparent); 162 } 163 if (Files.exists(zpath)) { 164 System.err.format("zpath %s already exists%n", zpath); 165 if (Files.isDirectory(zpath)) { 166 System.err.format("%n%s contents:%n", zpath); 167 Files.list(zpath).forEach(System.err::println); 168 } 169 FileOwnerAttributeView foav = Files.getFileAttributeView(zpath, 170 FileOwnerAttributeView.class); 171 System.err.format("zpath %s owner: %s%n", zpath, foav.getOwner()); 172 System.err.format("zpath %s permissions:%n", zpath); 173 Set<PosixFilePermission> perms = 174 Files.getPosixFilePermissions(zpath); 175 perms.stream().forEach(System.err::println); 176 } 177 if (Files.isSymbolicLink(zpath)) { 178 System.err.format("zpath %s is a symbolic link%n", zpath); 179 } 180 Files.copy(new ByteArrayInputStream(baos.toByteArray()), zpath); 181 try (ZipFile zf = new ZipFile(zpath.toFile())) { 182 ze = zf.getEntry("TestExtraTime.java"); 183 // ZipFile read entry from cen, which does not have a/ctime, 184 // for now. 185 check(mtime, null, null, ze, extra); 186 } finally { 187 Files.delete(zpath); 188 } 189 } 190 check(FileTime mtime, FileTime atime, FileTime ctime, ZipEntry ze, byte[] extra)191 static void check(FileTime mtime, FileTime atime, FileTime ctime, 192 ZipEntry ze, byte[] extra) { 193 /* 194 System.out.printf(" mtime [%tc]: [%tc]/[%tc]%n", 195 mtime.to(TimeUnit.MILLISECONDS), 196 ze.getTime(), 197 ze.getLastModifiedTime().to(TimeUnit.MILLISECONDS)); 198 */ 199 if (mtime != null && 200 mtime.to(TimeUnit.SECONDS) != 201 ze.getLastModifiedTime().to(TimeUnit.SECONDS)) { 202 throw new RuntimeException("Timestamp: storing mtime failed!"); 203 } 204 if (atime != null && 205 atime.to(TimeUnit.SECONDS) != 206 ze.getLastAccessTime().to(TimeUnit.SECONDS)) 207 throw new RuntimeException("Timestamp: storing atime failed!"); 208 if (ctime != null && 209 ctime.to(TimeUnit.SECONDS) != 210 ze.getCreationTime().to(TimeUnit.SECONDS)) 211 throw new RuntimeException("Timestamp: storing ctime failed!"); 212 if (extra != null) { 213 // if extra data exists, the current implementation put it at 214 // the end of the extra data array (implementation detail) 215 byte[] extra1 = ze.getExtra(); 216 if (extra1 == null || extra1.length < extra.length || 217 !Arrays.equals(Arrays.copyOfRange(extra1, 218 extra1.length - extra.length, 219 extra1.length), 220 extra)) { 221 throw new RuntimeException("Timestamp: storing extra field failed!"); 222 } 223 } 224 } 225 testNullHandling()226 static void testNullHandling() { 227 ZipEntry ze = new ZipEntry("TestExtraTime.java"); 228 try { 229 ze.setLastAccessTime(null); 230 throw new RuntimeException("setLastAccessTime(null) should throw NPE"); 231 } catch (NullPointerException ignored) { 232 // pass 233 } 234 try { 235 ze.setCreationTime(null); 236 throw new RuntimeException("setCreationTime(null) should throw NPE"); 237 } catch (NullPointerException ignored) { 238 // pass 239 } 240 try { 241 ze.setLastModifiedTime(null); 242 throw new RuntimeException("setLastModifiedTime(null) should throw NPE"); 243 } catch (NullPointerException ignored) { 244 // pass 245 } 246 } 247 248 // verify that setting and getting any time is possible as per the intent 249 // of 4759491 testTimeConversions()250 static void testTimeConversions() { 251 // Sample across the entire range 252 long step = Long.MAX_VALUE / 100L; 253 testTimeConversions(Long.MIN_VALUE, Long.MAX_VALUE - step, step); 254 255 // Samples through the near future 256 long currentTime = System.currentTimeMillis(); 257 testTimeConversions(currentTime, currentTime + 1_000_000, 10_000); 258 } 259 testTimeConversions(long from, long to, long step)260 static void testTimeConversions(long from, long to, long step) { 261 ZipEntry ze = new ZipEntry("TestExtraTime.java"); 262 for (long time = from; time <= to; time += step) { 263 ze.setTime(time); 264 FileTime lastModifiedTime = ze.getLastModifiedTime(); 265 if (lastModifiedTime.toMillis() != time) { 266 throw new RuntimeException("setTime should make getLastModifiedTime " + 267 "return the specified instant: " + time + 268 " got: " + lastModifiedTime.toMillis()); 269 } 270 if (ze.getTime() != time) { 271 throw new RuntimeException("getTime after setTime, expected: " + 272 time + " got: " + ze.getTime()); 273 } 274 } 275 } 276 check(ZipEntry ze, byte[] extra)277 static void check(ZipEntry ze, byte[] extra) { 278 if (extra != null) { 279 byte[] extra1 = ze.getExtra(); 280 if (extra1 == null || extra1.length < extra.length || 281 !Arrays.equals(Arrays.copyOfRange(extra1, 282 extra1.length - extra.length, 283 extra1.length), 284 extra)) { 285 throw new RuntimeException("Timestamp: storing extra field failed!"); 286 } 287 } 288 } 289 testTagOnlyHandling()290 static void testTagOnlyHandling() throws Throwable { 291 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 292 byte[] extra = new byte[] { 0x0a, 0, 4, 0, 0, 0, 0, 0 }; 293 try (ZipOutputStream zos = new ZipOutputStream(baos)) { 294 ZipEntry ze = new ZipEntry("TestExtraTime.java"); 295 ze.setExtra(extra); 296 zos.putNextEntry(ze); 297 zos.write(new byte[] { 1,2 ,3, 4}); 298 } 299 try (ZipInputStream zis = new ZipInputStream( 300 new ByteArrayInputStream(baos.toByteArray()))) { 301 ZipEntry ze = zis.getNextEntry(); 302 check(ze, extra); 303 } 304 // Android-changed: create file in a temp dir, 'test.dir' is not available. 305 // Path zpath = Paths.get(System.getProperty("test.dir", "."), 306 // "TestExtraTime.zip"); 307 Path zpath = Files.createTempDirectory("TestExtraTime").resolve("TestExtraTime.zip"); 308 Files.copy(new ByteArrayInputStream(baos.toByteArray()), zpath); 309 try (ZipFile zf = new ZipFile(zpath.toFile())) { 310 ZipEntry ze = zf.getEntry("TestExtraTime.java"); 311 check(ze, extra); 312 } finally { 313 Files.delete(zpath); 314 } 315 } 316 checkLastModifiedTimeDOS(FileTime mtime, ZipEntry ze)317 static void checkLastModifiedTimeDOS(FileTime mtime, ZipEntry ze) { 318 FileTime lmt = ze.getLastModifiedTime(); 319 if ((lmt.to(TimeUnit.SECONDS) >>> 1) != (mtime.to(TimeUnit.SECONDS) >>> 1) || 320 lmt.to(TimeUnit.MILLISECONDS) != ze.getTime() || 321 lmt.to(TimeUnit.MILLISECONDS) % 1000 != 0) { 322 throw new RuntimeException("Timestamp: storing mtime in dos format failed!"); 323 } 324 } 325 testNullMtime()326 static void testNullMtime() throws Throwable { 327 Instant now = Instant.now(); 328 FileTime ctime = FileTime.from(now); 329 FileTime atime = FileTime.from(now.plusSeconds(7)); 330 FileTime mtime = FileTime.from(now.plusSeconds(13)); 331 System.out.printf("--------------------%nTesting: [%s]/[%s]/[%s]%n", 332 mtime, atime, ctime); 333 334 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 335 try (ZipOutputStream zos = new ZipOutputStream(baos)) { 336 ZipEntry ze = new ZipEntry("TestExtraTime.java"); 337 ze.setCreationTime(ctime); 338 ze.setLastAccessTime(atime); 339 // ze.setLastModifiedTime(now); 340 ze.setTime(mtime.toMillis()); 341 zos.putNextEntry(ze); 342 zos.write(new byte[] { 1,2 ,3, 4}); 343 } 344 345 try (ZipInputStream zis = new ZipInputStream( 346 new ByteArrayInputStream(baos.toByteArray()))) { 347 ZipEntry ze = zis.getNextEntry(); 348 // check LOC 349 check(null, atime, ctime, ze, null); 350 checkLastModifiedTimeDOS(mtime, ze); 351 } 352 // Android-changed: create file in a temp dir, 'test.dir' is not available. 353 // Path zpath = Paths.get(System.getProperty("test.dir", "."), 354 // "TestExtraTime.zip"); 355 Path zpath = Files.createTempDirectory("TestExtraTime").resolve("TestExtraTime.zip"); 356 Files.copy(new ByteArrayInputStream(baos.toByteArray()), zpath); 357 try (ZipFile zf = new ZipFile(zpath.toFile())) { 358 ZipEntry ze = zf.getEntry("TestExtraTime.java"); 359 // check CEN 360 checkLastModifiedTimeDOS(mtime, ze); 361 } finally { 362 Files.delete(zpath); 363 } 364 } 365 } 366