diff --git a/src/proxy/hdrs/MIME.cc b/src/proxy/hdrs/MIME.cc index 9430385d42c..25e2cd43145 100644 --- a/src/proxy/hdrs/MIME.cc +++ b/src/proxy/hdrs/MIME.cc @@ -2509,7 +2509,8 @@ mime_parser_parse(MIMEParser *parser, HdrHeap *heap, MIMEHdrImpl *mh, const char } field_name.rtrim_if(&ParseRules::is_ws); raw_print_field = false; - } else if (parsed.suffix(2) != "\r\n") { + } else if (parsed.suffix(2) != "\r\n" || (parsed.size() > 2 && parsed[parsed.size() - 3] == '\r')) { + // Do not preserve malformed line endings when forwarding the field. raw_print_field = false; } diff --git a/src/proxy/hdrs/unit_tests/test_Hdrs.cc b/src/proxy/hdrs/unit_tests/test_Hdrs.cc index 71d52af9a23..7c00b3ca504 100644 --- a/src/proxy/hdrs/unit_tests/test_Hdrs.cc +++ b/src/proxy/hdrs/unit_tests/test_Hdrs.cc @@ -2983,6 +2983,32 @@ TEST_CASE("HTTP parser tolerates high-bit bytes without UB", "[proxy][hdrtest]") req_hdr.destroy(); } +TEST_CASE("HTTP parser normalizes repeated carriage returns in header line endings", "[proxy][hdrtest]") +{ + constexpr std::string_view message = "GET / HTTP/1.1\r\nHost: example.com\r\nExtra-CRs: \r\r\r\r\n\r\n"sv; + + HTTPParser parser; + http_parser_init(&parser); + + HTTPHdr req_hdr; + HdrHeap *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64); + req_hdr.create(HTTPType::REQUEST, HTTP_1_1, heap); + + auto start = message.data(); + REQUIRE(req_hdr.parse_req(&parser, &start, message.data() + message.size(), true) == ParseResult::DONE); + + std::string serialized(static_cast(req_hdr.length_get()), '\0'); + int index = 0; + int offset = 0; + req_hdr.print(serialized.data(), static_cast(serialized.size()), &index, &offset); + serialized.resize(static_cast(index)); + + CHECK(serialized.find("Extra-CRs: \r\n") != std::string::npos); + CHECK(serialized.find("Extra-CRs: \r\r") == std::string::npos); + + req_hdr.destroy(); +} + TEST_CASE("HTTP response parser tolerates high-bit bytes without UB", "[proxy][hdrtest]") { struct Test {