Skip to content

Commit a1d7c17

Browse files
authored
Merge pull request #2940 from rauhul/feature/URLComponents.percentEncodedQueryItems
2 parents 91c645f + a693f4d commit a1d7c17

File tree

3 files changed

+282
-6
lines changed

3 files changed

+282
-6
lines changed

Sources/Foundation/NSURL.swift

+54-5
Original file line numberDiff line numberDiff line change
@@ -1451,9 +1451,15 @@ open class NSURLComponents: NSObject, NSCopying {
14511451
return NSRange(_CFURLComponentsGetRangeOfFragment(_components))
14521452
}
14531453

1454-
// The getter method that underlies the queryItems property parses the query string based on these delimiters and returns an NSArray containing any number of NSURLQueryItem objects, each of which represents a single key-value pair, in the order in which they appear in the original query string. Note that a name may appear more than once in a single query string, so the name values are not guaranteed to be unique. If the NSURLComponents object has an empty query component, queryItems returns an empty NSArray. If the NSURLComponents object has no query component, queryItems returns nil.
1455-
// The setter method that underlies the queryItems property combines an NSArray containing any number of NSURLQueryItem objects, each of which represents a single key-value pair, into a query string and sets the NSURLComponents' query property. Passing an empty NSArray to setQueryItems sets the query component of the NSURLComponents object to an empty string. Passing nil to setQueryItems removes the query component of the NSURLComponents object.
1456-
// Note: If a name-value pair in a query is empty (i.e. the query string starts with '&', ends with '&', or has "&&" within it), you get a NSURLQueryItem with a zero-length name and and a nil value. If a query's name-value pair has nothing before the equals sign, you get a zero-length name. If a query's name-value pair has nothing after the equals sign, you get a zero-length value. If a query's name-value pair has no equals sign, the query name-value pair string is the name and you get a nil value.
1454+
/// Returns an array of query items for this `URLComponents`, in the order in which they appear in the original query string.
1455+
///
1456+
/// Each `URLQueryItem` represents a single key-value pair,
1457+
///
1458+
/// Note that a name may appear more than once in a single query string, so the name values are not guaranteed to be unique. If the `URLComponents` has an empty query component, returns an empty array. If the `URLComponents` has no query component, returns nil.
1459+
///
1460+
/// The setter combines an array containing any number of `URLQueryItem`s, each of which represents a single key-value pair, into a query string and sets the `URLComponents` query property. Passing an empty array sets the query component of the `URLComponents` to an empty string. Passing nil removes the query component of the `URLComponents`.
1461+
///
1462+
/// - note: If a name-value pair in a query is empty (i.e. the query string starts with '&', ends with '&', or has "&&" within it), you get a `URLQueryItem` with a zero-length name and a nil value. If a query's name-value pair has nothing before the equals sign, you get a zero-length name. If a query's name-value pair has nothing after the equals sign, you get a zero-length value. If a query's name-value pair has no equals sign, the query name-value pair string is the name and you get a nil value.
14571463
open var queryItems: [URLQueryItem]? {
14581464
get {
14591465
// This CFURL implementation returns a CFArray of CFDictionary; each CFDictionary has an entry for name and optionally an entry for value
@@ -1465,8 +1471,8 @@ open class NSURLComponents: NSObject, NSCopying {
14651471
return (0..<count).map { idx in
14661472
let oneEntry = unsafeBitCast(CFArrayGetValueAtIndex(queryArray, idx), to: NSDictionary.self)
14671473
let swiftEntry = oneEntry._swiftObject
1468-
let entryName = swiftEntry["name"] as! String
1469-
let entryValue = swiftEntry["value"] as? String
1474+
let entryName = swiftEntry[_kCFURLComponentsNameKey as! String] as! String
1475+
let entryValue = swiftEntry[_kCFURLComponentsValueKey as! String] as? String
14701476
return URLQueryItem(name: entryName, value: entryValue)
14711477
}
14721478
}
@@ -1490,6 +1496,49 @@ open class NSURLComponents: NSObject, NSCopying {
14901496
_CFURLComponentsSetQueryItems(_components, names._cfObject, values._cfObject)
14911497
}
14921498
}
1499+
1500+
/// Returns an array of query items for this `URLComponents`, in the order in which they appear in the original query string. Any percent-encoding in a query item name or value is retained
1501+
///
1502+
/// The setter combines an array containing any number of `URLQueryItem`s, each of which represents a single key-value pair, into a query string and sets the `URLComponents` query property. This property assumes the query item names and values are already correctly percent-encoded, and that the query item names do not contain the query item delimiter characters '&' and '='. Attempting to set an incorrectly percent-encoded query item or a query item name with the query item delimiter characters '&' and '=' will cause a `fatalError`.
1503+
open var percentEncodedQueryItems: [URLQueryItem]? {
1504+
get {
1505+
// This CFURL implementation returns a CFArray of CFDictionary; each CFDictionary has an entry for name and optionally an entry for value
1506+
guard let queryArray = _CFURLComponentsCopyPercentEncodedQueryItems(_components) else {
1507+
return nil
1508+
}
1509+
1510+
let count = CFArrayGetCount(queryArray)
1511+
return (0..<count).map { idx in
1512+
let oneEntry = unsafeBitCast(CFArrayGetValueAtIndex(queryArray, idx), to: NSDictionary.self)
1513+
let swiftEntry = oneEntry._swiftObject
1514+
let entryName = swiftEntry[_kCFURLComponentsNameKey as! String] as! String
1515+
let entryValue = swiftEntry[_kCFURLComponentsValueKey as! String] as? String
1516+
return URLQueryItem(name: entryName, value: entryValue)
1517+
}
1518+
}
1519+
// This setter essentially acts like `throws!` as described in https://forums.swift.org/t/handling-c-exceptions/34823/7 as throwing accessors are not yet supported, see https://forums.swift.org/t/throwable-accessors/20509 for more details.
1520+
set(new) /* throws! */ {
1521+
guard let new = new else {
1522+
self.percentEncodedQuery = nil
1523+
return
1524+
}
1525+
1526+
// The CFURL implementation requires two CFArrays, one for names and one for values
1527+
var names = [CFTypeRef]()
1528+
var values = [CFTypeRef]()
1529+
for entry in new {
1530+
names.append(entry.name._cfObject)
1531+
if let v = entry.value {
1532+
values.append(v._cfObject)
1533+
} else {
1534+
values.append(kCFNull)
1535+
}
1536+
}
1537+
guard _CFURLComponentsSetPercentEncodedQueryItems(_components, names._cfObject, values._cfObject) else {
1538+
fatalError("NSInvalidArgumentException: invalid characters in percentEncodedQueryItems")
1539+
}
1540+
}
1541+
}
14931542
}
14941543

