Skip to content

Commit a02bd72

Browse files
committed
(137068266) URL.fileSystemPath should strip leading slash for Windows drive letters (swiftlang#964)
1 parent 0889797 commit a02bd72

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

Sources/FoundationEssentials/URL/URL.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1319,9 +1319,32 @@ public struct URL: Equatable, Sendable, Hashable {
13191319
}
13201320
}
13211321

1322+
private static func windowsPath(for posixPath: String) -> String {
1323+
let utf8 = posixPath.utf8
1324+
guard utf8.count >= 4 else {
1325+
return posixPath
1326+
}
1327+
// "C:\" is standardized to "/C:/" on initialization
1328+
let array = Array(utf8)
1329+
if array[0] == ._slash,
1330+
array[1].isAlpha,
1331+
array[2] == ._colon,
1332+
array[3] == ._slash {
1333+
return String(Substring(utf8.dropFirst()))
1334+
}
1335+
return posixPath
1336+
}
1337+
13221338
private static func fileSystemPath(for urlPath: String) -> String {
13231339
let charsToLeaveEncoded: Set<UInt8> = [._slash, 0]
1324-
return Parser.percentDecode(urlPath._droppingTrailingSlashes, excluding: charsToLeaveEncoded) ?? ""
1340+
guard let posixPath = Parser.percentDecode(urlPath._droppingTrailingSlashes, excluding: charsToLeaveEncoded) else {
1341+
return ""
1342+
}
1343+
#if os(Windows)
1344+
return windowsPath(for: posixPath)
1345+
#else
1346+
return posixPath
1347+
#endif
13251348
}
13261349

13271350
var fileSystemPath: String {

Tests/FoundationEssentialsTests/URLTests.swift

Lines changed: 12 additions & 0 deletions
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)