diff --git a/Sources/AsyncHTTPClient/RequestValidation.swift b/Sources/AsyncHTTPClient/RequestValidation.swift index f338e06a9..a8bbb8fc6 100644 --- a/Sources/AsyncHTTPClient/RequestValidation.swift +++ b/Sources/AsyncHTTPClient/RequestValidation.swift @@ -47,6 +47,11 @@ extension HTTPHeaders { private func validateFieldNames() throws { let invalidFieldNames = self.compactMap { name, _ -> String? in + // [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names) defines + // `field-name = token`, and `token = 1*tchar`, so a field name must contain at least + // one character. `allSatisfy` alone is true for the empty name. + guard !name.isEmpty else { return name } + let satisfy = name.utf8.allSatisfy { char -> Bool in switch char { case UInt8(ascii: "a")...UInt8(ascii: "z"), diff --git a/Tests/AsyncHTTPClientTests/RequestValidationTests.swift b/Tests/AsyncHTTPClientTests/RequestValidationTests.swift index ea5a6bd66..37fa3e0eb 100644 --- a/Tests/AsyncHTTPClientTests/RequestValidationTests.swift +++ b/Tests/AsyncHTTPClientTests/RequestValidationTests.swift @@ -82,6 +82,19 @@ class RequestValidationTests: XCTestCase { } } + func testEmptyHeaderFieldNameIsRejected() { + // RFC 9110 defines `field-name = token` and `token = 1*tchar`, so the empty + // name is not a valid field name. + var headers = HTTPHeaders([ + ("", "Haha") + ]) + + XCTAssertThrowsError(try headers.validateAndSetTransportFraming(method: .GET, bodyLength: .known(0))) { + error in + XCTAssertEqual(error as? HTTPClientError, HTTPClientError.invalidHeaderFieldNames([""])) + } + } + func testValidHeaderFieldNames() { var headers = HTTPHeaders([ ("abcdefghijklmnopqrstuvwxyz", "Haha"),