Skip to content

Commit 37fd6b8

Browse files
authored
(133882014) URL(filePath: path, directoryHint: .notDirectory) should strip trailing slashes (#867)
1 parent e905df1 commit 37fd6b8

File tree

2 files changed

+64
-32
lines changed

2 files changed

+64
-32
lines changed

Sources/FoundationEssentials/URL/URL.swift

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,24 +2022,28 @@ extension URL {
20222022

20232023
#if !NO_FILESYSTEM
20242024
private static func isDirectory(_ path: String) -> Bool {
2025-
#if !FOUNDATION_FRAMEWORK
2025+
#if os(Windows)
2026+
let path = path.replacing(._slash, with: ._backslash)
2027+
#endif
2028+
#if !FOUNDATION_FRAMEWORK
20262029
var isDirectory: Bool = false
20272030
_ = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
20282031
return isDirectory
2029-
#else
2032+
#else
20302033
var isDirectory: ObjCBool = false
20312034
_ = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
20322035
return isDirectory.boolValue
2033-
#endif
2036+
#endif
20342037
}
20352038
#endif // !NO_FILESYSTEM
20362039

20372040
/// Checks if a file path is absolute and standardizes the inputted file path on Windows
2041+
/// Assumes the path only contains `/` as the path separator
20382042
internal static func isAbsolute(standardizing filePath: inout String) -> Bool {
20392043
#if os(Windows)
20402044
var isAbsolute = false
20412045
let utf8 = filePath.utf8
2042-
if utf8.first == ._backslash {
2046+
if utf8.first == ._slash {
20432047
// Either an absolute path or a UNC path
20442048
isAbsolute = true
20452049
} else if utf8.count >= 3 {
@@ -2052,18 +2056,18 @@ extension URL {
20522056
isAbsolute = (
20532057
first.isAlpha
20542058
&& (second == ._colon || second == ._pipe)
2055-
&& third == ._backslash
2059+
&& third == ._slash
20562060
)
20572061

20582062
if isAbsolute {
2059-
// Standardize to "\[drive-letter]:\..."
2063+
// Standardize to "/[drive-letter]:/..."
20602064
if second == ._pipe {
20612065
var filePathArray = Array(utf8)
20622066
filePathArray[1] = ._colon
2063-
filePathArray.insert(._backslash, at: 0)
2067+
filePathArray.insert(._slash, at: 0)
20642068
filePath = String(decoding: filePathArray, as: UTF8.self)
20652069
} else {
2066-
filePath = "\\" + filePath
2070+
filePath = "/" + filePath
20672071
}
20682072
}
20692073
}
@@ -2107,10 +2111,9 @@ extension URL {
21072111
}
21082112

21092113
#if os(Windows)
2110-
let slash = UInt8(ascii: "\\")
2111-
var filePath = path.replacing(UInt8(ascii: "/"), with: slash)
2114+
// Convert any "\" to "/" before storing the URL parse info
2115+
var filePath = path.replacing(._backslash, with: ._slash)
21122116
#else
2113-
let slash = UInt8(ascii: "/")
21142117
var filePath = path
21152118
#endif
21162119

@@ -2122,41 +2125,31 @@ extension URL {
21222125
}
21232126
#endif
21242127

2125-
func absoluteFilePath() -> String {
2126-
guard !isAbsolute, let baseURL else {
2127-
return filePath
2128-
}
2129-
let basePath = baseURL.path()
2130-
#if os(Windows)
2131-
let urlPath = filePath.replacing(UInt8(ascii: "\\"), with: UInt8(ascii: "/"))
2132-
return URL.fileSystemPath(for: basePath.merging(relativePath: urlPath)).replacing(UInt8(ascii: "/"), with: UInt8(ascii: "\\"))
2133-
#else
2134-
return URL.fileSystemPath(for: basePath.merging(relativePath: filePath))
2135-
#endif
2136-
}
2137-
21382128
let isDirectory: Bool
21392129
switch directoryHint {
21402130
case .isDirectory:
21412131
isDirectory = true
21422132
case .notDirectory:
2133+
filePath = filePath._droppingTrailingSlashes
21432134
isDirectory = false
21442135
case .checkFileSystem:
21452136
#if !NO_FILESYSTEM
2137+
func absoluteFilePath() -> String {
2138+
guard !isAbsolute, let baseURL else {
2139+
return filePath
2140+
}
2141+
let absolutePath = baseURL.path().merging(relativePath: filePath)
2142+
return URL.fileSystemPath(for: absolutePath)
2143+
}
21462144
isDirectory = URL.isDirectory(absoluteFilePath())
21472145
#else
2148-
isDirectory = filePath.utf8.last == slash
2146+
isDirectory = filePath.utf8.last == ._slash
21492147
#endif
21502148
case .inferFromPath:
2151-
isDirectory = filePath.utf8.last == slash
2149+
isDirectory = filePath.utf8.last == ._slash
21522150
}
21532151

2154-
#if os(Windows)
2155-
// Convert any "\" back to "/" before storing the URL parse info
2156-
filePath = filePath.replacing(UInt8(ascii: "\\"), with: UInt8(ascii: "/"))
2157-
#endif
2158-
2159-
if !filePath.isEmpty && filePath.utf8.last != UInt8(ascii: "/") && isDirectory {
2152+
if isDirectory && !filePath.isEmpty && filePath.utf8.last != ._slash {
21602153
filePath += "/"
21612154
}
21622155
var components = URLComponents()
@@ -2434,6 +2427,9 @@ extension URL {
24342427
guard var filePath = path else {
24352428
return nil
24362429
}
2430+
#if os(Windows)
2431+
filePath = filePath.replacing(._backslash, with: ._slash)
2432+
#endif
24372433
guard URL.isAbsolute(standardizing: &filePath) else {
24382434
return nil
24392435
}

Tests/FoundationEssentialsTests/URLTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,42 @@ final class URLTests : XCTestCase {
586586
XCTAssertEqual(url.fileSystemPath, "/path/slashes")
587587
}
588588

589+
func testURLNotDirectoryHintStripsTrailingSlash() throws {
590+
// Supply a path with a trailing slash but say it's not a direcotry
591+
var url = URL(filePath: "/path/", directoryHint: .notDirectory)
592+
XCTAssertFalse(url.hasDirectoryPath)
593+
XCTAssertEqual(url.path(), "/path")
594+
595+
url = URL(fileURLWithPath: "/path/", isDirectory: false)
596+
XCTAssertFalse(url.hasDirectoryPath)
597+
XCTAssertEqual(url.path(), "/path")
598+
599+
url = URL(filePath: "/path///", directoryHint: .notDirectory)
600+
XCTAssertFalse(url.hasDirectoryPath)
601+
XCTAssertEqual(url.path(), "/path")
602+
603+
url = URL(fileURLWithPath: "/path///", isDirectory: false)
604+
XCTAssertFalse(url.hasDirectoryPath)
605+
XCTAssertEqual(url.path(), "/path")
606+
607+
// With .checkFileSystem, don't modify the path for a non-existent file
608+
url = URL(filePath: "/my/non/existent/path/", directoryHint: .checkFileSystem)
609+
XCTAssertTrue(url.hasDirectoryPath)
610+
XCTAssertEqual(url.path(), "/my/non/existent/path/")
611+
612+
url = URL(fileURLWithPath: "/my/non/existent/path/")
613+
XCTAssertTrue(url.hasDirectoryPath)
614+
XCTAssertEqual(url.path(), "/my/non/existent/path/")
615+
616+
url = URL(filePath: "/my/non/existent/path", directoryHint: .checkFileSystem)
617+
XCTAssertFalse(url.hasDirectoryPath)
618+
XCTAssertEqual(url.path(), "/my/non/existent/path")
619+
620+
url = URL(fileURLWithPath: "/my/non/existent/path")
621+
XCTAssertFalse(url.hasDirectoryPath)
622+
XCTAssertEqual(url.path(), "/my/non/existent/path")
623+
}
624+
589625
func testURLComponentsPercentEncodedUnencodedProperties() throws {
590626
var comp = URLComponents()
591627

0 commit comments

Comments
 (0)