Forked from nolanw/URLRequest+MultipartFormData.swift
Last active
April 22, 2022 12:04
-
-
Save lexrus/e9936f76c80d7f943a1d2c56004cf552 to your computer and use it in GitHub Desktop.
Swift multipart/form-data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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, files: [MultipartFormFile]) 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) else { | |
throw MultipartFormDataEncodingError.name(rawName) | |
} | |
if let data = rawValue.data(using: encoding) { | |
if let disposition = "Content-Disposition: form-data; name=\"\(rawName)\"\r\n".data(using: encoding) { | |
body.append(contentsOf: disposition) | |
} | |
body.append("\r\n".data(using: .utf8)!) | |
body.append(data) | |
} else { | |
throw MultipartFormDataEncodingError.value("\(rawValue)", name: rawName) | |
} | |
} | |
for file in files { | |
if !body.isEmpty { | |
body.append("\r\n".data(using: .utf8)!) | |
} | |
body.append("--\(boundary)\r\n".data(using: .utf8)!) | |
body.append("Content-Disposition: form-data; name=\"\(file.formKey)\"; filename=\"\(file.filename)\"\r\n".data(using: .utf8)!) | |
body.append("Content-Type: \(file.contentType)\r\n\r\n".data(using: .utf8)!) | |
body.append(file.data) | |
body.append("\r\n".data(using: .utf8)!) | |
} | |
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!) | |
return body | |
}() | |
} | |
} | |
public struct MultipartFormFile { | |
public var formKey: String | |
public var filename: String | |
public var contentType: String | |
public var data: Data | |
} | |
public enum MultipartFormDataEncodingError: Error { | |
case characterSetName | |
case name(String) | |
case value(String, name: String) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment