Skip to content

Commit

Permalink
Wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ptoffy committed Nov 6, 2024
1 parent 64a014c commit d6c26c3
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 23 deletions.
8 changes: 4 additions & 4 deletions Sources/MultipartKit/MultipartParser+parse.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import HTTPTypes

extension MultipartParser {
public static func parse(_ data: some Collection<UInt8>, boundary: some Collection<UInt8>) throws -> [MultipartPart<ArraySlice<UInt8>>] {
public func parse(_ data: some Collection<UInt8>) throws -> [MultipartPart<ArraySlice<UInt8>>] {
var output: [MultipartPart<ArraySlice<UInt8>>] = []
var parser = MultipartParser(boundary: boundary)
var parser = MultipartParser(boundary: self.boundary)

var currentHeaders: HTTPFields?
var currentBody = ArraySlice<UInt8>()

// Append data to the parser and process the sections
parser.append(buffer: data)

while true {
switch parser.read() {
case .success(let optionalPart):
Expand Down
4 changes: 2 additions & 2 deletions Sources/MultipartKit/MultipartParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ public struct MultipartParser {

let boundary: ArraySlice<UInt8>
private var state: State

init(boundary: some Collection<UInt8>) {
self.boundary = .init([45, 45] + boundary)
self.state = .initial
}

init(boundary: String) {
public init(boundary: String) {
self.boundary = [45, 45] + ArraySlice(boundary.utf8)
self.state = .initial
}
Expand Down
13 changes: 10 additions & 3 deletions Sources/MultipartKit/MultipartPart.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import HTTPTypes

public struct MultipartPart<Body: Collection<UInt8>>: Equatable, Sendable where Body: Sendable & Equatable {
public let headerFields: HTTPFields
public typealias MultipartPartBodyElement = Collection<UInt8> & Equatable & Sendable

public struct MultipartPart<Body: MultipartPartBodyElement>: Equatable, Sendable {
public var headerFields: HTTPFields
public var body: Body

public init(headerFields: HTTPFields, body: Body) {
self.headerFields = headerFields
self.body = body
}

public var name: String? {
get { self.headerFields.getParameter(.contentDisposition, "name") }
set { self.headerFields.setParameter(.contentDisposition, "name", to: newValue, defaultValue: "form-data") }
}
}
15 changes: 11 additions & 4 deletions Sources/MultipartKit/MultipartSerializer.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
/// Serializes `MultipartForm`s to `Data`.
///
/// See `MultipartParser` for more information about the multipart encoding.
public enum MultipartSerializer: Sendable {
public struct MultipartSerializer: Sendable {
let boundary: String

/// Creates a new `MultipartSerializer`.
init(boundary: String) {
self.boundary = boundary
}

/// Serializes the `MultipartForm` to data.
///
/// let data = try MultipartSerializer().serialize(parts: [part], boundary: "123")
Expand All @@ -12,9 +19,9 @@ public enum MultipartSerializer: Sendable {
/// - boundary: Multipart boundary to use for encoding. This must not appear anywhere in the encoded data.
/// - throws: Any errors that may occur during serialization.
/// - returns: `multipart`-encoded `Data`.
public static func serialize(parts: [MultipartPart<some Collection<UInt8>>], boundary: String) throws -> String {
public func serialize(parts: [MultipartPart<some Collection<UInt8>>]) throws -> String {
var buffer = [UInt8]()
try self.serialize(parts: parts, boundary: boundary, into: &buffer)
try self.serialize(parts: parts, into: &buffer)
return String(decoding: buffer, as: UTF8.self)
}

Expand All @@ -29,7 +36,7 @@ public enum MultipartSerializer: Sendable {
/// - boundary: Multipart boundary to use for encoding. This must not appear anywhere in the encoded data.
/// - buffer: Buffer to write to.
/// - throws: Any errors that may occur during serialization.
public static func serialize(parts: [MultipartPart<some Collection<UInt8>>], boundary: String, into buffer: inout [UInt8]) throws {
public func serialize(parts: [MultipartPart<some Collection<UInt8>>], into buffer: inout [UInt8]) throws {
for part in parts {
buffer.append(contentsOf: Array("--\(boundary)\r\n".utf8))
for field in part.headerFields {
Expand Down
50 changes: 50 additions & 0 deletions Sources/MultipartKit/Utilities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation
import HTTPTypes

extension HTTPFields {
func getParameter(_ name: HTTPField.Name, _ key: String) -> String? {
headerParts(name: name)?
.filter { $0.contains("\(key)=") }
.first?
.split(separator: "=")
.last?
.trimmingCharacters(in: .quotes)
}

mutating func setParameter(
_ name: HTTPField.Name,
_ key: String,
to value: String?,
defaultValue: String
) {
var current: [String]

if let existing = self.headerParts(name: name) {
current = existing.filter { !$0.hasPrefix("\(key)=") }
} else {
current = [defaultValue]
}

if let value = value {
current.append("\(key)=\"\(value)\"")
}

let new = current.joined(separator: "; ").trimmingCharacters(in: .whitespaces)

self[name] = new
}

func headerParts(name: HTTPField.Name) -> [String]? {
self[name]
.flatMap {
$0.split(separator: ";")
.map { $0.trimmingCharacters(in: .whitespaces) }
}
}
}

extension CharacterSet {
static var quotes: CharacterSet {
return .init(charactersIn: #""'"#)
}
}
19 changes: 11 additions & 8 deletions Tests/MultipartKitTests/ParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ struct ParserTests {
}
}
}

@Test("Parse Synchronously")
func parseSynchronously() async throws {
let boundary = "boundary123"
Expand All @@ -138,14 +138,17 @@ struct ParserTests {
123e4567-e89b-12d3-a456-426655440000\r
\(boundary)--
"""

let parts = try MultipartParser.parse([UInt8](message.utf8), boundary: [UInt8](boundary.utf8))


let parts = try MultipartParser(boundary: boundary)
.parse([UInt8](message.utf8))

#expect(parts.count == 1)
#expect(parts[0].headerFields == .init([
.init(name: .contentDisposition, value: "form-data; name=\"id\""),
.init(name: .contentType, value: "text/plain"),
]))
#expect(
parts[0].headerFields
== .init([
.init(name: .contentDisposition, value: "form-data; name=\"id\""),
.init(name: .contentType, value: "text/plain"),
]))
#expect(parts[0].body == ArraySlice("123e4567-e89b-12d3-a456-426655440000".utf8))
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/MultipartKitTests/SerializerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ struct SerializerTests {
.init(name: .contentType, value: "text/plain"),
]),
body: ArraySlice("Hello, world!".utf8)
),
)
]

let serialized = try MultipartSerializer.serialize(parts: example, boundary: "boundary123")
}
}

0 comments on commit d6c26c3

Please sign in to comment.