diff --git a/Sources/HTTPTypes/HTTPField.swift b/Sources/HTTPTypes/HTTPField.swift index 306600a..82fd9a7 100644 --- a/Sources/HTTPTypes/HTTPField.swift +++ b/Sources/HTTPTypes/HTTPField.swift @@ -68,6 +68,16 @@ public struct HTTPField: Sendable, Hashable { self.rawValue = Self.legalizeValue(ISOLatin1String(value)) } + /// Create an HTTP field from a name and a value. Leniently legalize the value. + /// - Parameters: + /// - name: The HTTP field name. + /// - lenientValue: The HTTP field value. Newlines and NULs are converted into space + /// characters. + public init(name: Name, lenientValue: some Collection) { + self.name = name + self.rawValue = Self.lenientLegalizeValue(ISOLatin1String(lenientValue)) + } + init(name: Name, uncheckedValue: ISOLatin1String) { self.name = name self.rawValue = uncheckedValue @@ -162,6 +172,22 @@ public struct HTTPField: Sendable, Hashable { } } + static func lenientLegalizeValue(_ value: ISOLatin1String) -> ISOLatin1String { + if value._storage.utf8.allSatisfy({ $0 != 0x00 && $0 != 0x0A && $0 != 0x0D }) { + return value + } else { + let bytes = value._storage.utf8.lazy.map { byte -> UInt8 in + switch byte { + case 0x00, 0x0A, 0x0D: + return 0x20 + default: + return byte + } + } + return ISOLatin1String(unchecked: String(decoding: bytes, as: UTF8.self)) + } + } + /// Whether the string is valid for an HTTP field value based on RFC 9110. /// /// https://www.rfc-editor.org/rfc/rfc9110.html#name-field-values diff --git a/Tests/HTTPTypesTests/HTTPTypesTests.swift b/Tests/HTTPTypesTests/HTTPTypesTests.swift index c5dc1cf..cf01254 100644 --- a/Tests/HTTPTypesTests/HTTPTypesTests.swift +++ b/Tests/HTTPTypesTests/HTTPTypesTests.swift @@ -38,6 +38,7 @@ final class HTTPTypesTests: XCTestCase { XCTAssertEqual(HTTPField(name: .accept, value: " a 😀 \t\n b \t \r ").value, "a 😀 \t b") XCTAssertEqual(HTTPField(name: .accept, value: "").value, "") XCTAssertFalse(HTTPField.isValidValue(" ")) + XCTAssertEqual(HTTPField(name: .accept, lenientValue: " \r\n\0\t ".utf8).value, " \t ") } func testRequest() {