Skip to content

Commit 6249fc1

Browse files
committed
(139676985) URL(filePath:) should resolve Windows drive-relative paths
1 parent 221a0dd commit 6249fc1

File tree

2 files changed

+41
-3
lines changed

2 files changed

+41
-3
lines changed

Sources/FoundationEssentials/URL/URL.swift

+31-2
Original file line numberDiff line numberDiff line change
@@ -2225,11 +2225,40 @@ extension URL {
22252225
#if os(Windows)
22262226
// Convert any "\" to "/" before storing the URL parse info
22272227
var filePath = path.replacing(._backslash, with: ._slash)
2228+
let isAbsolute: Bool
2229+
var iter = filePath.utf8.makeIterator()
2230+
if let driveLetter = iter.next(), driveLetter.isAlpha,
2231+
iter.next() == ._colon,
2232+
iter.next() != ._slash {
2233+
// Drive-relative path: use the current directory for the given drive letter
2234+
// as the base URL, and remove the drive letter from the relative path.
2235+
let relativePath = String(Substring(filePath.utf8.dropFirst(2)))
2236+
let basePath: String? = "\(Unicode.Scalar(driveLetter)):".withCString(encodedAs: UTF16.self) { pwszDriveLetter in
2237+
let dwLength: DWORD = GetFullPathNameW(pwszDriveLetter, 0, nil, nil)
2238+
guard dwLength > 0 else {
2239+
return nil
2240+
}
2241+
return try? withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) {
2242+
guard GetFullPathNameW(pwszDriveLetter, DWORD($0.count), $0.baseAddress, nil) > 0 else {
2243+
return nil
2244+
}
2245+
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
2246+
}
2247+
}
2248+
guard let basePath else {
2249+
self.init(filePath: relativePath, directoryHint: directoryHint, relativeTo: base)
2250+
return
2251+
}
2252+
baseURL = URL(filePath: basePath, directoryHint: .isDirectory)
2253+
filePath = relativePath
2254+
isAbsolute = false
2255+
} else {
2256+
isAbsolute = URL.isAbsolute(standardizing: &filePath)
2257+
}
22282258
#else
22292259
var filePath = path
2230-
#endif
2231-
22322260
let isAbsolute = URL.isAbsolute(standardizing: &filePath)
2261+
#endif
22332262

22342263
#if !NO_FILESYSTEM
22352264
if !isAbsolute {

Tests/FoundationEssentialsTests/URLTests.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,19 @@ final class URLTests : XCTestCase {
379379
if iter.next() == ._slash,
380380
let driveLetter = iter.next(), driveLetter.isLetter!,
381381
iter.next() == ._colon {
382-
let path = #"\\?\"# + "\(Unicode.Scalar(driveLetter))" + #":\"#
382+
let drive = "\(Unicode.Scalar(driveLetter))"
383+
let path = #"\\?\"# + drive + #":\"#
383384
url = URL(filePath: path, directoryHint: .isDirectory)
384385
XCTAssertEqual(url.path.last, "/")
385386
XCTAssertEqual(url.fileSystemPath.last, "/")
387+
388+
// Test drive-relative path
389+
let driveRelativePath = "\(drive):hello"
390+
url = URL(filePath: driveRelativePath)
391+
XCTAssertEqual(url.relativePath, "hello")
392+
XCTAssertEqual(url.relativeString, "hello")
393+
XCTAssertTrue(url.baseURL?.path.starts(with: "\(drive):/") ?? false)
394+
XCTAssertTrue(url.path.starts(with: "\(drive):/"))
386395
}
387396
}
388397
#endif

0 commit comments

Comments
 (0)