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