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