From 20d5ed91aea405a02e3072a55089a2304253c752 Mon Sep 17 00:00:00 2001 From: Guoye Zhang Date: Tue, 23 Apr 2024 17:31:54 -0700 Subject: [PATCH 1/2] Support lenient legalization of header field values --- Sources/HTTPTypes/HTTPField.swift | 25 +++++++++++++++++++++++ Tests/HTTPTypesTests/HTTPTypesTests.swift | 1 + 2 files changed, 26 insertions(+) diff --git a/Sources/HTTPTypes/HTTPField.swift b/Sources/HTTPTypes/HTTPField.swift index 306600a..d845031 100644 --- a/Sources/HTTPTypes/HTTPField.swift +++ b/Sources/HTTPTypes/HTTPField.swift @@ -68,6 +68,15 @@ 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. + /// - value: 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 +171,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() { From c8357002a154177713885fa8ed1b5e5125557e17 Mon Sep 17 00:00:00 2001 From: Guoye Zhang Date: Wed, 24 Apr 2024 10:37:47 -0700 Subject: [PATCH 2/2] Update comment --- Sources/HTTPTypes/HTTPField.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/HTTPTypes/HTTPField.swift b/Sources/HTTPTypes/HTTPField.swift index d845031..82fd9a7 100644 --- a/Sources/HTTPTypes/HTTPField.swift +++ b/Sources/HTTPTypes/HTTPField.swift @@ -71,7 +71,8 @@ public struct HTTPField: Sendable, Hashable { /// Create an HTTP field from a name and a value. Leniently legalize the value. /// - Parameters: /// - name: The HTTP field name. - /// - value: The HTTP field value. Newlines and NULs are converted into space characters. + /// - 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))