how to http post special chars in swift
I'm using the following to post an email and password to my server (php script). The problem I'm having is the password contains a special char (specifically the &
symbol) which seems to be getting stripped. I presume because it thinks its separating variables being passed. How can I pass this char without it stripping it?
let myURL = NSURL(string: "my script url here")
let request = NSMutableURLRequest(URL: myURL!)
request.HTTPMethod = "POST"
let postString = "email=\(userEmailText)&password=\(userPasswordText)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
You should be wary of using NSURLComponents
because the NSURLQueryItem
might percent-escape the character in question, the &
, it doesn't percent-escape the +
character (which PHP will interpret as a space in conformance with the W3C Specification for x-www-form-urlencoded
). As the queryItems
documentation says:
Note
RFC 3986 specifies which characters must be percent-encoded in the query component of a URL, but not how those characters should be interpreted. The use of delimited key-value pairs is a common convention, but isn't standardized by a specification. Therefore, you may encounter interoperability problems with other implementations that follow this convention.
One notable example of potential interoperability problems is how the plus sign (
+
) character is handled:According to RFC 3986, the plus sign is a valid character within a query, and doesn't need to be percent-encoded. However, according to the W3C recommendations for URI addressing, the plus sign is reserved as shorthand notation for a space within a query string (for example,
?greeting=hello+world
).
This leaves a few alternatives for percent escaping the values you add to the query of the URL yourself if your value might include a +
character:
-
You can build your own
CharacterSet
of characters to be escaped and then useaddingPercentEncodingForURLQueryValue
in Swift 3:extension String { /// Returns a new string made from the `String` by replacing all characters not in the unreserved /// character set (as defined by RFC3986) with percent encoded characters. func addingPercentEncodingForURLQueryValue() -> String? { let allowedCharacters = CharacterSet.urlQueryValueAllowed() return addingPercentEncoding(withAllowedCharacters: allowedCharacters) } } extension CharacterSet { /// Returns the character set for characters allowed in the individual parameters within a query URL component. /// /// The query component of a URL is the component immediately following a question mark (?). /// For example, in the URL `http://www.example.com/index.php?key1=value1#jumpLink`, the query /// component is `key1=value1`. The individual parameters of that query would be the key `key1` /// and its associated value `value1`. /// /// According to RFC 3986, the set of unreserved characters includes /// /// `ALPHA / DIGIT / "-" / "." / "_" / "~"` /// /// In section 3.4 of the RFC, it further recommends adding `/` and `?` to the list of unescaped characters /// for the sake of compatibility with some erroneous implementations, so this routine also allows those /// to pass unescaped. static func urlQueryValueAllowed() -> CharacterSet { return CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~/?") } }
-
Alamofire takes a similar approach, but approaches this from the other direction, namely grabbing the
.urlQueryAllowed
character set (which is close, but not quite right), and takes out reserved characters identified in RFC 3986. In Swift 3:/// Returns a percent-escaped string following RFC 3986 for a query string key or value. /// /// RFC 3986 states that the following characters are "reserved" characters. /// /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" /// /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" /// should be percent-escaped in the query string. /// /// - parameter string: The string to be percent-escaped. /// /// - returns: The percent-escaped string. public func escape(_ string: String) -> String { let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 let subDelimitersToEncode = "!$&'()*+,;=" var allowedCharacterSet = CharacterSet.urlQueryAllowed allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") var escaped = "" //========================================================================================================== // // Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few // hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no // longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more // info, please refer to: // // - https://github.com/Alamofire/Alamofire/issues/206 // //========================================================================================================== if #available(iOS 8.3, *) { escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string } else { let batchSize = 50 var index = string.startIndex while index != string.endIndex { let startIndex = index let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex let range = startIndex..<endIndex let substring = string.substring(with: range) escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring index = endIndex } } return escaped }
You could then use the above to percent escape the key and the value in the body of the request, for example:
let parameters = ["email" : email, "password" : password]
request.httpBody = parameters
.map { (key, value) in
let escapedKey = key.addingPercentEncodingForURLQueryValue()!
let escapedValue = value.addingPercentEncodingForURLQueryValue()!
return "\(escapedKey)=\(escapedValue)"
}
.joined(separator: "&")
.data(using: .utf8)
For Swift 2 renditions of the above, see the previous revision of this answer.