Last active
August 10, 2018 23:12
-
-
Save KoCMoHaBTa/a4a422cd5daa1c040ca3ab6803202c70 to your computer and use it in GitHub Desktop.
Multipart Form Data implementation on Swift
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
// | |
// MultipartFormData.swift | |
// https://gist.github.com/KoCMoHaBTa/a4a422cd5daa1c040ca3ab6803202c70 | |
// | |
// Created by Milen Halachev on 20.07.18. | |
// Copyright © 2018 Milen Halachev. All rights reserved. | |
// | |
import Foundation | |
public struct MultipartFormData { | |
///The boundary of the form data. Default to random UUID. | |
public var boundary: String = "---------------------" + UUID().uuidString + "---------------------" | |
///The line ending of the form data. Default to `\r\n` | |
public var lineEnding: String = "\r\n" | |
///The parts, used to build the form data. | |
public var parts: [Part] = [] | |
///Creates an instance of the receiver with a single part | |
public init(parts: [Part]) { | |
self.parts = parts | |
} | |
///Creates an instance of the receiver with multiple parts | |
public init(part: Part) { | |
self.init(parts: [part]) | |
} | |
///Computes the combined data of all parts and returns the result. | |
public var data: Data { | |
//if there are no parts - return empty data | |
guard self.parts.isEmpty == false else { | |
return Data() | |
} | |
let boundary = "--\(self.boundary)".data(using: .utf8)! //preppend -- to the boundary - MIME standart | |
let lineEnding = self.lineEnding.data(using: .utf8)! | |
var data = self.parts.reduce(into: Data(), { (data, part) in | |
let contentDisposition = "Content-Disposition: form-data; name=\"\(part.name)\"; filename=\"\(part.fileName)\"".data(using: .utf8)! | |
let contentType = "Content-Type: \(part.contentType)".data(using: .utf8)! | |
data.append(boundary) | |
data.append(lineEnding) | |
data.append(contentDisposition) | |
data.append(lineEnding) | |
data.append(contentType) | |
data.append(lineEnding) | |
data.append(lineEnding) //extra line | |
data.append(part.data) | |
data.append(lineEnding) | |
}) | |
data.append(boundary) //append the last boundar | |
data.append("--".data(using: .utf8)!) //append -- to the last boundary - MIME standart | |
return data | |
} | |
} | |
extension MultipartFormData { | |
public struct Part { | |
///The binary data of the part. | |
public let data: Data | |
///The value of the `Content-Type` header of the part | |
public let contentType: String | |
///The value of the `name` parameter in the `Content-Disposition` header section of the part. | |
public let name: String | |
///The value of the `filename` parameter in the `Content-Disposition` header section of the part. | |
public let fileName: String | |
public init(data: Data, contentType: String, name: String, fileName: String) { | |
self.data = data | |
self.contentType = contentType | |
self.name = name | |
self.fileName = fileName | |
} | |
///Creates an instance of the receiver with the provided data. The name is set to random UUID | |
public init(data: Data, contentType: String, fileName: String) { | |
self.init(data: data, contentType: contentType, name: .uuid, fileName: fileName) | |
} | |
///Creates an instance of the receiver with the provided data. The name and fileName are set to random UUID | |
public init(data: Data, contentType: String) { | |
self.init(data: data, contentType: contentType, name: .uuid, fileName: .uuid) | |
} | |
///Creates an instance of the receiver with the provided data. The content type is set to `application/octet-stream` and the name and fileName are set to random GUID | |
public init(data: Data) { | |
self.init(data: data, contentType: "application/octet-stream", name: .uuid, fileName: .uuid) | |
} | |
} | |
} |
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
// | |
// MultipartFormDataTests.swift | |
// https://gist.github.com/KoCMoHaBTa/a4a422cd5daa1c040ca3ab6803202c70 | |
// | |
// Created by Milen Halachev on 20.07.18. | |
// Copyright © 2018 Milen Halachev. All rights reserved. | |
// | |
import Foundation | |
import XCTest | |
class MultipartFormDataTests: XCTestCase { | |
//when no parts are provided - produces empty data | |
func testNoParts() { | |
let multipart = MultipartFormData(parts: []) | |
XCTAssertEqual(multipart.data, Data()) | |
} | |
func testSinglePart() { | |
let expectedResult = | |
""" | |
--812dfde1-e9eb-447c-a4e7-e19d3a32290a | |
Content-Disposition: form-data; name="gg"; filename="gg.txt" | |
Content-Type: text/plain | |
123ABC | |
--812dfde1-e9eb-447c-a4e7-e19d3a32290a-- | |
""" | |
var multipart = MultipartFormData(part: MultipartFormData.Part(data: "123ABC".data(using: .utf8)!, contentType: "text/plain", name: "gg", fileName: "gg.txt")) | |
multipart.boundary = "812dfde1-e9eb-447c-a4e7-e19d3a32290a" | |
multipart.lineEnding = "\n" | |
let data = multipart.data | |
let string = String(data: data, encoding: .utf8) | |
XCTAssertEqual(expectedResult, string) | |
} | |
func testMultipleParts() { | |
let expectedResult = | |
""" | |
--812dfde1-e9eb-447c-a4e7-e19d3a32290a | |
Content-Disposition: form-data; name="gg"; filename="gg.txt" | |
Content-Type: text/plain | |
123ABC | |
--812dfde1-e9eb-447c-a4e7-e19d3a32290a | |
Content-Disposition: form-data; name="gson"; filename="omg.json" | |
Content-Type: application/json | |
{"value":123} | |
--812dfde1-e9eb-447c-a4e7-e19d3a32290a | |
Content-Disposition: form-data; name="snimka"; filename="omg.png" | |
Content-Type: image/png | |
lkjaskjldakljfjkdah | |
--812dfde1-e9eb-447c-a4e7-e19d3a32290a-- | |
""" | |
let parts = [ | |
MultipartFormData.Part(data: "123ABC".data(using: .utf8)!, contentType: "text/plain", name: "gg", fileName: "gg.txt"), | |
MultipartFormData.Part(data: try! JSONSerialization.data(withJSONObject: ["value": 123], options: []), contentType: "application/json", name: "gson", fileName: "omg.json"), | |
MultipartFormData.Part(data: "lkjaskjldakljfjkdah".data(using: .utf8)!, contentType: "image/png", name: "snimka", fileName: "omg.png") | |
] | |
var multipart = MultipartFormData(parts: parts) | |
multipart.boundary = "812dfde1-e9eb-447c-a4e7-e19d3a32290a" | |
multipart.lineEnding = "\n" | |
let data = multipart.data | |
let string = String(data: data, encoding: .utf8) | |
XCTAssertEqual(expectedResult, string) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment