1 /*
   2  * Copyright (c) 2018, SAP SE. All rights reserved.
   3  *
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.  Oracle designates this
   9  * particular file as subject to the "Classpath" exception as provided
  10  * by Oracle in the LICENSE file that accompanied this code.
  11  *
  12  * This code is distributed in the hope that it will be useful, but WITHOUT
  13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  15  * version 2 for more details (a copy is included in the LICENSE file that
  16  * accompanied this code).
  17  *
  18  * You should have received a copy of the GNU General Public License version
  19  * 2 along with this work; if not, write to the Free Software Foundation,
  20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  21  *
  22  * Please contact SAP SE, Dietmar-Hopp-Allee 16, 69190 Walldorf, Germany
  23  * or visit www.sap.com if you need additional information or have any
  24  * questions.
  25  */
  26 
  27 import static java.nio.file.attribute.PosixFilePermission.*;
  28 import static org.testng.Assert.assertEquals;
  29 import static org.testng.Assert.assertNotEquals;
  30 import static org.testng.Assert.assertNotNull;
  31 import static org.testng.Assert.assertTrue;
  32 import static org.testng.Assert.fail;
  33 
  34 import java.io.IOException;
  35 import java.nio.file.CopyOption;
  36 import java.nio.file.DirectoryStream;
  37 import java.nio.file.FileSystem;
  38 import java.nio.file.FileVisitResult;
  39 import java.nio.file.Files;
  40 import java.nio.file.Path;
  41 import java.nio.file.Paths;
  42 import java.nio.file.SimpleFileVisitor;
  43 import java.nio.file.StandardCopyOption;
  44 import java.nio.file.attribute.BasicFileAttributes;
  45 import java.nio.file.attribute.PosixFileAttributes;
  46 import java.nio.file.attribute.PosixFileAttributeView;
  47 import java.nio.file.attribute.PosixFilePermission;
  48 import java.nio.file.attribute.PosixFilePermissions;
  49 import java.nio.file.spi.FileSystemProvider;
  50 import java.util.Collections;
  51 import java.util.HashMap;
  52 import java.util.Map;
  53 import java.util.Set;
  54 import java.util.concurrent.atomic.AtomicInteger;
  55 
  56 import org.testng.annotations.Test;
  57 
  58 /**
  59  * @test
  60  * @modules jdk.zipfs
  61  * @run testng TestPosixPerms
  62  * @summary Test zip file operations handling POSIX permissions.
  63  */
  64 public class TestPosixPerms {
  65 
  66     private static final String ZIP_FILE = "testPosixPerms.zip";
  67     private static final String ZIP_FILE_COPY = "testPosixPermsCopy.zip";
  68     private static final String ZIP_FILE_NEW = "testPosixPermsNew.zip";
  69     private static final String UNZIP_DIR = "unzip/";
  70     private static final Map<String, Object> CREATE_TRUE = new HashMap<>();
  71     private static final FileSystemProvider zipFS;
  72     private static final CopyOption[] NO_OPTIONS = {};
  73     private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES};
  74 
  75     public static class CopyVisitor extends SimpleFileVisitor<Path> {
  76         private Path from, to;
  77         private final CopyOption[] options;
  78 
  79         public CopyVisitor(Path from, Path to, boolean manualCopyPosixPerms, CopyOption... options) {
  80             this.from = from;
  81             this.to = to;
  82             this.options = manualCopyPosixPerms ? NO_OPTIONS : COPY_ATTRIBUTES;
  83         }
  84 
  85         private static void copyPermissions(Path source, Path target) throws IOException {
  86             PosixFileAttributeView targetAttrs;
  87             if ((targetAttrs = Files.getFileAttributeView(target, PosixFileAttributeView.class)) != null) {
  88                 try {
  89                     PosixFileAttributes sourceAttrs = Files.readAttributes(source, PosixFileAttributes.class);
  90                     if (sourceAttrs != null) {
  91                         targetAttrs.setPermissions(sourceAttrs.permissions());
  92                     }
  93                 } catch (UnsupportedOperationException uoe) {
  94                     // Just ignore if source has no Posix file permissions.
  95                 }
  96             }
  97         }
  98 
  99         @Override
 100         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
 101             Path target = to.resolve(from.relativize(dir).toString());
 102             if (!Files.exists(target)) {
 103                 Files.copy(dir, target, options);
 104                 if (options == NO_OPTIONS) {
 105                     copyPermissions(dir, target);
 106                 }
 107             }
 108             return FileVisitResult.CONTINUE;
 109         }
 110 
 111         @Override
 112         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 113             Path target = to.resolve(from.relativize(file).toString());
 114             Files.copy(file, target, options);
 115             if (options == NO_OPTIONS) {
 116                 copyPermissions(file, target);
 117             }
 118             return FileVisitResult.CONTINUE;
 119         }
 120     }
 121 
 122     private int entriesCreated;
 123 
 124     static {
 125         zipFS = getZipFSProvider();
 126         assertNotNull(zipFS, "ZIP filesystem provider is not installed");
 127         CREATE_TRUE.put("create", "true");
 128     }
 129 
 130     private static FileSystemProvider getZipFSProvider() {
 131         for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
 132             if ("jar".equals(provider.getScheme()))
 133                 return provider;
 134         }
 135         return null;
 136     }
 137 
 138     private void checkPermissionsOfEntry(Path file, boolean directory, Set<PosixFilePermission> expected) {
 139         System.out.println("Checking " + file + "...");
 140         assertEquals(Files.isDirectory(file), directory, "Unexpected directory attribute.");
 141         try {
 142             System.out.println(Files.readAttributes(file, PosixFileAttributes.class).toString());
 143         } catch (IOException e) {
 144             fail("Failed to list file attributes (posix) for entry.", e);
 145         }
 146         try {
 147             Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(file);
 148             assertNotEquals(permissions, null, "Retruned permissions of entry are null. " +
 149                 "This should not happen but we should rather see an UnsupportedOperationException.");
 150             assertNotEquals(expected, null, "Got a set of " + permissions.size() +
 151                 " permissions but expected null/UnsupportedOperationException.");
 152             assertEquals(permissions.size(), expected.size(), "Unexpected number of permissions( " +
 153                 permissions.size() + " received vs " + expected.size() + " expected).");
 154             for (PosixFilePermission p : expected) {
 155                 assertTrue(permissions.contains(p), "Posix permission " + p + " missing.");
 156             }
 157         } catch (UnsupportedOperationException e) {
 158             if (expected != null) {
 159                 fail("Unexpected: No posix permissions associated with entry.");
 160             }
 161         } catch (IOException e) {
 162             fail("Caught unexpected exception obtaining posix file permissions.", e);
 163         }
 164     }
 165 
 166     private void putFile(FileSystem fs, String name, Set<PosixFilePermission> perms) throws IOException {
 167         if (perms == null) {
 168             Files.createFile(fs.getPath(name));
 169         } else {
 170             Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(perms));
 171         }
 172         entriesCreated++;
 173     }
 174 
 175     private void putDirectory(FileSystem fs, String name, Set<PosixFilePermission> perms) throws IOException {
 176         if (perms == null) {
 177             Files.createDirectory(fs.getPath(name));
 178         } else {
 179             Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(perms));
 180         }
 181         entriesCreated++;
 182     }
 183 
 184     private FileSystem openOrcreateZipFile(Path zpath) throws Exception {
 185         if (Files.exists(zpath)) {
 186             FileSystem fs = zipFS.newFileSystem(zpath, new HashMap<>());
 187             return fs;
 188         } else {
 189             System.out.println("Create " + zpath + "...");
 190             FileSystem fs = zipFS.newFileSystem(zpath, CREATE_TRUE);
 191             putDirectory(fs, "dir", Set.of(
 192                 OWNER_READ, OWNER_WRITE, OWNER_EXECUTE,
 193                 GROUP_READ, GROUP_WRITE, GROUP_EXECUTE,
 194                 OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE));
 195             putFile(fs, "uread", Set.of(OWNER_READ));
 196             putFile(fs, "uwrite", Set.of(OWNER_WRITE));
 197             putFile(fs, "uexec", Set.of(OWNER_EXECUTE));
 198             putFile(fs, "gread", Set.of(GROUP_READ));
 199             putFile(fs, "gwrite", Set.of(GROUP_WRITE));
 200             putFile(fs, "gexec", Set.of(GROUP_EXECUTE));
 201             putFile(fs, "oread", Set.of(OTHERS_READ));
 202             putFile(fs, "owrite", Set.of(OTHERS_WRITE));
 203             putFile(fs, "oexec", Set.of(OTHERS_EXECUTE));
 204             putFile(fs, "emptyperms", Collections.<PosixFilePermission>emptySet());
 205             putFile(fs, "noperms", null);
 206             putFile(fs, "permsaddedlater", null);
 207             Files.setPosixFilePermissions(fs.getPath("permsaddedlater"), Set.of(OWNER_READ));
 208             return fs;
 209         }
 210     }
 211 
 212     private void checkPosixPerms(Path path) throws Exception {
 213         AtomicInteger entries = new AtomicInteger();
 214 
 215         try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) {
 216             paths.forEach(file -> {
 217                 entries.getAndIncrement();
 218                 String name = file.getFileName().toString();
 219                 if (name.startsWith("dir")) {
 220                     checkPermissionsOfEntry(file, true, Set.of(
 221                         OWNER_READ, OWNER_WRITE, OWNER_EXECUTE,
 222                         GROUP_READ, GROUP_WRITE, GROUP_EXECUTE,
 223                         OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE));
 224                 } else if (name.equals("uread")) {
 225                     checkPermissionsOfEntry(file, false, Set.of(OWNER_READ));
 226                 } else if (name.equals("uwrite")) {
 227                     checkPermissionsOfEntry(file, false, Set.of(OWNER_WRITE));
 228                 } else if (name.equals("uexec")) {
 229                     checkPermissionsOfEntry(file, false, Set.of(OWNER_EXECUTE));
 230                 } else if (name.equals("gread")) {
 231                     checkPermissionsOfEntry(file, false, Set.of(GROUP_READ));
 232                 } else if (name.equals("gwrite")) {
 233                     checkPermissionsOfEntry(file, false, Set.of(GROUP_WRITE));
 234                 } else if (name.equals("gexec")) {
 235                     checkPermissionsOfEntry(file, false, Set.of(GROUP_EXECUTE));
 236                 } else if (name.equals("oread")) {
 237                     checkPermissionsOfEntry(file, false, Set.of(OTHERS_READ));
 238                 } else if (name.equals("owrite")) {
 239                     checkPermissionsOfEntry(file, false, Set.of(OTHERS_WRITE));
 240                 } else if (name.equals("oexec")) {
 241                     checkPermissionsOfEntry(file, false, Set.of(OTHERS_EXECUTE));
 242                 } else if (name.equals("emptyperms")) {
 243                     checkPermissionsOfEntry(file, false, Collections.<PosixFilePermission>emptySet());
 244                 } else if (name.equals("noperms")) {
 245                     // Only check for "no permissions" in files in the zip file system
 246                     // If such a file gets extracted into a "normal" file system it will
 247                     // get the system default permissions (i.e. "umask")
 248                     if ("jar".equals(path.getFileSystem().provider().getScheme())) {
 249                         checkPermissionsOfEntry(file, false, null);
 250                     }
 251                 } else if (name.equals("permsaddedlater")) {
 252                     checkPermissionsOfEntry(file, false, Set.of(OWNER_READ));
 253                 } else {
 254                     fail("Found unknown entry " + name + ".");
 255                 }
 256             });
 257         }
 258         System.out.println("Number of entries: " + entries.get() + ".");
 259         assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries.");
 260     }
 261 
 262     @Test(priority=0)
 263     public void testWriteAndReadArchiveWithPosixPerms() throws Exception {
 264         Path in = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE);
 265 
 266         try (FileSystem zipIn = openOrcreateZipFile(in)) {
 267             System.out.println("Test reading " + in + "...");
 268             checkPosixPerms(zipIn.getPath("/"));
 269         }
 270     }
 271 
 272     @Test(priority=1)
 273     public void testPosixPermsAfterCopy() throws Exception {
 274         Path in = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE);
 275         Path out = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE_COPY);
 276 
 277         try (FileSystem zipIn = openOrcreateZipFile(in);
 278              FileSystem zipOut = zipFS.newFileSystem(out, CREATE_TRUE)) {
 279             Path from = zipIn.getPath("/");
 280             Path to = zipOut.getPath("/");
 281             Files.walkFileTree(from, new CopyVisitor(from, to, false, StandardCopyOption.COPY_ATTRIBUTES));
 282             System.out.println("Test reading " + out + "...");
 283             checkPosixPerms(to);
 284         }
 285     }
 286 
 287     @Test(priority=2)
 288     public void testPosixPermsAfterUnzip() throws Exception {
 289         Path in = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE);
 290         Path out = Files.createDirectory(Paths.get(System.getProperty("test.dir", "."), UNZIP_DIR));
 291         boolean posixFS;
 292         try {
 293             Files.getPosixFilePermissions(out);
 294             posixFS = true;
 295         } catch (UnsupportedOperationException e) {
 296             posixFS = false;
 297         }
 298 
 299         try (FileSystem zipIn = openOrcreateZipFile(in)) {
 300             Path from = zipIn.getPath("/");
 301             Files.walkFileTree(from, new CopyVisitor(from, out, true, StandardCopyOption.COPY_ATTRIBUTES));
 302             System.out.println("Test reading " + out + "...");
 303             if (posixFS) {
 304                 checkPosixPerms(out);
 305             }
 306         }
 307     }
 308 
 309     @Test(priority=3)
 310     public void testPosixPermsAfterZip() throws Exception {
 311         Path in = Paths.get(System.getProperty("test.dir", "."), UNZIP_DIR);
 312         Path out = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE_NEW);
 313         boolean posixFS;
 314         try {
 315             Files.getPosixFilePermissions(in);
 316             posixFS = true;
 317         } catch (UnsupportedOperationException e) {
 318             posixFS = false;
 319         }
 320 
 321         try (FileSystem zipOut = zipFS.newFileSystem(out, CREATE_TRUE)) {
 322             out = zipOut.getPath("/");
 323 
 324             // Have to hack this manually otherwise we won't be able to read these files at all
 325             Set<PosixFilePermission> emptyperms_p = null, gexec_p = null, gwrite_p = null, gread_p = null,
 326                 oread_p = null, oexec_p = null, owrite_p = null, uexec_p = null, uwrite_p = null;
 327             if (posixFS) {
 328                 Path emptyperms = in.resolve("emptyperms");
 329                 emptyperms_p = Files.getPosixFilePermissions(emptyperms);
 330                 emptyperms_p.add(OWNER_READ);
 331                 Files.setPosixFilePermissions(emptyperms, emptyperms_p);
 332                 Path gexec = in.resolve("gexec");
 333                 gexec_p = Files.getPosixFilePermissions(gexec);
 334                 gexec_p.add(OWNER_READ);
 335                 Files.setPosixFilePermissions(gexec, gexec_p);
 336                 Path gwrite = in.resolve("gwrite");
 337                 gwrite_p = Files.getPosixFilePermissions(gwrite);
 338                 gwrite_p.add(OWNER_READ);
 339                 Files.setPosixFilePermissions(gwrite, gwrite_p);
 340                 Path gread = in.resolve("gread");
 341                 gread_p = Files.getPosixFilePermissions(gread);
 342                 gread_p.add(OWNER_READ);
 343                 Files.setPosixFilePermissions(gread, gread_p);
 344                 Path oread = in.resolve("oread");
 345                 oread_p = Files.getPosixFilePermissions(oread);
 346                 oread_p.add(OWNER_READ);
 347                 Files.setPosixFilePermissions(oread, oread_p);
 348                 Path oexec = in.resolve("oexec");
 349                 oexec_p = Files.getPosixFilePermissions(oexec);
 350                 oexec_p.add(OWNER_READ);
 351                 Files.setPosixFilePermissions(oexec, oexec_p);
 352                 Path owrite = in.resolve("owrite");
 353                 owrite_p = Files.getPosixFilePermissions(owrite);
 354                 owrite_p.add(OWNER_READ);
 355                 Files.setPosixFilePermissions(owrite, owrite_p);
 356                 Path uexec = in.resolve("uexec");
 357                 uexec_p = Files.getPosixFilePermissions(uexec);
 358                 uexec_p.add(OWNER_READ);
 359                 Files.setPosixFilePermissions(uexec, uexec_p);
 360                 Path uwrite = in.resolve("uwrite");
 361                 uwrite_p = Files.getPosixFilePermissions(uwrite);
 362                 uwrite_p.add(OWNER_READ);
 363                 Files.setPosixFilePermissions(uwrite, uwrite_p);
 364             }
 365 
 366             Files.walkFileTree(in, new CopyVisitor(in, out, true, StandardCopyOption.COPY_ATTRIBUTES));
 367 
 368             if (posixFS) {
 369                 // Fix back all the files in the target zip file which have been made readable before
 370                 emptyperms_p.remove(OWNER_READ);
 371                 Files.setPosixFilePermissions(zipOut.getPath("emptyperms"), emptyperms_p);
 372                 gexec_p.remove(OWNER_READ);
 373                 Files.setPosixFilePermissions(zipOut.getPath("gexec"), gexec_p);
 374                 gwrite_p.remove(OWNER_READ);
 375                 Files.setPosixFilePermissions(zipOut.getPath("gwrite"), gwrite_p);
 376                 gread_p.remove(OWNER_READ);
 377                 Files.setPosixFilePermissions(zipOut.getPath("gread"), gread_p);
 378                 oread_p.remove(OWNER_READ);
 379                 Files.setPosixFilePermissions(zipOut.getPath("oread"), oread_p);
 380                 oexec_p.remove(OWNER_READ);
 381                 Files.setPosixFilePermissions(zipOut.getPath("oexec"), oexec_p);
 382                 owrite_p.remove(OWNER_READ);
 383                 Files.setPosixFilePermissions(zipOut.getPath("owrite"), owrite_p);
 384                 uexec_p.remove(OWNER_READ);
 385                 Files.setPosixFilePermissions(zipOut.getPath("uexec"), uexec_p);
 386                 uwrite_p.remove(OWNER_READ);
 387                 Files.setPosixFilePermissions(zipOut.getPath("uwrite"), uwrite_p);
 388                 // Fix "noperms" which has the default permissions after unzipping in 'testPosixPermsAfterUnzip()'
 389                 Files.setPosixFilePermissions(zipOut.getPath("noperms"), null);
 390             }
 391 
 392             System.out.println("Test reading " + out + "...");
 393             if (posixFS) {
 394                 checkPosixPerms(out);
 395             }
 396         }
 397     }
 398 }