From f2db40caa8aaaa7e29e2482d052bfdd5ed8f39b2 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Sun, 6 Oct 2024 21:23:13 -0600 Subject: [PATCH] (137068266) URL.fileSystemPath should strip leading slash for Windows drive letters --- Sources/FoundationEssentials/URL/URL.swift | 25 ++++++++++++++++++- .../FoundationEssentialsTests/URLTests.swift | 12 +++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/URL/URL.swift b/Sources/FoundationEssentials/URL/URL.swift index 54c78f94a..7be594410 100644 --- a/Sources/FoundationEssentials/URL/URL.swift +++ b/Sources/FoundationEssentials/URL/URL.swift @@ -1146,9 +1146,32 @@ public struct URL: Equatable, Sendable, Hashable { } } + private static func windowsPath(for posixPath: String) -> String { + let utf8 = posixPath.utf8 + guard utf8.count >= 4 else { + return posixPath + } + // "C:\" is standardized to "/C:/" on initialization + let array = Array(utf8) + if array[0] == ._slash, + array[1].isAlpha, + array[2] == ._colon, + array[3] == ._slash { + return String(Substring(utf8.dropFirst())) + } + return posixPath + } + private static func fileSystemPath(for urlPath: String) -> String { let charsToLeaveEncoded: Set = [._slash, 0] - return Parser.percentDecode(urlPath._droppingTrailingSlashes, excluding: charsToLeaveEncoded) ?? "" + guard let posixPath = Parser.percentDecode(urlPath._droppingTrailingSlashes, excluding: charsToLeaveEncoded) else { + return "" + } + #if os(Windows) + return windowsPath(for: posixPath) + #else + return posixPath + #endif } var fileSystemPath: String { diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index 9e4c388f6..3703eaea6 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -330,6 +330,18 @@ final class URLTests : XCTestCase { try FileManager.default.removeItem(at: URL(filePath: "\(tempDirectory.path)/tmp-dir")) } + #if os(Windows) + func testURLWindowsDriveLetterPath() throws { + let url = URL(filePath: "C:\\test\\path", directoryHint: .notDirectory) + // .absoluteString and .path() use the RFC 8089 URL path + XCTAssertEqual(url.absoluteString, "file:///C:/test/path") + XCTAssertEqual(url.path(), "/C:/test/path") + // .path and .fileSystemPath strip the leading slash + XCTAssertEqual(url.path, "C:/test/path") + XCTAssertEqual(url.fileSystemPath, "C:/test/path") + } + #endif + func testURLFilePathRelativeToBase() throws { try FileManagerPlayground { Directory("dir") {