@@ -2003,6 +2003,50 @@ extension URL {
2003
2003
}
2004
2004
#endif // !NO_FILESYSTEM
2005
2005
2006
+ /// Checks if a file path is absolute and standardizes the inputted file path on Windows
2007
+ internal static func isAbsolute( standardizing filePath: inout String ) -> Bool {
2008
+ #if os(Windows)
2009
+ var isAbsolute = false
2010
+ let utf8 = filePath. utf8
2011
+ if utf8. first == UInt8 ( ascii: " \\ " ) {
2012
+ // Either an absolute path or a UNC path
2013
+ isAbsolute = true
2014
+ } else if utf8. count >= 3 {
2015
+ // Check if this is a drive letter
2016
+ let first = utf8. first!
2017
+ let secondIndex = utf8. index ( after: utf8. startIndex)
2018
+ let second = utf8 [ secondIndex]
2019
+ let thirdIndex = utf8. index ( after: secondIndex)
2020
+ let third = utf8 [ thirdIndex]
2021
+ isAbsolute = (
2022
+ first. isAlpha
2023
+ && ( second == UInt8 ( ascii: " : " ) || second == UInt8 ( ascii: " | " ) )
2024
+ && third == UInt8 ( ascii: " \\ " )
2025
+ )
2026
+
2027
+ if !isAbsolute {
2028
+ // Strip the drive letter so it's not mistaken as a scheme
2029
+ filePath = String ( filePath [ thirdIndex... ] )
2030
+ } else {
2031
+ // Standardize to "\[drive-letter]:\..."
2032
+ if second == UInt8 ( ascii: " | " ) {
2033
+ var filePathArray = Array ( utf8)
2034
+ filePathArray [ 1 ] = UInt8 ( ascii: " : " )
2035
+ filePathArray. insert ( UInt8 ( ascii: " \\ " ) , at: 0 )
2036
+ filePath = String ( unsafeUninitializedCapacity: filePathArray. count) { buffer in
2037
+ buffer. initialize ( fromContentsOf: filePathArray)
2038
+ }
2039
+ } else {
2040
+ filePath = " \\ " + filePath
2041
+ }
2042
+ }
2043
+ }
2044
+ #else
2045
+ let isAbsolute = filePath. utf8. first == UInt8 ( ascii: " / " ) || filePath. utf8. first == UInt8 ( ascii: " ~ " )
2046
+ #endif
2047
+ return isAbsolute
2048
+ }
2049
+
2006
2050
/// Initializes a newly created file URL referencing the local file or directory at path, relative to a base URL.
2007
2051
///
2008
2052
/// If an empty string is used for the path, then the path is assumed to be ".".
@@ -2027,17 +2071,59 @@ extension URL {
2027
2071
return
2028
2072
}
2029
2073
#endif // FOUNDATION_FRAMEWORK
2074
+ var baseURL = base
2030
2075
guard !path. isEmpty else {
2031
- #if NO_FILESYSTEM
2032
- let baseURL = base
2033
- #else
2034
- let baseURL = base ?? . currentDirectoryOrNil( )
2076
+ #if !NO_FILESYSTEM
2077
+ baseURL = baseURL ?? . currentDirectoryOrNil( )
2035
2078
#endif
2036
2079
self . init ( string: " " , relativeTo: baseURL) !
2037
2080
return
2038
2081
}
2039
2082
2083
+ #if os(Windows)
2084
+ let slash = UInt8 ( ascii: " \\ " )
2085
+ var hostName : String ?
2086
+ var hostEnd = path. startIndex
2087
+ if path. utf8. starts ( with: [ slash, slash] ) {
2088
+ let hostStart = path. utf8. index ( path. utf8. startIndex, offsetBy: 2 )
2089
+ hostEnd = path [ hostStart... ] . utf8. firstIndex { $0 == slash || $0 = UInt8 ( ascii: " / " ) }
2090
+ hostName = path [ hostStart..< hostEnd]
2091
+ }
2092
+ var filePath = path [ hostEnd... ] . replacing ( UInt8 ( ascii: " / " ) , with: slash)
2093
+ #else
2040
2094
let slash = UInt8 ( ascii: " / " )
2095
+ var filePath = path
2096
+ #endif
2097
+
2098
+ let isAbsolute = URL . isAbsolute ( standardizing: & filePath)
2099
+
2100
+ #if !NO_FILESYSTEM
2101
+ if !isAbsolute {
2102
+ baseURL = baseURL ?? . currentDirectoryOrNil( )
2103
+ }
2104
+ #endif
2105
+
2106
+ func absoluteFilePath( ) -> String {
2107
+ #if os(Windows)
2108
+ if let hostName {
2109
+ return #"\\"# + hostName + filePath
2110
+ }
2111
+ #endif
2112
+ guard !isAbsolute, let baseURL else {
2113
+ return filePath
2114
+ }
2115
+ #if os(Windows)
2116
+ let urlPath = filePath. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2117
+ var mergedPath = baseURL. mergedPath ( for: urlPath) . replacing ( UInt8 ( ascii: " / " ) , with: UInt8 ( ascii: " \\ " ) )
2118
+ if let baseHost = baseURL. host ( percentEncoded: false ) , !baseHost. isEmpty {
2119
+ return #"\\"# + baseHost + mergedPath
2120
+ }
2121
+ return mergedPath
2122
+ #else
2123
+ return baseURL. mergedPath ( for: filePath)
2124
+ #endif
2125
+ }
2126
+
2041
2127
let isDirectory : Bool
2042
2128
switch directoryHint {
2043
2129
case . isDirectory:
@@ -2046,39 +2132,46 @@ extension URL {
2046
2132
isDirectory = false
2047
2133
case . checkFileSystem:
2048
2134
#if !NO_FILESYSTEM
2049
- isDirectory = URL . isDirectory ( path )
2135
+ isDirectory = URL . isDirectory ( absoluteFilePath ( ) )
2050
2136
#else
2051
- isDirectory = path . utf8. last == slash
2137
+ isDirectory = filePath . utf8. last == slash
2052
2138
#endif
2053
2139
case . inferFromPath:
2054
- isDirectory = path . utf8. last == slash
2140
+ isDirectory = filePath . utf8. last == slash
2055
2141
}
2056
2142
2057
- var filePath = path
2058
- let isAbsolute = filePath. utf8. first == slash || filePath. utf8. first == UInt8 ( ascii: " ~ " )
2059
2143
if !isAbsolute {
2060
2144
#if !NO_FILESYSTEM
2061
2145
filePath = filePath. standardizingPath
2062
2146
#else
2063
2147
filePath = filePath. removingDotSegments
2064
2148
#endif
2065
2149
}
2066
- if !filePath. isEmpty && filePath. utf8. last != slash && isDirectory {
2150
+
2151
+ #if os(Windows)
2152
+ // Convert any "\" back to "/" before storing the URL parse info
2153
+ filePath = filePath. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2154
+ #endif
2155
+
2156
+ if !filePath. isEmpty && filePath. utf8. last != UInt8 ( ascii: " / " ) && isDirectory {
2067
2157
filePath += " / "
2068
2158
}
2069
2159
var components = URLComponents ( )
2070
2160
if isAbsolute {
2071
2161
components. scheme = " file "
2162
+ #if os(Windows)
2163
+ if let hostName {
2164
+ components. host = hostName
2165
+ } else {
2166
+ components. encodedHost = " "
2167
+ }
2168
+ #else
2072
2169
components. encodedHost = " "
2170
+ #endif
2073
2171
}
2074
2172
components. path = filePath
2075
2173
2076
2174
if !isAbsolute {
2077
- #if NO_FILESYSTEM
2078
- let baseURL = base
2079
- #else
2080
- let baseURL = base ?? . currentDirectoryOrNil( )
2081
- #endif
2082
2175
self = components. url ( relativeTo: baseURL) !
2083
2176
} else {
2084
2177
// Drop the baseURL if the URL is absolute
@@ -2087,6 +2180,9 @@ extension URL {
2087
2180
}
2088
2181
2089
2182
private func appending< S: StringProtocol > ( path: S , directoryHint: DirectoryHint , encodingSlashes: Bool ) -> URL {
2183
+ #if os(Windows)
2184
+ var path = path. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2185
+ #endif
2090
2186
guard var pathToAppend = Parser . percentEncode ( path, component: . path) else {
2091
2187
return self
2092
2188
}
@@ -2332,10 +2428,13 @@ extension URL {
2332
2428
extension URL {
2333
2429
private static func currentDirectoryOrNil( ) -> URL ? {
2334
2430
let path : String ? = FileManager . default. currentDirectoryPath
2335
- guard let path , path . utf8 . first == UInt8 ( ascii : " / " ) else {
2431
+ guard var filePath = path else {
2336
2432
return nil
2337
2433
}
2338
- return URL ( filePath: path, directoryHint: . isDirectory)
2434
+ guard URL . isAbsolute ( standardizing: & filePath) else {
2435
+ return nil
2436
+ }
2437
+ return URL ( filePath: filePath, directoryHint: . isDirectory)
2339
2438
}
2340
2439
2341
2440
/// The working directory of the current process.
@@ -2660,3 +2759,14 @@ extension URL: _ExpressibleByFileReferenceLiteral {
2660
2759
2661
2760
public typealias _FileReferenceLiteralType = URL
2662
2761
#endif // FOUNDATION_FRAMEWORK
2762
+
2763
+ fileprivate extension UInt8 {
2764
+ var isAlpha : Bool {
2765
+ switch self {
2766
+ case UInt8 ( ascii: " A " ) ... UInt8 ( ascii: " Z " ) , UInt8 ( ascii: " a " ) ... UInt8 ( ascii: " z " ) :
2767
+ return true
2768
+ default :
2769
+ return false
2770
+ }
2771
+ }
2772
+ }
0 commit comments