Skip to content

Commit

Permalink
Add sync parsing and serialising
Browse files Browse the repository at this point in the history
  • Loading branch information
ptoffy committed Nov 5, 2024
1 parent 122018f commit 64a014c
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 174 deletions.
11 changes: 0 additions & 11 deletions Sources/MultipartKit/Deprecated/MultipartError.swift

This file was deleted.

56 changes: 56 additions & 0 deletions Sources/MultipartKit/MultipartParser+parse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import HTTPTypes

extension MultipartParser {
public static func parse(_ data: some Collection<UInt8>, boundary: some Collection<UInt8>) throws -> [MultipartPart<ArraySlice<UInt8>>] {
var output: [MultipartPart<ArraySlice<UInt8>>] = []
var parser = MultipartParser(boundary: 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):
switch optionalPart {
case .none:
continue
case .some(let part):
switch part {
case .headerFields(let newFields):
if let headers = currentHeaders {
// Merge multiple header fields into the current headers
currentHeaders = HTTPFields(headers + newFields)
} else {
currentHeaders = newFields
}
case .bodyChunk(let bodyChunk):
// Accumulate body chunks
currentBody.append(contentsOf: bodyChunk)
case .boundary:
// Create a MultipartPart when reaching a boundary
if let headers = currentHeaders {
output.append(MultipartPart(headerFields: headers, body: currentBody))
}
// Reset for the next part
currentHeaders = nil
currentBody = []
}
}
case .needMoreData:
// No more data is available in synchronous parsing, this should never happen
preconditionFailure("More data is needed")
case .error(let error):
throw error
case .finished:
// If finished, add any remaining part
if let headers = currentHeaders {
output.append(MultipartPart(headerFields: headers, body: currentBody))
}
return output
}
}
}
}
17 changes: 11 additions & 6 deletions Sources/MultipartKit/MultipartParser.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import HTTPTypes

struct MultipartParser {
public struct MultipartParser {
enum Error: Swift.Error, Equatable {
case invalidBoundary
case invalidHeader(reason: String)
Expand All @@ -21,15 +21,20 @@ 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) {
self.boundary = ArraySlice(boundary.utf8)
self.boundary = [45, 45] + ArraySlice(boundary.utf8)
self.state = .initial
}

enum ReadResult {
case finished
case success(reading: MultipartPart? = nil)
case success(reading: MultipartSection? = nil)
case error(Error)
case needMoreData
}
Expand Down Expand Up @@ -75,12 +80,12 @@ struct MultipartParser {
switch buffer[index...].getIndexAfter([45, 45]) { // check if it's the final boundary (ends with "--")
case .success: // if it is, finish
self.state = .finished
return .finished
return .success(reading: .boundary(end: true))
case .prematureEnd:
return .needMoreData
case .wrongCharacter: // if it's not, move on to reading headers
self.state = .parsing(.header, buffer[index...])
return .success()
return .success(reading: .boundary(end: false))
}
}
}
Expand Down Expand Up @@ -175,7 +180,7 @@ struct MultipartParser {

// move on to reading the next header
self.state = .parsing(.header, buffer[headerValue.endIndex...])
return .success(reading: .headerField(field))
return .success(reading: .headerFields(.init([field])))
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct MultipartParseSequence<BackingSequence: AsyncSequence>: AsyncSequence where BackingSequence.Element: Collection<UInt8> {
public struct MultipartParserAsyncSequence<BackingSequence: AsyncSequence>: AsyncSequence where BackingSequence.Element: Collection<UInt8> {
private let parser: MultipartParser
private let buffer: BackingSequence

Expand All @@ -20,7 +20,7 @@ public struct MultipartParseSequence<BackingSequence: AsyncSequence>: AsyncSeque
self.iterator = iterator
}

public mutating func next() async throws -> MultipartPart? {
public mutating func next() async throws -> MultipartSection? {
while true {
switch parser.read() {
case .success(let optionalPart):
Expand Down
13 changes: 8 additions & 5 deletions Sources/MultipartKit/MultipartPart.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import Foundation
import HTTPTypes

public enum MultipartPart: Equatable, Sendable {
case headerField(HTTPField)
case bodyChunk(ArraySlice<UInt8>)
case boundary
public struct MultipartPart<Body: Collection<UInt8>>: Equatable, Sendable where Body: Sendable & Equatable {
public let headerFields: HTTPFields
public var body: Body

public init(headerFields: HTTPFields, body: Body) {
self.headerFields = headerFields
self.body = body
}
}
8 changes: 8 additions & 0 deletions Sources/MultipartKit/MultipartSection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation
import HTTPTypes

public enum MultipartSection: Equatable, Sendable {
case headerFields(HTTPFields)
case bodyChunk(ArraySlice<UInt8>)
case boundary(end: Bool)
}
46 changes: 15 additions & 31 deletions Sources/MultipartKit/MultipartSerializer.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import NIOCore

/// Serializes `MultipartForm`s to `Data`.
///
/// See `MultipartParser` for more information about the multipart encoding.
public final class MultipartSerializer: Sendable {

/// Creates a new `MultipartSerializer`.
public init() {}

public enum MultipartSerializer: Sendable {
/// Serializes the `MultipartForm` to data.
///
/// let data = try MultipartSerializer().serialize(parts: [part], boundary: "123")
Expand All @@ -18,10 +12,10 @@ public final class 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 func serialize(parts: [MultipartPart], boundary: String) throws -> String {
var buffer = ByteBufferAllocator().buffer(capacity: 0)
public static func serialize(parts: [MultipartPart<some Collection<UInt8>>], boundary: String) throws -> String {
var buffer = [UInt8]()
try self.serialize(parts: parts, boundary: boundary, into: &buffer)
return String(decoding: buffer.readableBytesView, as: UTF8.self)
return String(decoding: buffer, as: UTF8.self)
}

/// Serializes the `MultipartForm` into a `ByteBuffer`.
Expand All @@ -35,26 +29,16 @@ public final class 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 func serialize(parts: [MultipartPart], boundary: String, into buffer: inout ByteBuffer)
throws
{
// for part in parts {
// buffer.writeString("--")
// buffer.writeString(boundary)
// buffer.writeString("\r\n")
// for (key, val) in part.headers {
// buffer.writeString(key)
// buffer.writeString(": ")
// buffer.writeString(val)
// buffer.writeString("\r\n")
// }
// buffer.writeString("\r\n")
// var body = part.body
// buffer.writeBuffer(&body)
// buffer.writeString("\r\n")
// }
// buffer.writeString("--")
// buffer.writeString(boundary)
// buffer.writeString("--\r\n")
public static func serialize(parts: [MultipartPart<some Collection<UInt8>>], boundary: String, into buffer: inout [UInt8]) throws {
for part in parts {
buffer.append(contentsOf: Array("--\(boundary)\r\n".utf8))
for field in part.headerFields {
buffer.append(contentsOf: Array("\(field.description)\r\n".utf8))
}
buffer.append(contentsOf: Array("\r\n".utf8))
buffer.append(contentsOf: part.body)
buffer.append(contentsOf: Array("\r\n".utf8))
}
buffer.append(contentsOf: Array("--\(boundary)--\r\n".utf8))
}
}
52 changes: 0 additions & 52 deletions Sources/MultipartKit/Utilities.swift

This file was deleted.

Loading

0 comments on commit 64a014c

Please sign in to comment.