Option for rejecting isolated carriage returns in header values. PiperOrigin-RevId: 619209119
diff --git a/quiche/balsa/balsa_frame.cc b/quiche/balsa/balsa_frame.cc index a1d04b6..03bc994 100644 --- a/quiche/balsa/balsa_frame.cc +++ b/quiche/balsa/balsa_frame.cc
@@ -545,6 +545,12 @@ found_invalid = true; invalid_chars_[*c]++; } + if (*c == '\r' && + http_validation_policy().disallow_lone_cr_in_request_headers && + c + 1 < stream_end && *(c + 1) != '\n') { + found_invalid = true; + invalid_chars_[*c]++; + } } return found_invalid;
diff --git a/quiche/balsa/balsa_frame_test.cc b/quiche/balsa/balsa_frame_test.cc index 0e81900..294e11f 100644 --- a/quiche/balsa/balsa_frame_test.cc +++ b/quiche/balsa/balsa_frame_test.cc
@@ -803,7 +803,7 @@ TEST(HTTPBalsaFrame, RequestLinesParsedProperly) { SCOPED_TRACE("Testing that lines are properly parsed."); - const char firstline[] = "GET / HTTP/1.1\r\n"; + const char firstline[] = "GET / \rHTTP/1.1\r\n"; const std::pair<std::string, std::string> headers[] = { std::pair<std::string, std::string>("foo", "bar"), std::pair<std::string, std::string>("duck", "water"), @@ -840,6 +840,41 @@ HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\r\n"); } +TEST(HTTPBalsaFrame, CarriageReturnIllegalInHeaders) { + HttpValidationPolicy policy{.disallow_lone_cr_in_request_headers = true}; + BalsaHeaders balsa_headers; + BalsaFrame framer; + framer.set_is_request(true); + framer.set_balsa_headers(&balsa_headers); + framer.set_http_validation_policy(policy); + framer.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError); + const std::pair<std::string, std::string> headers[] = { + std::pair<std::string, std::string>("foo", "bar"), + std::pair<std::string, std::string>("trucks", "value-has-solo-\r-in it"), + }; + std::string message = + CreateMessage("GET / \rHTTP/1.1\r\n", headers, 2, ":", "\r\n", ""); + framer.ProcessInput(message.data(), message.size()); + EXPECT_EQ(framer.ErrorCode(), BalsaFrameEnums::INVALID_HEADER_CHARACTER); + // One carriage return in firstline, 1 in header value. + EXPECT_EQ(framer.get_invalid_chars().at('\r'), 2); +} + +TEST(HTTPBalsaFrame, CarriageReturnIllegalInHeaderKey) { + BalsaHeaders balsa_headers; + BalsaFrame framer; + framer.set_is_request(true); + framer.set_balsa_headers(&balsa_headers); + framer.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError); + const std::pair<std::string, std::string> headers[] = { + std::pair<std::string, std::string>("tru\rcks", "along"), + }; + std::string message = + CreateMessage("GET / HTTP/1.1\r\n", headers, 1, ":", "\r\n", ""); + framer.ProcessInput(message.data(), message.size()); + EXPECT_EQ(framer.ErrorCode(), BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER); +} + TEST(HTTPBalsaFrame, ResponseLinesParsedProperly) { SCOPED_TRACE("ResponseLineParsedProperly"); const char firstline[] = "HTTP/1.0 200 A reason\tphrase\r\n";
diff --git a/quiche/balsa/http_validation_policy.h b/quiche/balsa/http_validation_policy.h index 5b912a1..9ea158ec 100644 --- a/quiche/balsa/http_validation_policy.h +++ b/quiche/balsa/http_validation_policy.h
@@ -54,6 +54,11 @@ // invalid character. Invalid characters are always disallowed according to // InvalidCharsLevel in request headers. bool disallow_invalid_header_characters_in_response = false; + + // If true, then signal an INVALID_HEADER_CHARACTER warning or error, or + // neither, depending on InvalidCharsLevel, if a request header value contains + // a carriage return that is not succeeded by a line feed. + bool disallow_lone_cr_in_request_headers = false; }; } // namespace quiche