@@ -1319,13 +1319,32 @@ public struct URL: Equatable, Sendable, Hashable {
1319
1319
}
1320
1320
}
1321
1321
1322
- private static func fileSystemPath( for urlPath: String ) -> String {
1323
- var result = urlPath
1324
- if result. count > 1 && result. utf8. last == UInt8 ( ascii: " / " ) {
1325
- _ = result. popLast ( )
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 ( ) ) )
1326
1334
}
1335
+ return posixPath
1336
+ }
1337
+
1338
+ private static func fileSystemPath( for urlPath: String ) -> String {
1327
1339
let charsToLeaveEncoded : Set < UInt8 > = [ . _slash, 0 ]
1328
- return Parser . percentDecode ( result, 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
1329
1348
}
1330
1349
1331
1350
var fileSystemPath : String {
@@ -2026,55 +2045,65 @@ extension URL {
2026
2045
2027
2046
#if !NO_FILESYSTEM
2028
2047
private static func isDirectory( _ path: String ) -> Bool {
2029
- #if !FOUNDATION_FRAMEWORK
2048
+ #if os(Windows)
2049
+ let path = path. replacing ( . _slash, with: . _backslash)
2050
+ #endif
2051
+ #if !FOUNDATION_FRAMEWORK
2030
2052
var isDirectory : Bool = false
2031
2053
_ = FileManager . default. fileExists ( atPath: path, isDirectory: & isDirectory)
2032
2054
return isDirectory
2033
- #else
2055
+ #else
2034
2056
var isDirectory : ObjCBool = false
2035
2057
_ = FileManager . default. fileExists ( atPath: path, isDirectory: & isDirectory)
2036
2058
return isDirectory. boolValue
2037
- #endif
2059
+ #endif
2038
2060
}
2039
2061
#endif // !NO_FILESYSTEM
2040
2062
2041
2063
/// Checks if a file path is absolute and standardizes the inputted file path on Windows
2064
+ /// Assumes the path only contains `/` as the path separator
2042
2065
internal static func isAbsolute( standardizing filePath: inout String ) -> Bool {
2066
+ if filePath. utf8. first == . _slash {
2067
+ return true
2068
+ }
2043
2069
#if os(Windows)
2044
- var isAbsolute = false
2045
2070
let utf8 = filePath. utf8
2046
- if utf8. first == . _backslash {
2047
- // Either an absolute path or a UNC path
2048
- isAbsolute = true
2049
- } else if utf8. count >= 3 {
2050
- // Check if this is a drive letter
2051
- let first = utf8. first!
2052
- let secondIndex = utf8. index ( after: utf8. startIndex)
2053
- let second = utf8 [ secondIndex]
2054
- let thirdIndex = utf8. index ( after: secondIndex)
2055
- let third = utf8 [ thirdIndex]
2056
- isAbsolute = (
2057
- first. isAlpha
2058
- && ( second == . _colon || second == . _pipe)
2059
- && third == . _backslash
2060
- )
2061
-
2062
- if isAbsolute {
2063
- // Standardize to "\[drive-letter]:\..."
2064
- if second == . _pipe {
2065
- var filePathArray = Array ( utf8)
2066
- filePathArray [ 1 ] = . _colon
2067
- filePathArray. insert ( . _backslash, at: 0 )
2068
- filePath = String ( decoding: filePathArray, as: UTF8 . self)
2069
- } else {
2070
- filePath = " \\ " + filePath
2071
- }
2071
+ guard utf8. count >= 3 else {
2072
+ return false
2073
+ }
2074
+ // Check if this is a drive letter
2075
+ let first = utf8. first!
2076
+ let secondIndex = utf8. index ( after: utf8. startIndex)
2077
+ let second = utf8 [ secondIndex]
2078
+ let thirdIndex = utf8. index ( after: secondIndex)
2079
+ let third = utf8 [ thirdIndex]
2080
+ let isAbsolute = (
2081
+ first. isAlpha
2082
+ && ( second == . _colon || second == . _pipe)
2083
+ && third == . _slash
2084
+ )
2085
+ if isAbsolute {
2086
+ // Standardize to "/[drive-letter]:/..."
2087
+ if second == . _pipe {
2088
+ var filePathArray = Array ( utf8)
2089
+ filePathArray [ 1 ] = . _colon
2090
+ filePathArray. insert ( . _slash, at: 0 )
2091
+ filePath = String ( decoding: filePathArray, as: UTF8 . self)
2092
+ } else {
2093
+ filePath = " / " + filePath
2072
2094
}
2073
2095
}
2074
- #else
2075
- let isAbsolute = filePath. utf8. first == UInt8 ( ascii: " / " ) || filePath. utf8. first == UInt8 ( ascii: " ~ " )
2076
- #endif
2077
2096
return isAbsolute
2097
+ #else // os(Windows)
2098
+ #if !NO_FILESYSTEM
2099
+ // Expand the tilde if present
2100
+ if filePath. utf8. first == UInt8 ( ascii: " ~ " ) {
2101
+ filePath = filePath. expandingTildeInPath
2102
+ }
2103
+ #endif
2104
+ // Make sure the expanded path is absolute
2105
+ return filePath. utf8. first == . _slash
2106
+ #endif // os(Windows)
2078
2107
}
2079
2108
2080
2109
/// Initializes a newly created file URL referencing the local file or directory at path, relative to a base URL.
@@ -2111,10 +2140,9 @@ extension URL {
2111
2140
}
2112
2141
2113
2142
#if os(Windows)
2114
- let slash = UInt8 ( ascii : " \\ " )
2115
- var filePath = path. replacing ( UInt8 ( ascii : " / " ) , with: slash )
2143
+ // Convert any "\" to "/" before storing the URL parse info
2144
+ var filePath = path. replacing ( . _backslash , with: . _slash )
2116
2145
#else
2117
- let slash = UInt8 ( ascii: " / " )
2118
2146
var filePath = path
2119
2147
#endif
2120
2148
@@ -2126,41 +2154,31 @@ extension URL {
2126
2154
}
2127
2155
#endif
2128
2156
2129
- func absoluteFilePath( ) -> String {
2130
- guard !isAbsolute, let baseURL else {
2131
- return filePath
2132
- }
2133
- let basePath = baseURL. path ( )
2134
- #if os(Windows)
2135
- let urlPath = filePath. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2136
- return URL . fileSystemPath ( for: basePath. merging ( relativePath: urlPath) ) . replacing ( UInt8 ( ascii: " / " ) , with: UInt8 ( ascii: " \\ " ) )
2137
- #else
2138
- return URL . fileSystemPath ( for: basePath. merging ( relativePath: filePath) )
2139
- #endif
2140
- }
2141
-
2142
2157
let isDirectory : Bool
2143
2158
switch directoryHint {
2144
2159
case . isDirectory:
2145
2160
isDirectory = true
2146
2161
case . notDirectory:
2162
+ filePath = filePath. _droppingTrailingSlashes
2147
2163
isDirectory = false
2148
2164
case . checkFileSystem:
2149
2165
#if !NO_FILESYSTEM
2166
+ func absoluteFilePath( ) -> String {
2167
+ guard !isAbsolute, let baseURL else {
2168
+ return filePath
2169
+ }
2170
+ let absolutePath = baseURL. path ( ) . merging ( relativePath: filePath)
2171
+ return URL . fileSystemPath ( for: absolutePath)
2172
+ }
2150
2173
isDirectory = URL . isDirectory ( absoluteFilePath ( ) )
2151
2174
#else
2152
- isDirectory = filePath. utf8. last == slash
2175
+ isDirectory = filePath. utf8. last == . _slash
2153
2176
#endif
2154
2177
case . inferFromPath:
2155
- isDirectory = filePath. utf8. last == slash
2178
+ isDirectory = filePath. utf8. last == . _slash
2156
2179
}
2157
2180
2158
- #if os(Windows)
2159
- // Convert any "\" back to "/" before storing the URL parse info
2160
- filePath = filePath. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2161
- #endif
2162
-
2163
- if !filePath. isEmpty && filePath. utf8. last != UInt8 ( ascii: " / " ) && isDirectory {
2181
+ if isDirectory && !filePath. isEmpty && filePath. utf8. last != . _slash {
2164
2182
filePath += " / "
2165
2183
}
2166
2184
var components = URLComponents ( )
@@ -2438,6 +2456,9 @@ extension URL {
2438
2456
guard var filePath = path else {
2439
2457
return nil
2440
2458
}
2459
+ #if os(Windows)
2460
+ filePath = filePath. replacing ( . _backslash, with: . _slash)
2461
+ #endif
2441
2462
guard URL . isAbsolute ( standardizing: & filePath) else {
2442
2463
return nil
2443
2464
}
0 commit comments