|
21 | 21 | import java.util.Objects; |
22 | 22 | import java.util.stream.Stream; |
23 | 23 |
|
| 24 | +import static java.lang.Character.isLetter; |
| 25 | + |
24 | 26 | /** |
25 | 27 | * Describes a file entitlement with a path and mode. |
26 | 28 | */ |
@@ -60,6 +62,51 @@ static FileData ofPathSetting(String setting, Mode mode) { |
60 | 62 | static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode) { |
61 | 63 | return new RelativePathSettingFileData(setting, baseDir, mode); |
62 | 64 | } |
| 65 | + |
| 66 | + /** |
| 67 | + * Tests if a path is absolute or relative, taking into consideration both Unix and Windows conventions. |
| 68 | + * Note that this leads to a conflict, resolved in favor of Unix rules: `/foo` can be either a Unix absolute path, or a Windows |
| 69 | + * relative path with "wrong" directory separator (using non-canonical slash in Windows). |
| 70 | + */ |
| 71 | + static boolean isAbsolutePath(String path) { |
| 72 | + if (path.isEmpty()) { |
| 73 | + return false; |
| 74 | + } |
| 75 | + if (path.charAt(0) == '/') { |
| 76 | + // Unix/BSD absolute |
| 77 | + return true; |
| 78 | + } |
| 79 | + |
| 80 | + return isWindowsAbsolutePath(path); |
| 81 | + } |
| 82 | + |
| 83 | + private static boolean isSlash(char c) { |
| 84 | + return (c == '\\') || (c == '/'); |
| 85 | + } |
| 86 | + |
| 87 | + private static boolean isWindowsAbsolutePath(String input) { |
| 88 | + // if a prefix is present, we expected (long) UNC or (long) absolute |
| 89 | + if (input.startsWith("\\\\?\\")) { |
| 90 | + return true; |
| 91 | + } |
| 92 | + |
| 93 | + if (input.length() > 1) { |
| 94 | + char c0 = input.charAt(0); |
| 95 | + char c1 = input.charAt(1); |
| 96 | + char c = 0; |
| 97 | + int next = 2; |
| 98 | + if (isSlash(c0) && isSlash(c1)) { |
| 99 | + // Two slashes or more: UNC |
| 100 | + return true; |
| 101 | + } |
| 102 | + if (isLetter(c0) && c1 == ':') { |
| 103 | + // A drive: absolute |
| 104 | + return true; |
| 105 | + } |
| 106 | + } |
| 107 | + // Otherwise relative |
| 108 | + return false; |
| 109 | + } |
63 | 110 | } |
64 | 111 |
|
65 | 112 | private sealed interface RelativeFileData extends FileData { |
@@ -190,17 +237,15 @@ public static FilesEntitlement build(List<Object> paths) { |
190 | 237 | throw new PolicyValidationException("files entitlement with a 'relative_path' must specify 'relative_to'"); |
191 | 238 | } |
192 | 239 |
|
193 | | - Path relativePath = Path.of(relativePathAsString); |
194 | | - if (relativePath.isAbsolute()) { |
| 240 | + if (FileData.isAbsolutePath(relativePathAsString)) { |
195 | 241 | throw new PolicyValidationException("'relative_path' [" + relativePathAsString + "] must be relative"); |
196 | 242 | } |
197 | | - filesData.add(FileData.ofRelativePath(relativePath, baseDir, mode)); |
| 243 | + filesData.add(FileData.ofRelativePath(Path.of(relativePathAsString), baseDir, mode)); |
198 | 244 | } else if (pathAsString != null) { |
199 | | - Path path = Path.of(pathAsString); |
200 | | - if (path.isAbsolute() == false) { |
| 245 | + if (FileData.isAbsolutePath(pathAsString) == false) { |
201 | 246 | throw new PolicyValidationException("'path' [" + pathAsString + "] must be absolute"); |
202 | 247 | } |
203 | | - filesData.add(FileData.ofPath(path, mode)); |
| 248 | + filesData.add(FileData.ofPath(Path.of(pathAsString), mode)); |
204 | 249 | } else if (pathSetting != null) { |
205 | 250 | filesData.add(FileData.ofPathSetting(pathSetting, mode)); |
206 | 251 | } else if (relativePathSetting != null) { |
|
0 commit comments