|
13 | 13 | public struct URLResourceKey {}
|
14 | 14 | #endif
|
15 | 15 |
|
16 |
| -#if os(Windows) |
| 16 | +#if canImport(Darwin) |
| 17 | +import Darwin |
| 18 | +#elseif canImport(Android) |
| 19 | +@preconcurrency import Android |
| 20 | +#elseif canImport(Glibc) |
| 21 | +@preconcurrency import Glibc |
| 22 | +#elseif canImport(Musl) |
| 23 | +@preconcurrency import Musl |
| 24 | +#elseif os(Windows) |
17 | 25 | import WinSDK
|
| 26 | +#elseif os(WASI) |
| 27 | +@preconcurrency import WASILibc |
18 | 28 | #endif
|
19 | 29 |
|
20 | 30 | #if FOUNDATION_FRAMEWORK
|
@@ -2199,17 +2209,30 @@ extension URL {
|
2199 | 2209 |
|
2200 | 2210 | #if !NO_FILESYSTEM
|
2201 | 2211 | private static func isDirectory(_ path: String) -> Bool {
|
| 2212 | + guard !path.isEmpty else { return false } |
2202 | 2213 | #if os(Windows)
|
2203 | 2214 | let path = path.replacing(._slash, with: ._backslash)
|
2204 |
| - #endif |
2205 |
| - #if !FOUNDATION_FRAMEWORK |
2206 |
| - var isDirectory: Bool = false |
2207 |
| - _ = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) |
2208 |
| - return isDirectory |
| 2215 | + return (try? path.withNTPathRepresentation { pwszPath in |
| 2216 | + // If path points to a symlink (reparse point), get a handle to |
| 2217 | + // the symlink itself using FILE_FLAG_OPEN_REPARSE_POINT. |
| 2218 | + let handle = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nil) |
| 2219 | + guard handle != INVALID_HANDLE_VALUE else { return false } |
| 2220 | + defer { CloseHandle(handle) } |
| 2221 | + var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION() |
| 2222 | + guard GetFileInformationByHandle(handle, &info) else { return false } |
| 2223 | + if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT { return false } |
| 2224 | + return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY |
| 2225 | + }) ?? false |
2209 | 2226 | #else
|
2210 |
| - var isDirectory: ObjCBool = false |
2211 |
| - _ = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) |
2212 |
| - return isDirectory.boolValue |
| 2227 | + // FileManager uses stat() to check if the file exists. |
| 2228 | + // URL historically won't follow a symlink at the end |
| 2229 | + // of the path, so use lstat() here instead. |
| 2230 | + return path.withFileSystemRepresentation { fsRep in |
| 2231 | + guard let fsRep else { return false } |
| 2232 | + var fileInfo = stat() |
| 2233 | + guard lstat(fsRep, &fileInfo) == 0 else { return false } |
| 2234 | + return (mode_t(fileInfo.st_mode) & S_IFMT) == S_IFDIR |
| 2235 | + } |
2213 | 2236 | #endif
|
2214 | 2237 | }
|
2215 | 2238 | #endif // !NO_FILESYSTEM
|
|
0 commit comments