diff --git a/src/main/org/apache/tools/ant/util/PermissionUtils.java b/src/main/org/apache/tools/ant/util/PermissionUtils.java new file mode 100644 index 000000000..6fbf163c7 --- /dev/null +++ b/src/main/org/apache/tools/ant/util/PermissionUtils.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Set; + +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.resources.ArchiveResource; +import org.apache.tools.ant.types.resources.FileProvider; + +/** + * Contains helper methods for dealing with {@link + * PosixFilePermission} or the traditional Unix mode representation of + * permissions. + * + * @since Ant 1.10.0 + */ +public class PermissionUtils { + + private PermissionUtils() { } + + /** + * Translates a set of permissons into a Unix stat(2) {@code + * st_mode} result. + * @param permissions the permissions + * @param type the file type + * @return the "mode" + */ + public static int modeFromPermissions(Set permissions, + FileType type) { + int mode; + switch (type) { + case SYMLINK: + mode = 012; + break; + case REGULAR_FILE: + mode = 010; + break; + case DIR: + mode = 004; + break; + default: + // OTHER could be a character or block device, a socket or a FIFO - so don't set anything + mode = 0; + break; + } + mode <<= 3; + mode <<= 3; // we don't support sticky, setuid, setgid + mode |= modeFromPermissions(permissions, "OWNER"); + mode <<= 3; + mode |= modeFromPermissions(permissions, "GROUP"); + mode <<= 3; + mode |= modeFromPermissions(permissions, "OTHERS"); + return mode; + } + + /** + * Translates a Unix stat(2) {@code st_mode} compatible value into + * a set of permissions. + * @param mode the "mode" + * @return set of permissions + */ + public static Set permissionsFromMode(int mode) { + Set permissions = EnumSet.noneOf(PosixFilePermission.class); + addPermissions(permissions, "OTHERS", mode); + addPermissions(permissions, "GROUP", mode >> 3); + addPermissions(permissions, "OWNER", mode >> 6); + return permissions; + } + + /** + * Sets permissions on a {@link Resource} - doesn't do anything + * for unsupported resource types. + * + *

Supported types are:

+ *
    + *
  • any {@link FileProvider}
  • + *
  • {@link ArchiveResource}
  • + *
+ * + * @param resource the resource to set permissions for + * @param permissions the permissions + */ + public static void setPermissions(Resource r, Set permissions) + throws IOException { + FileProvider f = r.as(FileProvider.class); + if (f != null) { + Files.setPosixFilePermissions(f.getFile().toPath(), permissions); + } else if (r instanceof ArchiveResource) { + ((ArchiveResource) r).setMode(modeFromPermissions(permissions, + FileType.of(r))); + } + } + + /** + * Sets permissions of a {@link Resource} - doesn't returns an + * empty set for unsupported resource types. + * + *

Supported types are:

+ *
    + *
  • any {@link FileProvider}
  • + *
  • {@link ArchiveResource}
  • + *
+ * + * @param resource the resource to read permissions from + * @return the permissions + */ + public static Set getPermissions(Resource r) throws IOException { + FileProvider f = r.as(FileProvider.class); + if (f != null) { + return Files.getPosixFilePermissions(f.getFile().toPath()); + } else if (r instanceof ArchiveResource) { + return permissionsFromMode(((ArchiveResource) r).getMode()); + } + return EnumSet.noneOf(PosixFilePermission.class); + } + + private static long modeFromPermissions(Set permissions, + String prefix) { + long mode = 0; + if (permissions.contains(PosixFilePermission.valueOf(prefix + "_READ"))) { + mode |= 4; + } + if (permissions.contains(PosixFilePermission.valueOf(prefix + "_WRITE"))) { + mode |= 2; + } + if (permissions.contains(PosixFilePermission.valueOf(prefix + "_EXECUTE"))) { + mode |= 1; + } + return mode; + } + + private static void addPermissions(Set permissions, + String prefix, long mode) { + if ((mode & 1) == 1) { + permissions.add(PosixFilePermission.valueOf(prefix + "_EXECUTE")); + } + if ((mode & 2) == 2) { + permissions.add(PosixFilePermission.valueOf(prefix + "_WRITE")); + } + if ((mode & 4) == 4) { + permissions.add(PosixFilePermission.valueOf(prefix + "_READ")); + } + } + + /** + * The supported types of files, maps to the {@code isFoo} methods + * in {@link java.nio.file.attribute.BasicFileAttributes}. + */ + public enum FileType { + /** A regular file. */ + REGULAR_FILE, + /** A directory. */ + DIR, + /** A symbolic link. */ + SYMLINK, + /** Something that is neither a regular file nor a directory nor a symbolic link. */ + OTHER; + + /** + * Determines the file type of a {@link Path}. + */ + public static FileType of(Path p) throws IOException { + BasicFileAttributes attrs = + Files.readAttributes(p, BasicFileAttributes.class); + if (attrs.isRegularFile()) { + return FileType.REGULAR_FILE; + } else if (attrs.isDirectory()) { + return FileType.DIR; + } else if (attrs.isSymbolicLink()) { + return FileType.SYMLINK; + } + return FileType.OTHER; + } + + /** + * Determines the file type of a {@link Resource}. + */ + public static FileType of(Resource r) { + if (r.isDirectory()) { + return FileType.DIR; + } + return FileType.REGULAR_FILE; + } + } +} diff --git a/src/tests/junit/org/apache/tools/ant/util/PermissionUtilsTest.java b/src/tests/junit/org/apache/tools/ant/util/PermissionUtilsTest.java new file mode 100644 index 000000000..eed265de3 --- /dev/null +++ b/src/tests/junit/org/apache/tools/ant/util/PermissionUtilsTest.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.util; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Set; + +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.TarResource; +import org.apache.tools.ant.types.resources.ZipResource; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarOutputStream; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipOutputStream; +import org.junit.Test; + +public class PermissionUtilsTest { + + @Test + public void modeFromPermissionsReturnsExpectedResult() { + int mode = PermissionUtils.modeFromPermissions(EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE), + PermissionUtils.FileType.REGULAR_FILE); + assertEquals("100700", Integer.toString(mode, 8)); + } + + @Test + public void permissionsFromModeReturnsExpectedResult() { + Set s = PermissionUtils.permissionsFromMode(0100753); + assertEquals(EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_READ, + PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_WRITE, + PosixFilePermission.OTHERS_EXECUTE), + s); + } + + @Test + public void detectsFileTypeOfRegularFileFromPath() throws IOException { + File f = File.createTempFile("ant", ".tst"); + f.deleteOnExit(); + assertEquals(PermissionUtils.FileType.REGULAR_FILE, + PermissionUtils.FileType.of(f.toPath())); + } + + @Test + public void detectsFileTypeOfRegularFileFromResource() throws IOException { + File f = File.createTempFile("ant", ".tst"); + f.deleteOnExit(); + assertEquals(PermissionUtils.FileType.REGULAR_FILE, + PermissionUtils.FileType.of(new FileResource(f))); + } + + @Test + public void detectsFileTypeOfDirectoryFromPath() throws IOException { + File f = File.createTempFile("ant", ".dir"); + f.delete(); + f.mkdirs(); + f.deleteOnExit(); + assertEquals(PermissionUtils.FileType.DIR, + PermissionUtils.FileType.of(f.toPath())); + } + + @Test + public void detectsFileTypeOfDirectoryFromResource() throws IOException { + File f = File.createTempFile("ant", ".tst"); + f.delete(); + f.mkdirs(); + f.deleteOnExit(); + assertEquals(PermissionUtils.FileType.DIR, + PermissionUtils.FileType.of(new FileResource(f))); + } + + @Test + public void getSetPermissionsWorksForFiles() throws IOException { + File f = File.createTempFile("ant", ".tst"); + f.deleteOnExit(); + Set s = + EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_READ); + PermissionUtils.setPermissions(new FileResource(f), s); + assertEquals(s, PermissionUtils.getPermissions(new FileResource(f))); + } + + @Test + public void getSetPermissionsWorksForZipResources() throws IOException { + File f = File.createTempFile("ant", ".zip"); + f.deleteOnExit(); + try (ZipOutputStream os = new ZipOutputStream(f)) { + ZipEntry e = new ZipEntry("foo"); + os.putNextEntry(e); + os.closeEntry(); + } + + ZipResource r = new ZipResource(); + r.setName("foo"); + r.setArchive(f); + Set s = + EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_READ); + PermissionUtils.setPermissions(r, s); + assertEquals(s, PermissionUtils.getPermissions(r)); + } + + @Test + public void getSetPermissionsWorksForTarResources() throws IOException { + File f = File.createTempFile("ant", ".zip"); + f.deleteOnExit(); + try (TarOutputStream os = new TarOutputStream(new FileOutputStream(f))) { + TarEntry e = new TarEntry("foo"); + os.putNextEntry(e); + os.closeEntry(); + } + + TarResource r = new TarResource(); + r.setName("foo"); + r.setArchive(f); + Set s = + EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_READ); + PermissionUtils.setPermissions(r, s); + assertEquals(s, PermissionUtils.getPermissions(r)); + } +}