1 /* 2 * Copyright (c) 2019 SAP SE. 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 import java.io.File; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.nio.file.*; 29 import java.nio.file.attribute.BasicFileAttributes; 30 import java.nio.file.attribute.GroupPrincipal; 31 import java.nio.file.attribute.PosixFileAttributeView; 32 import java.nio.file.attribute.PosixFileAttributes; 33 import java.nio.file.attribute.PosixFilePermission; 34 import java.nio.file.attribute.PosixFilePermissions; 35 import java.nio.file.attribute.UserPrincipal; 36 import java.security.AccessController; 37 import java.security.PrivilegedAction; 38 import java.security.PrivilegedActionException; 39 import java.security.PrivilegedExceptionAction; 40 import java.util.Collections; 41 import java.util.Enumeration; 42 import java.util.HashMap; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.concurrent.atomic.AtomicInteger; 46 import java.util.jar.JarEntry; 47 import java.util.jar.JarFile; 48 import java.util.spi.ToolProvider; 49 import java.util.zip.ZipEntry; 50 import java.util.zip.ZipFile; 51 52 import org.testng.annotations.Test; 53 54 import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE; 55 import static java.nio.file.attribute.PosixFilePermission.GROUP_READ; 56 import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE; 57 import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE; 58 import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; 59 import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE; 60 import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; 61 import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; 62 import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; 63 import static org.testng.Assert.assertEquals; 64 import static org.testng.Assert.assertNotNull; 65 import static org.testng.Assert.assertNull; 66 import static org.testng.Assert.assertTrue; 67 import static org.testng.Assert.fail; 68 69 /** 70 * @test 71 * @bug 8213031 72 * @modules jdk.zipfs 73 * jdk.jartool 74 * @run testng TestPosix 75 * @run testng/othervm/java.security.policy=test.policy.posix TestPosix 76 * @summary Test POSIX zip file operations. 77 */ 78 public class TestPosix { 79 private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") 80 .orElseThrow(()->new RuntimeException("jar tool not found")); 81 82 // files and directories 83 private static final Path ZIP_FILE = Paths.get("testPosix.zip"); 84 private static final Path JAR_FILE = Paths.get("testPosix.jar"); 85 private static final Path ZIP_FILE_COPY = Paths.get("testPosixCopy.zip"); 86 private static final Path UNZIP_DIR = Paths.get("unzip/"); 87 88 // permission sets 89 private static final Set<PosixFilePermission> ALLPERMS = 90 PosixFilePermissions.fromString("rwxrwxrwx"); 91 private static final Set<PosixFilePermission> EMPTYPERMS = 92 Collections.<PosixFilePermission>emptySet(); 93 private static final Set<PosixFilePermission> UR = Set.of(OWNER_READ); 94 private static final Set<PosixFilePermission> UW = Set.of(OWNER_WRITE); 95 private static final Set<PosixFilePermission> UE = Set.of(OWNER_EXECUTE); 96 private static final Set<PosixFilePermission> GR = Set.of(GROUP_READ); 97 private static final Set<PosixFilePermission> GW = Set.of(GROUP_WRITE); 98 private static final Set<PosixFilePermission> GE = Set.of(GROUP_EXECUTE); 99 private static final Set<PosixFilePermission> OR = Set.of(OTHERS_READ); 100 private static final Set<PosixFilePermission> OW = Set.of(OTHERS_WRITE); 101 private static final Set<PosixFilePermission> OE = Set.of(OTHERS_EXECUTE); 102 103 // principals 104 private static final UserPrincipal DUMMY_USER = ()->"defusr"; 105 private static final GroupPrincipal DUMMY_GROUP = ()->"defgrp"; 106 107 // FS open options 108 private static final Map<String, Object> ENV_DEFAULT = Collections.<String, Object>emptyMap(); 109 private static final Map<String, Object> ENV_POSIX = Map.of("enablePosixFileAttributes", true); 110 111 // misc 112 private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES}; 113 private static final Map<String, ZipFileEntryInfo> ENTRIES = new HashMap<>(); 114 115 private int entriesCreated; 116 117 static enum checkExpects { 118 contentOnly, 119 noPermDataInZip, 120 permsInZip, 121 permsPosix 122 } 123 124 static class ZipFileEntryInfo { 125 // permissions to set initially 126 private final Set<PosixFilePermission> intialPerms; 127 // permissions to set in a later call 128 private final Set<PosixFilePermission> laterPerms; 129 // permissions that should be effective in the zip file 130 private final Set<PosixFilePermission> permsInZip; 131 // permissions that should be returned by zipfs w/Posix support 132 private final Set<PosixFilePermission> permsPosix; 133 // entry is a directory 134 private final boolean isDir; 135 // need additional read flag in copy test 136 private final boolean setReadFlag; 137 ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms, Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag)138 private ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms, 139 Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag) 140 { 141 this.intialPerms = initialPerms; 142 this.laterPerms = laterPerms; 143 this.permsInZip = permsInZip; 144 this.permsPosix = permsZipPosix; 145 this.isDir = isDir; 146 this.setReadFlag = setReadFlag; 147 } 148 } 149 150 static class CopyVisitor extends SimpleFileVisitor<Path> { 151 private Path from, to; 152 private boolean copyPerms; 153 CopyVisitor(Path from, Path to)154 CopyVisitor(Path from, Path to) { 155 this.from = from; 156 this.to = to; 157 } 158 CopyVisitor(Path from, Path to, boolean copyPerms)159 CopyVisitor(Path from, Path to, boolean copyPerms) { 160 this.from = from; 161 this.to = to; 162 this.copyPerms = copyPerms; 163 } 164 165 @Override preVisitDirectory(Path dir, BasicFileAttributes attrs)166 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 167 FileVisitResult rc = super.preVisitDirectory(dir, attrs); 168 Path target = to.resolve(from.relativize(dir).toString()); 169 if (!Files.exists(target)) { 170 Files.copy(dir, target, COPY_ATTRIBUTES); 171 if (copyPerms) { 172 Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(dir)); 173 } 174 } 175 return rc; 176 } 177 178 @Override visitFile(Path file, BasicFileAttributes attrs)179 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 180 FileVisitResult rc = super.visitFile(file, attrs); 181 Path target = to.resolve(from.relativize(file).toString()); 182 Files.copy(file, target, COPY_ATTRIBUTES); 183 if (copyPerms) { 184 Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(file)); 185 } 186 return rc; 187 } 188 } 189 190 static class DeleteVisitor extends SimpleFileVisitor<Path> { 191 @Override postVisitDirectory(Path dir, IOException exc)192 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 193 FileVisitResult rc = super.postVisitDirectory(dir, exc); 194 Files.delete(dir); 195 return rc; 196 } 197 198 @Override visitFile(Path file, BasicFileAttributes attrs)199 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 200 FileVisitResult rc = super.visitFile(file, attrs); 201 Files.delete(file); 202 return rc; 203 } 204 } 205 206 @FunctionalInterface 207 static interface Executor { doIt()208 void doIt() throws IOException; 209 } 210 211 static { 212 ENTRIES.put("dir", new ZipFileEntryInfo(ALLPERMS, null, ALLPERMS, ALLPERMS, true, false)); 213 ENTRIES.put("uread", new ZipFileEntryInfo(UR, null, UR, UR, false, false)); 214 ENTRIES.put("uwrite", new ZipFileEntryInfo(UW, null, UW, UW, false, true)); 215 ENTRIES.put("uexec", new ZipFileEntryInfo(UE, null, UE, UE, false, true)); 216 ENTRIES.put("gread", new ZipFileEntryInfo(GR, null, GR, GR, false, true)); 217 ENTRIES.put("gwrite", new ZipFileEntryInfo(GW, null, GW, GW, false, true)); 218 ENTRIES.put("gexec", new ZipFileEntryInfo(GE, null, GE, GE, false, true)); 219 ENTRIES.put("oread", new ZipFileEntryInfo(OR, null, OR, OR, false, true)); 220 ENTRIES.put("owrite", new ZipFileEntryInfo(OW, null, OW, OW, false, true)); 221 ENTRIES.put("oexec", new ZipFileEntryInfo(OE, null, OE, OE, false, true)); 222 ENTRIES.put("emptyperms", new ZipFileEntryInfo(EMPTYPERMS, null, EMPTYPERMS, EMPTYPERMS, false, true)); 223 ENTRIES.put("noperms", new ZipFileEntryInfo(null, null, null, ALLPERMS, false, false)); 224 ENTRIES.put("permslater", new ZipFileEntryInfo(null, UR, UR, UR, false, false)); 225 } 226 expectedDefaultOwner(Path zf)227 private static String expectedDefaultOwner(Path zf) { 228 try { 229 try { 230 PrivilegedExceptionAction<String> pa = ()->Files.getOwner(zf).getName(); 231 return AccessController.doPrivileged(pa); 232 } catch (UnsupportedOperationException e) { 233 // if we can't get the owner of the file, we fall back to system property user.name 234 PrivilegedAction<String> pa = ()->System.getProperty("user.name"); 235 return AccessController.doPrivileged(pa); 236 } 237 } catch (PrivilegedActionException | SecurityException e) { 238 System.out.println("Caught " + e.getClass().getName() + "(" + e.getMessage() + 239 ") when running a privileged operation to get the default owner."); 240 return null; 241 } 242 } 243 expectedDefaultGroup(Path zf, String defaultOwner)244 private static String expectedDefaultGroup(Path zf, String defaultOwner) { 245 try { 246 try { 247 PosixFileAttributeView zfpv = Files.getFileAttributeView(zf, PosixFileAttributeView.class); 248 if (zfpv == null) { 249 return defaultOwner; 250 } 251 PrivilegedExceptionAction<String> pa = ()->zfpv.readAttributes().group().getName(); 252 return AccessController.doPrivileged(pa); 253 } catch (UnsupportedOperationException e) { 254 return defaultOwner; 255 } 256 } catch (PrivilegedActionException | SecurityException e) { 257 System.out.println("Caught an exception when running a privileged operation to get the default group."); 258 e.printStackTrace(); 259 return null; 260 } 261 } 262 putEntry(FileSystem fs, String name, ZipFileEntryInfo entry)263 private void putEntry(FileSystem fs, String name, ZipFileEntryInfo entry) throws IOException { 264 if (entry.isDir) { 265 if (entry.intialPerms == null) { 266 Files.createDirectory(fs.getPath(name)); 267 } else { 268 Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms)); 269 } 270 271 } else { 272 if (entry.intialPerms == null) { 273 Files.createFile(fs.getPath(name)); 274 } else { 275 Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms)); 276 } 277 } 278 if (entry.laterPerms != null) { 279 Files.setAttribute(fs.getPath(name), "zip:permissions", entry.laterPerms); 280 } 281 entriesCreated++; 282 } 283 createTestZipFile(Path zpath, Map<String, Object> env)284 private FileSystem createTestZipFile(Path zpath, Map<String, Object> env) throws IOException { 285 if (Files.exists(zpath)) { 286 System.out.println("Deleting old " + zpath + "..."); 287 Files.delete(zpath); 288 } 289 System.out.println("Creating " + zpath + "..."); 290 entriesCreated = 0; 291 var opts = new HashMap<String, Object>(); 292 opts.putAll(env); 293 opts.put("create", true); 294 FileSystem fs = FileSystems.newFileSystem(zpath, opts); 295 for (String name : ENTRIES.keySet()) { 296 putEntry(fs, name, ENTRIES.get(name)); 297 } 298 return fs; 299 } 300 createEmptyZipFile(Path zpath, Map<String, Object> env)301 private FileSystem createEmptyZipFile(Path zpath, Map<String, Object> env) throws IOException { 302 if (Files.exists(zpath)) { 303 System.out.println("Deleting old " + zpath + "..."); 304 Files.delete(zpath); 305 } 306 System.out.println("Creating " + zpath + "..."); 307 var opts = new HashMap<String, Object>(); 308 opts.putAll(env); 309 opts.put("create", true); 310 return FileSystems.newFileSystem(zpath, opts); 311 } 312 delTree(Path p)313 private void delTree(Path p) throws IOException { 314 if (Files.exists(p)) { 315 Files.walkFileTree(p, new DeleteVisitor()); 316 } 317 } 318 addOwnerRead(Path root)319 private void addOwnerRead(Path root) throws IOException { 320 for (String name : ENTRIES.keySet()) { 321 ZipFileEntryInfo ei = ENTRIES.get(name); 322 if (!ei.setReadFlag) { 323 continue; 324 } 325 Path setReadOn = root.resolve(name); 326 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(setReadOn); 327 perms.add(OWNER_READ); 328 Files.setPosixFilePermissions(setReadOn, perms); 329 } 330 } 331 removeOwnerRead(Path root)332 private void removeOwnerRead(Path root) throws IOException { 333 for (String name : ENTRIES.keySet()) { 334 ZipFileEntryInfo ei = ENTRIES.get(name); 335 if (!ei.setReadFlag) { 336 continue; 337 } 338 Path removeReadFrom = root.resolve(name); 339 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(removeReadFrom); 340 perms.remove(OWNER_READ); 341 Files.setPosixFilePermissions(removeReadFrom, perms); 342 } 343 } 344 345 @SuppressWarnings("unchecked") checkEntry(Path file, checkExpects expected)346 private void checkEntry(Path file, checkExpects expected) { 347 System.out.println("Checking " + file + "..."); 348 String name = file.getFileName().toString(); 349 ZipFileEntryInfo ei = ENTRIES.get(name); 350 assertNotNull(ei, "Found unknown entry " + name + "."); 351 BasicFileAttributes attrs = null; 352 if (expected == checkExpects.permsPosix) { 353 try { 354 attrs = Files.readAttributes(file, PosixFileAttributes.class); 355 } catch (IOException e) { 356 e.printStackTrace(); 357 fail("Caught IOException reading file attributes (posix) for " + name + ": " + e.getMessage()); 358 } 359 } else { 360 try { 361 attrs = Files.readAttributes(file, BasicFileAttributes.class); 362 } catch (IOException e) { 363 e.printStackTrace(); 364 fail("Caught IOException reading file attributes (basic) " + name + ": " + e.getMessage()); 365 } 366 } 367 assertEquals(Files.isDirectory(file), ei.isDir, "Unexpected directory attribute for:" + System.lineSeparator() + attrs); 368 369 if (expected == checkExpects.contentOnly) { 370 return; 371 } 372 373 Set<PosixFilePermission> permissions; 374 if (expected == checkExpects.permsPosix) { 375 try { 376 permissions = Files.getPosixFilePermissions(file); 377 } catch (IOException e) { 378 e.printStackTrace(); 379 fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs); 380 return; 381 } 382 comparePermissions(ei.permsPosix, permissions); 383 } else if (expected == checkExpects.permsInZip || expected == checkExpects.noPermDataInZip) { 384 try { 385 permissions = (Set<PosixFilePermission>)Files.getAttribute(file, "zip:permissions"); 386 } catch (IOException e) { 387 e.printStackTrace(); 388 fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs); 389 return; 390 } 391 comparePermissions(expected == checkExpects.noPermDataInZip ? null : ei.permsInZip, permissions); 392 } 393 } 394 doCheckEntries(Path path, checkExpects expected)395 private void doCheckEntries(Path path, checkExpects expected) throws IOException { 396 AtomicInteger entries = new AtomicInteger(); 397 398 try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) { 399 paths.forEach(file -> { 400 entries.getAndIncrement(); 401 checkEntry(file, expected); 402 }); 403 } 404 System.out.println("Number of entries: " + entries.get() + "."); 405 assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries."); 406 } 407 checkEntries(FileSystem fs, checkExpects expected)408 private void checkEntries(FileSystem fs, checkExpects expected) throws IOException { 409 System.out.println("Checking permissions on file system " + fs + "..."); 410 doCheckEntries(fs.getPath("/"), expected); 411 } 412 checkEntries(Path path, checkExpects expected)413 private void checkEntries(Path path, checkExpects expected) throws IOException { 414 System.out.println("Checking permissions on path " + path + "..."); 415 doCheckEntries(path, expected); 416 } 417 throwsUOE(Executor e)418 private boolean throwsUOE(Executor e) throws IOException { 419 try { 420 e.doIt(); 421 return false; 422 } catch (UnsupportedOperationException exc) { 423 return true; 424 } 425 } 426 comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual)427 private void comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual) { 428 if (expected == null) { 429 assertNull(actual, "Permissions are not null"); 430 } else { 431 assertNotNull(actual, "Permissions are null."); 432 assertEquals(actual.size(), expected.size(), "Unexpected number of permissions (" + 433 actual.size() + " received vs " + expected.size() + " expected)."); 434 for (PosixFilePermission p : expected) { 435 assertTrue(actual.contains(p), "Posix permission " + p + " missing."); 436 } 437 } 438 } 439 440 /** 441 * This tests whether the entries in a zip file created w/o 442 * Posix support are correct. 443 * 444 * @throws IOException 445 */ 446 @Test testDefault()447 public void testDefault() throws IOException { 448 // create zip file using zipfs with default options 449 createTestZipFile(ZIP_FILE, ENV_DEFAULT).close(); 450 // check entries on zipfs with default options 451 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) { 452 checkEntries(zip, checkExpects.permsInZip); 453 } 454 // check entries on zipfs with posix options 455 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) { 456 checkEntries(zip, checkExpects.permsPosix); 457 } 458 } 459 460 /** 461 * This tests whether the entries in a zip file created w/ 462 * Posix support are correct. 463 * 464 * @throws IOException 465 */ 466 @Test testPosix()467 public void testPosix() throws IOException { 468 // create zip file using zipfs with posix option 469 createTestZipFile(ZIP_FILE, ENV_POSIX).close(); 470 // check entries on zipfs with default options 471 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) { 472 checkEntries(zip, checkExpects.permsInZip); 473 } 474 // check entries on zipfs with posix options 475 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) { 476 checkEntries(zip, checkExpects.permsPosix); 477 } 478 } 479 480 /** 481 * This tests whether the entries in a zip file copied from another 482 * are correct. 483 * 484 * @throws IOException 485 */ 486 @Test testCopy()487 public void testCopy() throws IOException { 488 // copy zip to zip with default options 489 try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT); 490 FileSystem zipOut = createEmptyZipFile(ZIP_FILE_COPY, ENV_DEFAULT)) { 491 Path from = zipIn.getPath("/"); 492 Files.walkFileTree(from, new CopyVisitor(from, zipOut.getPath("/"))); 493 } 494 // check entries on copied zipfs with default options 495 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) { 496 checkEntries(zip, checkExpects.permsInZip); 497 } 498 // check entries on copied zipfs with posix options 499 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) { 500 checkEntries(zip, checkExpects.permsPosix); 501 } 502 } 503 504 /** 505 * This tests whether the entries of a zip file look correct after extraction 506 * and re-packing. When not using zipfs with Posix support, we expect the 507 * effective permissions in the resulting zip file to be empty. 508 * 509 * @throws IOException 510 */ 511 @Test testUnzipDefault()512 public void testUnzipDefault() throws IOException { 513 delTree(UNZIP_DIR); 514 Files.createDirectory(UNZIP_DIR); 515 516 try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) { 517 Path from = srcZip.getPath("/"); 518 Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR)); 519 } 520 521 // we just check that the entries got extracted to file system 522 checkEntries(UNZIP_DIR, checkExpects.contentOnly); 523 524 // the target zip file is opened with Posix support 525 // but we expect no permission data to be copied using the default copy method 526 try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) { 527 Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/"))); 528 } 529 530 // check entries on copied zipfs - no permission data should exist 531 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) { 532 checkEntries(zip, checkExpects.noPermDataInZip); 533 } 534 } 535 536 /** 537 * This tests whether the entries of a zip file look correct after extraction 538 * and re-packing. If the default file system supports Posix, we test whether we 539 * correctly carry the Posix permissions. Otherwise there's not much to test in 540 * this method. 541 * 542 * @throws IOException 543 */ 544 @Test testUnzipPosix()545 public void testUnzipPosix() throws IOException { 546 delTree(UNZIP_DIR); 547 Files.createDirectory(UNZIP_DIR); 548 549 try { 550 Files.getPosixFilePermissions(UNZIP_DIR); 551 } catch (Exception e) { 552 // if we run into any exception here, be it because of the fact that the file system 553 // is not Posix or if we have insufficient security permissions, we can't do this test. 554 System.out.println("This can't be tested here because of " + e); 555 return; 556 } 557 558 try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_POSIX)) { 559 Path from = srcZip.getPath("/"); 560 // copy permissions as well 561 Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR, true)); 562 } 563 564 // permissions should have been propagated to file system 565 checkEntries(UNZIP_DIR, checkExpects.permsPosix); 566 567 try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) { 568 // Make some files owner readable to be able to copy them into the zipfs 569 addOwnerRead(UNZIP_DIR); 570 571 // copy permissions as well 572 Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/"), true)); 573 574 // Fix back all the files in the target zip file which have been made readable before 575 removeOwnerRead(tgtZip.getPath("/")); 576 } 577 578 // check entries on copied zipfs - permission data should have been propagated 579 try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) { 580 checkEntries(zip, checkExpects.permsPosix); 581 } 582 } 583 584 /** 585 * Tests POSIX default behavior. 586 * 587 * @throws IOException 588 */ 589 @Test testPosixDefaults()590 public void testPosixDefaults() throws IOException { 591 // test with posix = false, expect UnsupportedOperationException 592 try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) { 593 var entry = zipIn.getPath("/dir"); 594 assertTrue(throwsUOE(()->Files.getPosixFilePermissions(entry))); 595 assertTrue(throwsUOE(()->Files.setPosixFilePermissions(entry, UW))); 596 assertTrue(throwsUOE(()->Files.getOwner(entry))); 597 assertTrue(throwsUOE(()->Files.setOwner(entry, DUMMY_USER))); 598 assertTrue(throwsUOE(()->Files.getFileAttributeView(entry, PosixFileAttributeView.class))); 599 } 600 601 // test with posix = true -> default values 602 try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) { 603 String defaultOwner = expectedDefaultOwner(ZIP_FILE); 604 String defaultGroup = expectedDefaultGroup(ZIP_FILE, defaultOwner); 605 var entry = zipIn.getPath("/noperms"); 606 comparePermissions(ALLPERMS, Files.getPosixFilePermissions(entry)); 607 var owner = Files.getOwner(entry); 608 assertNotNull(owner, "owner should not be null"); 609 if (defaultOwner != null) { 610 assertEquals(owner.getName(), defaultOwner); 611 } 612 Files.setOwner(entry, DUMMY_USER); 613 assertEquals(Files.getOwner(entry), DUMMY_USER); 614 var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class); 615 var group = view.readAttributes().group(); 616 assertNotNull(group, "group must not be null"); 617 if (defaultGroup != null) { 618 assertEquals(group.getName(), defaultGroup); 619 } 620 view.setGroup(DUMMY_GROUP); 621 assertEquals(view.readAttributes().group(), DUMMY_GROUP); 622 entry = zipIn.getPath("/uexec"); 623 Files.setPosixFilePermissions(entry, GR); // will be persisted 624 comparePermissions(GR, Files.getPosixFilePermissions(entry)); 625 } 626 627 // test with posix = true + custom defaults of type String 628 try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true, 629 "defaultOwner", "auser", "defaultGroup", "agroup", "defaultPermissions", "r--------"))) 630 { 631 var entry = zipIn.getPath("/noperms"); 632 comparePermissions(UR, Files.getPosixFilePermissions(entry)); 633 assertEquals(Files.getOwner(entry).getName(), "auser"); 634 var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class); 635 assertEquals(view.readAttributes().group().getName(), "agroup"); 636 // check if the change to permissions of /uexec was persisted 637 comparePermissions(GR, Files.getPosixFilePermissions(zipIn.getPath("/uexec"))); 638 } 639 640 // test with posix = true + custom defaults as Objects 641 try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true, 642 "defaultOwner", DUMMY_USER, "defaultGroup", DUMMY_GROUP, "defaultPermissions", UR))) 643 { 644 var entry = zipIn.getPath("/noperms"); 645 comparePermissions(UR, Files.getPosixFilePermissions(entry)); 646 assertEquals(Files.getOwner(entry), DUMMY_USER); 647 var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class); 648 assertEquals(view.readAttributes().group(), DUMMY_GROUP); 649 } 650 } 651 652 /** 653 * Sanity check to test whether the zip file can be unzipped with the java.util.zip API. 654 * 655 * @throws IOException 656 */ 657 @Test testUnzipWithJavaUtilZip()658 public void testUnzipWithJavaUtilZip() throws IOException { 659 createTestZipFile(ZIP_FILE, ENV_DEFAULT).close(); 660 delTree(UNZIP_DIR); 661 Files.createDirectory(UNZIP_DIR); 662 File targetDir = UNZIP_DIR.toFile(); 663 try (ZipFile zf = new ZipFile(ZIP_FILE.toFile())) { 664 Enumeration<? extends ZipEntry> zenum = zf.entries(); 665 while (zenum.hasMoreElements()) { 666 ZipEntry ze = zenum.nextElement(); 667 File target = new File(targetDir + File.separator + ze.getName()); 668 if (ze.isDirectory()) { 669 target.mkdir(); 670 continue; 671 } 672 try (InputStream is = zf.getInputStream(ze); 673 FileOutputStream fos = new FileOutputStream(target)) 674 { 675 while (is.available() > 0) { 676 fos.write(is.read()); 677 } 678 } 679 } 680 } 681 } 682 683 /** 684 * Sanity check to test whether a jar file created with zipfs can be 685 * extracted with the java.util.jar API. 686 * 687 * @throws IOException 688 */ 689 @Test testJarFile()690 public void testJarFile() throws IOException { 691 // create jar file using zipfs with default options 692 createTestZipFile(JAR_FILE, ENV_DEFAULT).close(); 693 694 // extract it using java.util.jar.JarFile 695 delTree(UNZIP_DIR); 696 Files.createDirectory(UNZIP_DIR); 697 File targetDir = UNZIP_DIR.toFile(); 698 try (JarFile jf = new JarFile(ZIP_FILE.toFile())) { 699 Enumeration<? extends JarEntry> zenum = jf.entries(); 700 while (zenum.hasMoreElements()) { 701 JarEntry ze = zenum.nextElement(); 702 File target = new File(targetDir + File.separator + ze.getName()); 703 if (ze.isDirectory()) { 704 target.mkdir(); 705 continue; 706 } 707 try (InputStream is = jf.getInputStream(ze); 708 FileOutputStream fos = new FileOutputStream(target)) 709 { 710 while (is.available() > 0) { 711 fos.write(is.read()); 712 } 713 } 714 } 715 } 716 717 // extract it using the jar tool 718 delTree(UNZIP_DIR); 719 System.out.println("jar xvf " + JAR_FILE); 720 721 // the run method catches IOExceptions, we need to expose them 722 int rc = JAR_TOOL.run(System.out, System.err, "xvf", JAR_FILE.toString()); 723 assertEquals(rc, 0, "Return code of jar call is " + rc + " but expected 0"); 724 } 725 } 726