@@ -2003,6 +2003,48 @@ 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 ( decoding: filePathArray, as: UTF8 . self)
2037
+ } else {
2038
+ filePath = " \\ " + filePath
2039
+ }
2040
+ }
2041
+ }
2042
+ #else
2043
+ let isAbsolute = filePath. utf8. first == UInt8 ( ascii: " / " ) || filePath. utf8. first == UInt8 ( ascii: " ~ " )
2044
+ #endif
2045
+ return isAbsolute
2046
+ }
2047
+
2006
2048
/// Initializes a newly created file URL referencing the local file or directory at path, relative to a base URL.
2007
2049
///
2008
2050
/// If an empty string is used for the path, then the path is assumed to be ".".
@@ -2027,17 +2069,43 @@ extension URL {
2027
2069
return
2028
2070
}
2029
2071
#endif // FOUNDATION_FRAMEWORK
2072
+ var baseURL = base
2030
2073
guard !path. isEmpty else {
2031
- #if NO_FILESYSTEM
2032
- let baseURL = base
2033
- #else
2034
- let baseURL = base ?? . currentDirectoryOrNil( )
2074
+ #if !NO_FILESYSTEM
2075
+ baseURL = baseURL ?? . currentDirectoryOrNil( )
2035
2076
#endif
2036
2077
self . init ( string: " " , relativeTo: baseURL) !
2037
2078
return
2038
2079
}
2039
2080
2081
+ #if os(Windows)
2082
+ let slash = UInt8 ( ascii: " \\ " )
2083
+ var filePath = path. replacing ( UInt8 ( ascii: " / " ) , with: slash)
2084
+ #else
2040
2085
let slash = UInt8 ( ascii: " / " )
2086
+ var filePath = path
2087
+ #endif
2088
+
2089
+ let isAbsolute = URL . isAbsolute ( standardizing: & filePath)
2090
+
2091
+ #if !NO_FILESYSTEM
2092
+ if !isAbsolute {
2093
+ baseURL = baseURL ?? . currentDirectoryOrNil( )
2094
+ }
2095
+ #endif
2096
+
2097
+ func absoluteFilePath( ) -> String {
2098
+ guard !isAbsolute, let baseURL else {
2099
+ return filePath
2100
+ }
2101
+ #if os(Windows)
2102
+ let urlPath = filePath. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2103
+ return baseURL. mergedPath ( for: urlPath) . replacing ( UInt8 ( ascii: " / " ) , with: UInt8 ( ascii: " \\ " ) )
2104
+ #else
2105
+ return baseURL. mergedPath ( for: filePath)
2106
+ #endif
2107
+ }
2108
+
2041
2109
let isDirectory : Bool
2042
2110
switch directoryHint {
2043
2111
case . isDirectory:
@@ -2046,24 +2114,28 @@ extension URL {
2046
2114
isDirectory = false
2047
2115
case . checkFileSystem:
2048
2116
#if !NO_FILESYSTEM
2049
- isDirectory = URL . isDirectory ( path )
2117
+ isDirectory = URL . isDirectory ( absoluteFilePath ( ) )
2050
2118
#else
2051
- isDirectory = path . utf8. last == slash
2119
+ isDirectory = filePath . utf8. last == slash
2052
2120
#endif
2053
2121
case . inferFromPath:
2054
- isDirectory = path . utf8. last == slash
2122
+ isDirectory = filePath . utf8. last == slash
2055
2123
}
2056
2124
2057
- var filePath = path
2058
- let isAbsolute = filePath. utf8. first == slash || filePath. utf8. first == UInt8 ( ascii: " ~ " )
2059
2125
if !isAbsolute {
2060
2126
#if !NO_FILESYSTEM
2061
2127
filePath = filePath. standardizingPath
2062
2128
#else
2063
2129
filePath = filePath. removingDotSegments
2064
2130
#endif
2065
2131
}
2066
- if !filePath. isEmpty && filePath. utf8. last != slash && isDirectory {
2132
+
2133
+ #if os(Windows)
2134
+ // Convert any "\" back to "/" before storing the URL parse info
2135
+ filePath = filePath. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2136
+ #endif
2137
+
2138
+ if !filePath. isEmpty && filePath. utf8. last != UInt8 ( ascii: " / " ) && isDirectory {
2067
2139
filePath += " / "
2068
2140
}
2069
2141
var components = URLComponents ( )
@@ -2074,11 +2146,6 @@ extension URL {
2074
2146
components. path = filePath
2075
2147
2076
2148
if !isAbsolute {
2077
- #if NO_FILESYSTEM
2078
- let baseURL = base
2079
- #else
2080
- let baseURL = base ?? . currentDirectoryOrNil( )
2081
- #endif
2082
2149
self = components. url ( relativeTo: baseURL) !
2083
2150
} else {
2084
2151
// Drop the baseURL if the URL is absolute
@@ -2087,15 +2154,16 @@ extension URL {
2087
2154
}
2088
2155
2089
2156
private func appending< S: StringProtocol > ( path: S , directoryHint: DirectoryHint , encodingSlashes: Bool ) -> URL {
2157
+ #if os(Windows)
2158
+ let path = path. replacing ( UInt8 ( ascii: " \\ " ) , with: UInt8 ( ascii: " / " ) )
2159
+ #endif
2090
2160
guard var pathToAppend = Parser . percentEncode ( path, component: . path) else {
2091
2161
return self
2092
2162
}
2093
2163
if encodingSlashes {
2094
2164
var utf8 = Array ( pathToAppend. utf8)
2095
2165
utf8. replace ( [ UInt8 ( ascii: " / " ) ] , with: [ UInt8 ( ascii: " % " ) , UInt8 ( ascii: " 2 " ) , UInt8 ( ascii: " F " ) ] )
2096
- pathToAppend = String ( unsafeUninitializedCapacity: utf8. count) { buffer in
2097
- buffer. initialize ( fromContentsOf: utf8)
2098
- }
2166
+ pathToAppend = String ( decoding: utf8, as: UTF8 . self)
2099
2167
}
2100
2168
2101
2169
let slash = UInt8 ( ascii: " / " )
@@ -2332,10 +2400,13 @@ extension URL {
2332
2400
extension URL {
2333
2401
private static func currentDirectoryOrNil( ) -> URL ? {
2334
2402
let path : String ? = FileManager . default. currentDirectoryPath
2335
- guard let path , path . utf8 . first == UInt8 ( ascii : " / " ) else {
2403
+ guard var filePath = path else {
2336
2404
return nil
2337
2405
}
2338
- return URL ( filePath: path, directoryHint: . isDirectory)
2406
+ guard URL . isAbsolute ( standardizing: & filePath) else {
2407
+ return nil
2408
+ }
2409
+ return URL ( filePath: filePath, directoryHint: . isDirectory)
2339
2410
}
2340
2411
2341
2412
/// The working directory of the current process.
@@ -2660,3 +2731,14 @@ extension URL: _ExpressibleByFileReferenceLiteral {
2660
2731
2661
2732
public typealias _FileReferenceLiteralType = URL
2662
2733
#endif // FOUNDATION_FRAMEWORK
2734
+
2735
+ fileprivate extension UInt8 {
2736
+ var isAlpha : Bool {
2737
+ switch self {
2738
+ case UInt8 ( ascii: " A " ) ... UInt8 ( ascii: " Z " ) , UInt8 ( ascii: " a " ) ... UInt8 ( ascii: " z " ) :
2739
+ return true
2740
+ default :
2741
+ return false
2742
+ }
2743
+ }
2744
+ }
0 commit comments