Skip to content

Commit 914b9f7

Browse files
authored
Support NS/CFURL re-core in Swift (#1238)
* (146349351) Support NS/CFURL re-core in Swift * Fix .fileSystemPath() calls in Windows test * Use encoded strings for .absoluteURL, fix NSURL bridging and CFURL lastPathComponent edge cases * Add workaround for crash on Linux * Fix typo
1 parent c16e0d9 commit 914b9f7

13 files changed

+3784
-1626
lines changed

Sources/FoundationEssentials/CodableUtilities.swift

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ extension UInt8 {
166166
internal static var _exclamation: UInt8 { UInt8(ascii: "!") }
167167
internal static var _ampersand: UInt8 { UInt8(ascii: "&") }
168168
internal static var _pipe: UInt8 { UInt8(ascii: "|") }
169+
internal static var _percent: UInt8 { UInt8(ascii: "%") }
169170
internal static var _period: UInt8 { UInt8(ascii: ".") }
170171
internal static var _e: UInt8 { UInt8(ascii: "e") }
171172
internal static var _E: UInt8 { UInt8(ascii: "E") }

Sources/FoundationEssentials/String/String+Path.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,11 @@ extension String {
210210
guard !pathExtension.isEmpty, validatePathExtension(pathExtension) else {
211211
return self
212212
}
213+
if self == "/" { return "/.\(pathExtension)"}
213214
var result = self._droppingTrailingSlashes
214-
guard result != "/" else {
215+
if result == "/" {
215216
// Path was all slashes
216-
return self + ".\(pathExtension)"
217+
return Substring(self.utf8.dropLast()) + ".\(pathExtension)/"
217218
}
218219
result += ".\(pathExtension)"
219220
if utf8.last == ._slash {
@@ -404,6 +405,13 @@ extension String {
404405
}
405406
}
406407

408+
internal var _droppingTrailingSlash: String {
409+
guard utf8.last == ._slash, utf8.count > 1 else {
410+
return self
411+
}
412+
return String(Substring(utf8.dropLast()))
413+
}
414+
407415
internal var _droppingTrailingSlashes: String {
408416
guard !self.isEmpty else {
409417
return self

Sources/FoundationEssentials/URL/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
target_sources(FoundationEssentials PRIVATE
1616
URL.swift
17+
URL_Bridge.swift
18+
URL_ObjC.swift
19+
URL_Protocol.swift
20+
URL_Swift.swift
1721
URLComponents.swift
1822
URLComponents_ObjC.swift
1923
URLParser.swift)

Sources/FoundationEssentials/URL/URL.swift

+198-1,353
Large diffs are not rendered by default.

Sources/FoundationEssentials/URL/URLComponents.swift

+71
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,56 @@ public struct URLComponents: Hashable, Equatable, Sendable {
489489
return result
490490
}
491491

492+
internal func _uncheckedString(original: Bool) -> String {
493+
let componentsToDecode = original ? urlParseInfo?.encodedComponents ?? [] : []
494+
var result = ""
495+
if let scheme {
496+
result += "\(scheme):"
497+
}
498+
if hasAuthority {
499+
result += "//"
500+
}
501+
if componentsToDecode.contains(.user), let user {
502+
result += user
503+
} else if let percentEncodedUser {
504+
result += percentEncodedUser
505+
}
506+
if componentsToDecode.contains(.password), let password {
507+
result += ":\(password)"
508+
} else if let percentEncodedPassword {
509+
result += ":\(percentEncodedPassword)"
510+
}
511+
if percentEncodedUser != nil || percentEncodedPassword != nil {
512+
result += "@"
513+
}
514+
if componentsToDecode.contains(.host), let host {
515+
result += host
516+
} else if let encodedHost {
517+
result += encodedHost
518+
}
519+
if parseInfoIsValidForPort, let portString = urlParseInfo?.portString {
520+
result += ":\(portString)"
521+
} else if let port {
522+
result += ":\(port)"
523+
}
524+
if componentsToDecode.contains(.path) {
525+
result += path
526+
} else {
527+
result += percentEncodedPath
528+
}
529+
if componentsToDecode.contains(.query), let query {
530+
result += "?\(query)"
531+
} else if let percentEncodedQuery {
532+
result += "?\(percentEncodedQuery)"
533+
}
534+
if componentsToDecode.contains(.fragment), let fragment {
535+
result += "#\(fragment)"
536+
} else if let percentEncodedFragment {
537+
result += "#\(percentEncodedFragment)"
538+
}
539+
return result
540+
}
541+
492542
func rangeOf(_ component: Component) -> Range<String.Index>? {
493543
if let urlParseInfo, parseInfoIsValidForAllRanges {
494544
switch component {
@@ -694,6 +744,21 @@ public struct URLComponents: Hashable, Equatable, Sendable {
694744
self.components = _URLComponents(parseInfo: parseInfo)
695745
}
696746

747+
#if FOUNDATION_FRAMEWORK
748+
internal init?(url: _BridgedURL, resolvingAgainstBaseURL resolve: Bool) {
749+
let string: String
750+
if resolve {
751+
string = url.absoluteString
752+
} else {
753+
string = url.relativeString
754+
}
755+
guard let components = _URLComponents(string: string) else {
756+
return nil
757+
}
758+
self.components = components
759+
}
760+
#endif
761+
697762
/// Returns a URL created from the URLComponents.
698763
///
699764
/// If the URLComponents has an authority component (user, password, host or port) and a path component, then the path must either begin with "/" or be an empty string. If the NSURLComponents does not have an authority component (user, password, host or port) and has a path component, the path component must not start with "//". If those requirements are not met, nil is returned.
@@ -729,6 +794,12 @@ public struct URLComponents: Hashable, Equatable, Sendable {
729794
components.string
730795
}
731796

797+
/// For use by URL to get a non-nil string, potentially decoding components to return an original string.
798+
/// This does not provide any validation, since URL is historically less strict than URLComponents.
799+
internal func _uncheckedString(original: Bool) -> String {
800+
components._uncheckedString(original: original)
801+
}
802+
732803
/// The scheme subcomponent of the URL.
733804
///
734805
/// The getter for this property removes any percent encoding this component may have (if the component allows percent encoding). Setting this property assumes the subcomponent or component string is not percent encoded and will add percent encoding (if the component allows percent encoding).

Sources/FoundationEssentials/URL/URLComponents_ObjC.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ extension NSURLComponents {
3434
return _NSSwiftURLComponents(components: components)
3535
}
3636

37-
static func _parseString(_ string: String, encodingInvalidCharacters: Bool, compatibility: URLParserCompatibility.RawValue) -> String? {
38-
return RFC3986Parser.parse(urlString: string, encodingInvalidCharacters: encodingInvalidCharacters, compatibility: .init(rawValue: compatibility))?.urlString
37+
static func _parseString(_ string: String, encodingInvalidCharacters: Bool, allowEmptyScheme: Bool) -> String? {
38+
return RFC3986Parser.parse(urlString: string, encodingInvalidCharacters: encodingInvalidCharacters, allowEmptyScheme: allowEmptyScheme)?.urlString
3939
}
4040
}
4141

0 commit comments

Comments
 (0)