Skip to content

Commit 5141cf6

Browse files
authored
(137068266) URL.fileSystemPath should strip leading slash for Windows drive letters (#964)
1 parent 09bdd0d commit 5141cf6

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

Sources/FoundationEssentials/URL/URL.swift

+24-1
Original file line numberDiff line numberDiff line change
@@ -1146,9 +1146,32 @@ public struct URL: Equatable, Sendable, Hashable {
11461146
}
11471147
}
11481148

1149+
private static func windowsPath(for posixPath: String) -> String {
1150+
let utf8 = posixPath.utf8
1151+
guard utf8.count >= 4 else {
1152+
return posixPath
1153+
}
1154+
// "C:\" is standardized to "/C:/" on initialization
1155+
let array = Array(utf8)
1156+
if array[0] == ._slash,
1157+
array[1].isAlpha,
1158+
array[2] == ._colon,
1159+
array[3] == ._slash {
1160+
return String(Substring(utf8.dropFirst()))
1161+
}
1162+
return posixPath
1163+
}
1164+
11491165
private static func fileSystemPath(for urlPath: String) -> String {
11501166
let charsToLeaveEncoded: Set<UInt8> = [._slash, 0]
1151-
return Parser.percentDecode(urlPath._droppingTrailingSlashes, excluding: charsToLeaveEncoded) ?? ""
1167+
guard let posixPath = Parser.percentDecode(urlPath._droppingTrailingSlashes, excluding: charsToLeaveEncoded) else {
1168+
return ""
1169+
}
1170+
#if os(Windows)
1171+
return windowsPath(for: posixPath)
1172+
#else
1173+
return posixPath
1174+
#endif
11521175
}
11531176

11541177
var fileSystemPath: String {

Tests/FoundationEssentialsTests/URLTests.swift

+12
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,18 @@ final class URLTests : XCTestCase {
330330
try FileManager.default.removeItem(at: URL(filePath: "\(tempDirectory.path)/tmp-dir"))
331331
}
332332

333+
#if os(Windows)
334+
func testURLWindowsDriveLetterPath() throws {
335+
let url = URL(filePath: "C:\\test\\path", directoryHint: .notDirectory)
336+
// .absoluteString and .path() use the RFC 8089 URL path
337+
XCTAssertEqual(url.absoluteString, "file:///C:/test/path")
338+
XCTAssertEqual(url.path(), "/C:/test/path")
339+
// .path and .fileSystemPath strip the leading slash
340+
XCTAssertEqual(url.path, "C:/test/path")
341+
XCTAssertEqual(url.fileSystemPath, "C:/test/path")
342+
}
343+
#endif
344+
333345
func testURLFilePathRelativeToBase() throws {
334346
try FileManagerPlayground {
335347
Directory("dir") {

0 commit comments

Comments
 (0)