// Public domain - https://gist.github.com/nolanw/dff7cc5d5570b030d6ba385698348b7c import Foundation extension URLRequest { /** Configures the URL request for `multipart/form-data`. The request's `httpBody` is set, and a value is set for the HTTP header field `Content-Type`. - Parameter parameters: The form data to set. - Parameter encoding: The encoding to use for the keys and values. - Throws: `MultipartFormDataEncodingError` if any keys or values in `parameters` are not entirely in `encoding`. - Note: The default `httpMethod` is `GET`, and `GET` requests do not typically have a response body. Remember to set the `httpMethod` to e.g. `POST` before sending the request. - Seealso: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data */ public mutating func setMultipartFormData(_ parameters: [String: String], encoding: String.Encoding) throws { let makeRandom = { UInt32.random(in: (.min)...(.max)) } let boundary = String(format: "------------------------%08X%08X", makeRandom(), makeRandom()) let contentType: String = try { guard let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(encoding.rawValue)) else { throw MultipartFormDataEncodingError.characterSetName } return "multipart/form-data; charset=\(charset); boundary=\(boundary)" }() addValue(contentType, forHTTPHeaderField: "Content-Type") httpBody = try { var body = Data() for (rawName, rawValue) in parameters { if !body.isEmpty { body.append("\r\n".data(using: .utf8)!) } body.append("--\(boundary)\r\n".data(using: .utf8)!) guard rawName.canBeConverted(to: encoding), let disposition = "Content-Disposition: form-data; name=\"\(rawName)\"\r\n".data(using: encoding) else { throw MultipartFormDataEncodingError.name(rawName) } body.append(disposition) body.append("\r\n".data(using: .utf8)!) guard let value = rawValue.data(using: encoding) else { throw MultipartFormDataEncodingError.value(rawValue, name: rawName) } body.append(value) } body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!) return body }() } } public enum MultipartFormDataEncodingError: Error { case characterSetName case name(String) case value(String, name: String) }