Skip to content

Instantly share code, notes, and snippets.

@nolanw
Last active May 11, 2023 20:38
Show Gist options
  • Save nolanw/14b277903a2ba446f75202a6bfd55977 to your computer and use it in GitHub Desktop.
Save nolanw/14b277903a2ba446f75202a6bfd55977 to your computer and use it in GitHub Desktop.
Swift x-www-form-urlencoded
// Public domain - https://gist.github.com/nolanw/14b277903a2ba446f75202a6bfd55977
import Foundation
extension URLRequest {
/**
Configures the URL request for `application/x-www-form-urlencoded` data. The request's `httpBody` is set, and values are set for HTTP header fields `Content-Type` and `Content-Length`.
- Parameter queryItems: The (name, value) pairs to encode and set as the request's body.
- Note: The default `httpMethod` is `GET`, and `GET` requests do not typically have a body. Remember to set the `httpMethod` to e.g. `POST` before sending the request.
- Warning: It is a programmer error if any name or value in `queryItems` contains an unpaired UTF-16 surrogate.
*/
mutating func setFormURLEncoded(_ queryItems: [URLQueryItem]) {
setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
func serialize(_ s: String) -> String {
return s
// Force-unwrapping because only known failure case is unpaired surrogate, which we've documented above as an error.
.addingPercentEncoding(withAllowedCharacters: formURLEncodedAllowedCharacters)!
.replacingOccurrences(of: " ", with: "+")
}
// https://url.spec.whatwg.org/#concept-urlencoded-serializer
let output = queryItems.lazy
.map { ($0.name, $0.value ?? "") }
.map { (serialize($0), serialize($1)) }
.map { "\($0)=\($1)" }
.joined(separator: "&")
let data = output.data(using: .utf8)
httpBody = data
if let contentLength = data?.count {
setValue(String(contentLength), forHTTPHeaderField: "Content-Length")
}
}
}
private let formURLEncodedAllowedCharacters: CharacterSet = {
typealias c = UnicodeScalar
// https://url.spec.whatwg.org/#urlencoded-serializing
var allowed = CharacterSet()
allowed.insert(c(0x2A))
allowed.insert(charactersIn: c(0x2D)...c(0x2E))
allowed.insert(charactersIn: c(0x30)...c(0x39))
allowed.insert(charactersIn: c(0x41)...c(0x5A))
allowed.insert(c(0x5F))
allowed.insert(charactersIn: c(0x61)...c(0x7A))
// and we'll deal with ` ` later…
allowed.insert(" ")
return allowed
}()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment