@@ -60,7 +60,7 @@ public final class DefaultURLFormatter: URLFormatter {
6060 // Handle the entry if it has a scheme, make sure it's safe before browsing to it
6161 private func handleWithScheme( with entry: String ) -> URL ? {
6262 // Check if the URL includes a scheme
63- guard let url = URL ( string : entry) ,
63+ guard let url = handleURL ( with : entry) ,
6464 url. scheme != nil ,
6565 entry. range ( of: " \\ b:[0-9]{1,5} " , options: . regularExpression) == nil else {
6666 return nil
@@ -73,17 +73,44 @@ public final class DefaultURLFormatter: URLFormatter {
7373 }
7474 }
7575
76- guard let url = URL ( string: entry) else { return nil }
77-
7876 // Only allow this URL if it's safe
7977 let browsingContext = BrowsingContext ( type: . internalNavigation, url: url)
8078 if securityManager. canNavigateWith ( browsingContext: browsingContext) == . allowed {
81- return URL ( string : entry )
79+ return url
8280 } else {
8381 return nil
8482 }
8583 }
8684
85+ private func handleURL( with entry: String ) -> URL ? {
86+ // Per Apple docs, for apps linked on or after iOS 17 and aligned OS versions, URL parsing has updated
87+ // from the obsolete RFC 1738/1808 parsing to the same RFC 3986 parsing as URLComponents.
88+ // iOS 17+, URL automatically percent- and IDNA-encodes invalid characters to help create a valid URL.
89+
90+ if #available( iOS 17 , * ) {
91+ return URL ( string: entry)
92+ } else {
93+ // We only want to do percent encoding on our other schemes
94+ let hasHTTPScheme = entry. hasPrefix ( " http:// " ) || entry. hasPrefix ( " https:// " )
95+ guard !hasHTTPScheme else { return URL ( string: entry) }
96+ guard let escapedURL = entry. addingPercentEncoding ( withAllowedCharacters: urlAllowed) else {
97+ return nil
98+ }
99+ guard let finalURL = URL ( string: escapedURL) , schemeIsValid ( for: finalURL) else { return nil }
100+ return finalURL
101+ }
102+ }
103+
104+ /**
105+ Returns whether the URL's scheme is one of those listed on the official list of URI schemes.
106+ This only accepts permanent schemes: historical and provisional schemes are not accepted.
107+ */
108+ public func schemeIsValid( for url: URL ) -> Bool {
109+ guard let scheme = url. scheme else { return false }
110+ return SchemesDefinition . permanentURISchemes. contains ( scheme. lowercased ( ) )
111+ && url. absoluteString. lowercased ( ) != scheme + " : "
112+ }
113+
87114 // Handle the entry if it has no scheme
88115 private func handleNoScheme( with entry: String ) -> URL ? {
89116 // First trim white spaces
0 commit comments