|
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 | */ |
@@ -61,6 +63,51 @@ static FileData ofPathSetting(String setting, Mode mode) { |
61 | 63 | static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode) { |
62 | 64 | return new RelativePathSettingFileData(setting, baseDir, mode); |
63 | 65 | } |
| 66 | + |
| 67 | + /** |
| 68 | + * Tests if a path is absolute or relative, taking into consideration both Unix and Windows conventions. |
| 69 | + * Note that this leads to a conflict, resolved in favor of Unix rules: `/foo` can be either a Unix absolute path, or a Windows |
| 70 | + * relative path with "wrong" directory separator (using non-canonical slash in Windows). |
| 71 | + */ |
| 72 | + static boolean isAbsolutePath(String path) { |
| 73 | + if (path.isEmpty()) { |
| 74 | + return false; |
| 75 | + } |
| 76 | + if (path.charAt(0) == '/') { |
| 77 | + // Unix/BSD absolute |
| 78 | + return true; |
| 79 | + } |
| 80 | + |
| 81 | + return isWindowsAbsolutePath(path); |
| 82 | + } |
| 83 | + |
| 84 | + private static boolean isSlash(char c) { |
| 85 | + return (c == '\\') || (c == '/'); |
| 86 | + } |
| 87 | + |
| 88 | + private static boolean isWindowsAbsolutePath(String input) { |
| 89 | + // if a prefix is present, we expected (long) UNC or (long) absolute |
| 90 | + if (input.startsWith("\\\\?\\")) { |
| 91 | + return true; |
| 92 | + } |
| 93 | + |
| 94 | + if (input.length() > 1) { |
| 95 | + char c0 = input.charAt(0); |
| 96 | + char c1 = input.charAt(1); |
| 97 | + char c = 0; |
| 98 | + int next = 2; |
| 99 | + if (isSlash(c0) && isSlash(c1)) { |
| 100 | + // Two slashes or more: UNC |
| 101 | + return true; |
| 102 | + } |
| 103 | + if (isLetter(c0) && c1 == ':') { |
| 104 | + // A drive: absolute |
| 105 | + return true; |
| 106 | + } |
| 107 | + } |
| 108 | + // Otherwise relative |
| 109 | + return false; |
| 110 | + } |
64 | 111 | } |
65 | 112 |
|
66 | 113 | private sealed interface RelativeFileData extends FileData { |
@@ -198,17 +245,15 @@ public static FilesEntitlement build(List<Object> paths) { |
198 | 245 | throw new PolicyValidationException("files entitlement with a 'relative_path' must specify 'relative_to'"); |
199 | 246 | } |
200 | 247 |
|
201 | | - Path relativePath = Path.of(relativePathAsString); |
202 | | - if (relativePath.isAbsolute()) { |
| 248 | + if (FileData.isAbsolutePath(relativePathAsString)) { |
203 | 249 | throw new PolicyValidationException("'relative_path' [" + relativePathAsString + "] must be relative"); |
204 | 250 | } |
205 | | - filesData.add(FileData.ofRelativePath(relativePath, baseDir, mode)); |
| 251 | + filesData.add(FileData.ofRelativePath(Path.of(relativePathAsString), baseDir, mode)); |
206 | 252 | } else if (pathAsString != null) { |
207 | | - Path path = Path.of(pathAsString); |
208 | | - if (path.isAbsolute() == false) { |
| 253 | + if (FileData.isAbsolutePath(pathAsString) == false) { |
209 | 254 | throw new PolicyValidationException("'path' [" + pathAsString + "] must be absolute"); |
210 | 255 | } |
211 | | - filesData.add(FileData.ofPath(path, mode)); |
| 256 | + filesData.add(FileData.ofPath(Path.of(pathAsString), mode)); |
212 | 257 | } else if (pathSetting != null) { |
213 | 258 | filesData.add(FileData.ofPathSetting(pathSetting, mode)); |
214 | 259 | } else if (relativePathSetting != null) { |
|
0 commit comments