@@ -42,8 +42,14 @@ extension URL {
4242 return
4343 }
4444
45- let currentQueryItems = components. queryItems ?? [ ]
46- components. queryItems = currentQueryItems + queryItems
45+ let currentQueryItems = components. percentEncodedQueryItems ?? [ ]
46+
47+ components. percentEncodedQueryItems = currentQueryItems + queryItems. map {
48+ URLQueryItem (
49+ name: escape ( $0. name) ,
50+ value: $0. value. map ( escape)
51+ )
52+ }
4753
4854 if let newURL = components. url {
4955 self = newURL
@@ -56,3 +62,27 @@ extension URL {
5662 return url
5763 }
5864}
65+
66+ func escape( _ string: String ) -> String {
67+ string. addingPercentEncoding ( withAllowedCharacters: . sbURLQueryAllowed) ?? string
68+ }
69+
70+ extension CharacterSet {
71+ /// Creates a CharacterSet from RFC 3986 allowed characters.
72+ ///
73+ /// RFC 3986 states that the following characters are "reserved" characters.
74+ ///
75+ /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
76+ /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
77+ ///
78+ /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
79+ /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
80+ /// should be percent-escaped in the query string.
81+ static let sbURLQueryAllowed : CharacterSet = {
82+ let generalDelimitersToEncode = " :#[]@ " // does not include "?" or "/" due to RFC 3986 - Section 3.4
83+ let subDelimitersToEncode = " !$&'()*+,;= "
84+ let encodableDelimiters = CharacterSet ( charactersIn: " \( generalDelimitersToEncode) \( subDelimitersToEncode) " )
85+
86+ return CharacterSet . urlQueryAllowed. subtracting ( encodableDelimiters)
87+ } ( )
88+ }
0 commit comments