diff --git a/src/main/java/org/kamranzafar/jtar/PermissionUtils.java b/src/main/java/org/kamranzafar/jtar/PermissionUtils.java new file mode 100644 index 0000000..2e918a6 --- /dev/null +++ b/src/main/java/org/kamranzafar/jtar/PermissionUtils.java @@ -0,0 +1,109 @@ +package org.kamranzafar.jtar; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermission; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Helps dealing with file permissions. + */ +public class PermissionUtils { + + /** + * XXX: When using standard Java permissions, we treat 'owner' and 'group' equally and give no + * permissions for 'others'. + */ + private static enum StandardFilePermission { + EXECUTE(0110), WRITE(0220), READ(0440); + + private int mode; + + private StandardFilePermission(int mode) { + this.mode = mode; + } + } + + private static Map posixPermissionToInteger = new HashMap<>(); + + static { + posixPermissionToInteger.put(PosixFilePermission.OWNER_EXECUTE, 0100); + posixPermissionToInteger.put(PosixFilePermission.OWNER_WRITE, 0200); + posixPermissionToInteger.put(PosixFilePermission.OWNER_READ, 0400); + + posixPermissionToInteger.put(PosixFilePermission.GROUP_EXECUTE, 0010); + posixPermissionToInteger.put(PosixFilePermission.GROUP_WRITE, 0020); + posixPermissionToInteger.put(PosixFilePermission.GROUP_READ, 0040); + + posixPermissionToInteger.put(PosixFilePermission.OTHERS_EXECUTE, 0001); + posixPermissionToInteger.put(PosixFilePermission.OTHERS_WRITE, 0002); + posixPermissionToInteger.put(PosixFilePermission.OTHERS_READ, 0004); + } + + /** + * Get file permissions in octal mode, e.g. 0755. + * + * Note: it uses `java.nio.file.attribute.PosixFilePermission` if OS supports this, otherwise reverts to + * using standard Java file operations, e.g. `java.io.File#canExecute()`. In the first case permissions will + * be precisely as reported by the OS, in the second case 'owner' and 'group' will have equal permissions and + * 'others' will have no permissions, e.g. if file on Windows OS is `read-only` permissions will be `0550`. + * + * @throws NullPointerException if file is null. + * @throws IllegalArgumentException if file does not exist. + */ + public static int permissions(File f) { + if(f == null) { + throw new NullPointerException("File is null."); + } + if(!f.exists()) { + throw new IllegalArgumentException("File " + f + " does not exist."); + } + + return isPosix ? posixPermissions(f) : standardPermissions(f); + } + + private static final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); + + private static int posixPermissions(File f) { + int number = 0; + try { + Set permissions = Files.getPosixFilePermissions(f.toPath()); + for (Map.Entry entry : posixPermissionToInteger.entrySet()) { + if (permissions.contains(entry.getKey())) { + number += entry.getValue(); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return number; + } + + private static Set readStandardPermissions(File f) { + Set permissions = new HashSet<>(); + if(f.canExecute()) { + permissions.add(StandardFilePermission.EXECUTE); + } + if(f.canWrite()) { + permissions.add(StandardFilePermission.WRITE); + } + if(f.canRead()) { + permissions.add(StandardFilePermission.READ); + } + return permissions; + } + + private static Integer standardPermissions(File f) { + int number = 0; + Set permissions = readStandardPermissions(f); + for (StandardFilePermission permission : permissions) { + number += permission.mode; + } + return number; + } +} diff --git a/src/main/java/org/kamranzafar/jtar/TarEntry.java b/src/main/java/org/kamranzafar/jtar/TarEntry.java index fe01db4..9794f8b 100755 --- a/src/main/java/org/kamranzafar/jtar/TarEntry.java +++ b/src/main/java/org/kamranzafar/jtar/TarEntry.java @@ -170,7 +170,8 @@ public class TarEntry { * @param entryName */ public void extractTarHeader(String entryName) { - header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory()); + int permissions = PermissionUtils.permissions(file); + header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory(), permissions); } /** diff --git a/src/main/java/org/kamranzafar/jtar/TarHeader.java b/src/main/java/org/kamranzafar/jtar/TarHeader.java index b9d3a86..e63271a 100755 --- a/src/main/java/org/kamranzafar/jtar/TarHeader.java +++ b/src/main/java/org/kamranzafar/jtar/TarHeader.java @@ -206,12 +206,13 @@ public class TarHeader { * * @return */ - public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) { + public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir, int permissions) { String name = entryName; name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/'); TarHeader header = new TarHeader(); header.linkName = new StringBuffer(""); + header.mode = permissions; if (name.length() > 100) { header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/'))); @@ -219,16 +220,13 @@ public class TarHeader { } else { header.name = new StringBuffer(name); } - if (dir) { - header.mode = 040755; header.linkFlag = TarHeader.LF_DIR; if (header.name.charAt(header.name.length() - 1) != '/') { header.name.append("/"); } header.size = 0; } else { - header.mode = 0100644; header.linkFlag = TarHeader.LF_NORMAL; header.size = size; } diff --git a/src/test/java/org/kamranzafar/jtar/JTarTest.java b/src/test/java/org/kamranzafar/jtar/JTarTest.java index 235beb8..042550c 100755 --- a/src/test/java/org/kamranzafar/jtar/JTarTest.java +++ b/src/test/java/org/kamranzafar/jtar/JTarTest.java @@ -245,13 +245,15 @@ public class JTarTest { String fileName = "file.txt"; long fileSize = 14523; long modTime = System.currentTimeMillis() / 1000; + int permissions = 0755; // Create a header object and check the fields - TarHeader fileHeader = TarHeader.createHeader(fileName, fileSize, modTime, false); + TarHeader fileHeader = TarHeader.createHeader(fileName, fileSize, modTime, false, permissions); assertEquals(fileName, fileHeader.name.toString()); assertEquals(TarHeader.LF_NORMAL, fileHeader.linkFlag); assertEquals(fileSize, fileHeader.size); assertEquals(modTime, fileHeader.modTime); + assertEquals(permissions, fileHeader.mode); // Create an entry from the header TarEntry fileEntry = new TarEntry(fileHeader);