14951544
extension NSURL: _SwiftBridgeable {

Sources/Foundation/URLComponents.swift

+9-1
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,15 @@ public struct URLComponents : ReferenceConvertible, Hashable, Equatable, _Mutabl
267267
get { return _handle.map { $0.queryItems } }
268268
set { _applyMutation { $0.queryItems = newValue } }
269269
}
270-
270+
271+
/// Returns an array of query items for this `URLComponents`, in the order in which they appear in the original query string. Any percent-encoding in a query item name or value is retained
272+
///
273+
/// The setter combines an array containing any number of `URLQueryItem`s, each of which represents a single key-value pair, into a query string and sets the `URLComponents` query property. This property assumes the query item names and values are already correctly percent-encoded, and that the query item names do not contain the query item delimiter characters '&' and '='. Attempting to set an incorrectly percent-encoded query item or a query item name with the query item delimiter characters '&' and '=' will cause a `fatalError`.
274+
public var percentEncodedQueryItems: [URLQueryItem]? {
275+
get { return _handle.map { $0.percentEncodedQueryItems } }
276+
set { _applyMutation { $0.percentEncodedQueryItems = newValue } }
277+
}
278+
271279
public func hash(into hasher: inout Hasher) {
272280
hasher.combine(_handle.map { $0 })
273281
}

0 commit comments

Comments
 (0)