Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Sources/OpenAPIRuntime/Errors/RuntimeError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret
// Multipart
case missingRequiredMultipartFormDataContentType
case missingMultipartBoundaryContentTypeParameter
case multipartBoundaryTooLong

// Transport/Handler
case transportFailed(any Error)
Expand Down Expand Up @@ -118,6 +119,7 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret
case .missingRequiredMultipartFormDataContentType: return "Expected a 'multipart/form-data' content type."
case .missingMultipartBoundaryContentTypeParameter:
return "Missing 'boundary' parameter in the 'multipart/form-data' content type."
case .multipartBoundaryTooLong: return "Multipart boundary is too long; RFC 2046 allows at most 70 bytes."
case .transportFailed: return "Transport threw an error."
case .middlewareFailed(middlewareType: let type, _): return "Middleware of type '\(type)' threw an error."
case .handlerFailed: return "User handler threw an error."
Expand Down Expand Up @@ -166,7 +168,7 @@ extension RuntimeError: HTTPResponseConvertible {
case .unexpectedAcceptHeader: .notAcceptable
case .failedToDecodeStringConvertibleValue, .invalidAcceptSubstring, .invalidBase64String,
.invalidHeaderFieldName, .malformedAcceptHeader, .missingMultipartBoundaryContentTypeParameter,
.missingOrMalformedContentDispositionName, .missingRequiredHeaderField,
.multipartBoundaryTooLong, .missingOrMalformedContentDispositionName, .missingRequiredHeaderField,
.missingRequiredMultipartFormDataContentType, .missingRequiredQueryParameter, .missingRequiredPathParameter,
.missingRequiredRequestBody, .unsupportedParameterStyle, .failedToParseRequest:
.badRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
guard let boundary = self.parameters["boundary"] else {
throw RuntimeError.missingMultipartBoundaryContentTypeParameter
}
// RFC 2046 section 5.1.1 limits the boundary to at most 70 characters.
guard boundary.utf8.count <= 70 else { throw RuntimeError.multipartBoundaryTooLong }
return boundary
}
}
15 changes: 15 additions & 0 deletions Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,19 @@ final class Test_OpenAPIMIMEType: Test_Runtime {
testJSONWith2Params(against: subtypeWildcard, expected: .subtypeWildcard)
testJSONWith2Params(against: fullWildcard, expected: .wildcard)
}

func testRequiredBoundaryRejectsBoundaryOverRFC2046Limit() throws {
// RFC 2046 section 5.1.1 limits a multipart boundary to at most 70 characters.
let atLimit = String(repeating: "a", count: 70)
let mimeAtLimit: OpenAPIMIMEType? = OpenAPIMIMEType("multipart/form-data; boundary=\(atLimit)")
XCTAssertEqual(try mimeAtLimit.requiredBoundary(), atLimit)

let overLimit = String(repeating: "a", count: 71)
let mimeOverLimit: OpenAPIMIMEType? = OpenAPIMIMEType("multipart/form-data; boundary=\(overLimit)")
XCTAssertThrowsError(try mimeOverLimit.requiredBoundary()) { error in
guard case RuntimeError.multipartBoundaryTooLong = error else {
return XCTFail("Unexpected error: \(error)")
}
}
}
}