Add HttpValidationPolicy::disallow_double_quote_in_header_name. PiperOrigin-RevId: 544115479
diff --git a/quiche/balsa/balsa_frame.cc b/quiche/balsa/balsa_frame.cc index 6d48767..8a7dde9 100644 --- a/quiche/balsa/balsa_frame.cc +++ b/quiche/balsa/balsa_frame.cc
@@ -407,8 +407,16 @@ break; } - if (header_properties::IsInvalidHeaderKeyChar(*current)) { - // Generally invalid characters were found earlier. + // Generally invalid characters were found earlier. + if (http_validation_policy().disallow_double_quote_in_header_name) { + if (header_properties::IsInvalidHeaderKeyChar(*current)) { + HandleError(is_trailer + ? BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER + : BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER); + return false; + } + } else if (header_properties::IsInvalidHeaderKeyCharAllowDoubleQuote( + *current)) { HandleError(is_trailer ? BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER : BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
diff --git a/quiche/balsa/balsa_frame_test.cc b/quiche/balsa/balsa_frame_test.cc index 52f84ce..0482425 100644 --- a/quiche/balsa/balsa_frame_test.cc +++ b/quiche/balsa/balsa_frame_test.cc
@@ -3296,6 +3296,22 @@ EXPECT_TRUE(headers_.HasHeader("key\"hasquote")); } +TEST_F(HTTPBalsaFrameTest, KeyHasDisallowedDoubleQuote) { + HttpValidationPolicy http_validation_policy; + http_validation_policy.disallow_double_quote_in_header_name = true; + balsa_frame_.set_http_validation_policy(http_validation_policy); + + const std::string message = + "GET / HTTP/1.1\r\n" + "key\"hasquote: lock\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + // Missing colon is a warning, not an error. TEST_F(HTTPBalsaFrameTest, TrailerMissingColon) { std::string headers =
diff --git a/quiche/balsa/header_properties.cc b/quiche/balsa/header_properties.cc index 240979c..53978df 100644 --- a/quiche/balsa/header_properties.cc +++ b/quiche/balsa/header_properties.cc
@@ -68,6 +68,15 @@ return invalidCharTable; } +std::array<bool, 256> buildInvalidHeaderKeyCharLookupTableAllowDoubleQuote() { + std::array<bool, 256> invalidCharTable; + invalidCharTable.fill(false); + for (uint8_t c : kInvalidHeaderKeyCharListAllowDoubleQuote) { + invalidCharTable[c] = true; + } + return invalidCharTable; +} + std::array<bool, 256> buildInvalidCharLookupTable() { std::array<bool, 256> invalidCharTable; invalidCharTable.fill(false); @@ -92,6 +101,13 @@ return invalidHeaderKeyCharTable[c]; } +bool IsInvalidHeaderKeyCharAllowDoubleQuote(uint8_t c) { + static const std::array<bool, 256> invalidHeaderKeyCharTable = + buildInvalidHeaderKeyCharLookupTableAllowDoubleQuote(); + + return invalidHeaderKeyCharTable[c]; +} + bool IsInvalidHeaderChar(uint8_t c) { static const std::array<bool, 256> invalidCharTable = buildInvalidCharLookupTable();
diff --git a/quiche/balsa/header_properties.h b/quiche/balsa/header_properties.h index cbd5c59..5a8cc24 100644 --- a/quiche/balsa/header_properties.h +++ b/quiche/balsa/header_properties.h
@@ -21,13 +21,23 @@ // An array of characters that are invalid in HTTP header field names. // These are control characters, including \t, \n, \r, as well as space and // (),/;<=>?@[\]{} and \x7f (see +// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2, also // https://tools.ietf.org/html/rfc7230#section-3.2.6). inline constexpr char kInvalidHeaderKeyCharList[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x1E, 0x1F, ' ', '"', '(', ')', ',', '/', ';', '<', + '=', '>', '?', '@', '[', '\\', ']', '{', '}', 0x7F}; + +// This is a non-compliant variant of `kInvalidHeaderKeyCharList` +// that allows the character '"'. +inline constexpr char kInvalidHeaderKeyCharListAllowDoubleQuote[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, ' ', '(', ')', ',', '/', ';', '<', '=', - '>', '?', '@', '[', '\\', ']', '{', '}', 0x7f}; + '>', '?', '@', '[', '\\', ']', '{', '}', 0x7F}; // An array of characters that are invalid in HTTP header field values, // according to RFC 7230 Section 3.2. Valid low characters not in this array @@ -38,8 +48,10 @@ 0x0C, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x7F}; -// Returns true if the given `c` is invalid in a header field name. +// Returns true if the given `c` is invalid in a header field name. The first +// version is spec compliant, the second one incorrectly allows '"'. QUICHE_EXPORT bool IsInvalidHeaderKeyChar(uint8_t c); +QUICHE_EXPORT bool IsInvalidHeaderKeyCharAllowDoubleQuote(uint8_t c); // Returns true if the given `c` is invalid in a header field or the `value` has // invalid characters. QUICHE_EXPORT bool IsInvalidHeaderChar(uint8_t c);
diff --git a/quiche/balsa/header_properties_test.cc b/quiche/balsa/header_properties_test.cc index ef5f8fc..1930354 100644 --- a/quiche/balsa/header_properties_test.cc +++ b/quiche/balsa/header_properties_test.cc
@@ -25,15 +25,39 @@ EXPECT_TRUE(IsInvalidHeaderKeyChar(0x1F)); EXPECT_TRUE(IsInvalidHeaderKeyChar(0x7F)); EXPECT_TRUE(IsInvalidHeaderKeyChar(' ')); + EXPECT_TRUE(IsInvalidHeaderKeyChar('"')); EXPECT_TRUE(IsInvalidHeaderKeyChar('\t')); EXPECT_TRUE(IsInvalidHeaderKeyChar('\r')); EXPECT_TRUE(IsInvalidHeaderKeyChar('\n')); + EXPECT_TRUE(IsInvalidHeaderKeyChar('}')); EXPECT_FALSE(IsInvalidHeaderKeyChar('a')); EXPECT_FALSE(IsInvalidHeaderKeyChar('B')); EXPECT_FALSE(IsInvalidHeaderKeyChar('7')); EXPECT_FALSE(IsInvalidHeaderKeyChar(0x42)); - EXPECT_FALSE(IsInvalidHeaderChar(0x7D)); + EXPECT_FALSE(IsInvalidHeaderKeyChar(0x7C)); + EXPECT_FALSE(IsInvalidHeaderKeyChar(0x7E)); +} + +TEST(HeaderPropertiesTest, IsInvalidHeaderKeyCharAllowDoubleQuote) { + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x00)); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x06)); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x09)); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x1F)); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x7F)); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(' ')); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('\t')); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('\r')); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('\n')); + EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('}')); + + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('"')); + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('a')); + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('B')); + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('7')); + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x42)); + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x7C)); + EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x7E)); } TEST(HeaderPropertiesTest, IsInvalidHeaderChar) {
diff --git a/quiche/balsa/http_validation_policy.h b/quiche/balsa/http_validation_policy.h index a67093b..2e783d7 100644 --- a/quiche/balsa/http_validation_policy.h +++ b/quiche/balsa/http_validation_policy.h
@@ -13,7 +13,8 @@ // An HttpValidationPolicy captures policy choices affecting parsing of HTTP // requests. It offers individual Boolean members to be consulted during the -// parsing of an HTTP request. +// parsing of an HTTP request. For historical reasons, every member is set up +// such that `true` means more strict validation. struct QUICHE_EXPORT HttpValidationPolicy { // https://tools.ietf.org/html/rfc7230#section-3.2.4 deprecates "folding" // of long header lines onto continuation lines. @@ -42,6 +43,11 @@ // with a method POST or PUT, which requires a body, has neither a // "Content-Length" nor a "Transfer-Encoding: chunked" header. bool require_content_length_if_body_required = true; + + // If true, signal an INVALID_HEADER_NAME_CHARACTER or + // INVALID_TRAILER_NAME_CHARACTER error if the header or trailer name contains + // the character '"'. + bool disallow_double_quote_in_header_name = false; }; } // namespace quiche