Validate QUIC request/response headers against invalid token and disallowed headers.
Add empty string to disallow-list.
Split --gfe2_reloadable_flag_quic_verify_request_headers into 2 flags:
--gfe2_reloadable_flag_quic_verify_request_headers_2 to validate QUIC request/response headers against invalid request with ratio monitoring; mark H2 request with empty string header as invalid earlier in H2 stack.
--gfe2_reloadable_flag_quic_act_upon_invalid_header return error response upon any invalid QUIC request header.

Protected by quic_reloadable_flag_quic_verify_request_headers_2 and quic_reloadable_flag_quic_act_upon_invalid_header.

PiperOrigin-RevId: 407654730
diff --git a/http2/http2_constants.cc b/http2/http2_constants.cc
index 7ffdb04..6feea19 100644
--- a/http2/http2_constants.cc
+++ b/http2/http2_constants.cc
@@ -7,6 +7,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
+#include "http2/platform/api/http2_flag_utils.h"
 #include "http2/platform/api/http2_logging.h"
 
 namespace http2 {
@@ -152,12 +153,24 @@
 
 // Invalid HTTP/2 header names according to
 // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2.
-// TODO(birenroy): Consider adding "upgrade" to this set.
+// TODO(b/78024822): Consider adding "upgrade" to this set.
 constexpr char const* kHttp2InvalidHeaderNames[] = {
+    "connection",        "host", "keep-alive", "proxy-connection",
+    "transfer-encoding", "",
+};
+
+constexpr char const* kHttp2InvalidHeaderNamesOld[] = {
     "connection", "host", "keep-alive", "proxy-connection", "transfer-encoding",
 };
 
 const InvalidHeaderSet& GetInvalidHttp2HeaderSet() {
+  if (!GetQuicheReloadableFlag(quic, quic_verify_request_headers_2)) {
+    static const auto* invalid_header_set_old =
+        new InvalidHeaderSet(std::begin(http2::kHttp2InvalidHeaderNamesOld),
+                             std::end(http2::kHttp2InvalidHeaderNamesOld));
+    return *invalid_header_set_old;
+  }
+  HTTP2_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers_2, 3, 3);
   static const auto* invalid_header_set =
       new InvalidHeaderSet(std::begin(http2::kHttp2InvalidHeaderNames),
                            std::end(http2::kHttp2InvalidHeaderNames));
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index eae2996..949e703 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -6606,7 +6606,8 @@
 }
 
 TEST_P(EndToEndTest, InvalidExtendedConnect) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   ASSERT_TRUE(Initialize());
 
   if (!version_.UsesHttp3()) {
@@ -6626,7 +6627,8 @@
 }
 
 TEST_P(EndToEndTest, RejectExtendedConnect) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   // Disable extended CONNECT.
   memory_cache_backend_.set_enable_extended_connect(false);
   ASSERT_TRUE(Initialize());
@@ -6657,6 +6659,79 @@
   CheckResponseHeaders("404");
 }
 
+TEST_P(EndToEndTest, RejectInvalidRequestHeader) {
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
+  ASSERT_TRUE(Initialize());
+
+  spdy::SpdyHeaderBlock headers;
+  headers[":scheme"] = "https";
+  headers[":authority"] = "localhost";
+  headers[":method"] = "GET";
+  headers[":path"] = "/echo";
+  // transfer-encoding header is not allowed.
+  headers["transfer-encoding"] = "chunk";
+
+  client_->SendMessage(headers, "", /*fin=*/false);
+  client_->WaitForResponse();
+  CheckResponseHeaders("400");
+}
+
+TEST_P(EndToEndTest, RejectTransferEncodingResponse) {
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
+  ASSERT_TRUE(Initialize());
+
+  // Add a response with transfer-encoding headers.
+  SpdyHeaderBlock headers;
+  headers[":status"] = "200";
+  headers["transfer-encoding"] = "gzip";
+
+  SpdyHeaderBlock trailers;
+  trailers["some-trailing-header"] = "trailing-header-value";
+
+  memory_cache_backend_.AddResponse(server_hostname_, "/eep",
+                                    std::move(headers), "", trailers.Clone());
+
+  std::string received_response = client_->SendSynchronousRequest("/eep");
+  EXPECT_THAT(client_->stream_error(),
+              IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
+}
+
+TEST_P(EndToEndTest, RejectUpperCaseRequest) {
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
+  ASSERT_TRUE(Initialize());
+
+  spdy::SpdyHeaderBlock headers;
+  headers[":scheme"] = "https";
+  headers[":authority"] = "localhost";
+  headers[":method"] = "GET";
+  headers[":path"] = "/echo";
+  headers["UpperCaseHeader"] = "foo";
+
+  client_->SendMessage(headers, "", /*fin=*/false);
+  client_->WaitForResponse();
+  CheckResponseHeaders("400");
+}
+
+TEST_P(EndToEndTest, RejectRequestWithInvalidToken) {
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
+  ASSERT_TRUE(Initialize());
+
+  spdy::SpdyHeaderBlock headers;
+  headers[":scheme"] = "https";
+  headers[":authority"] = "localhost";
+  headers[":method"] = "GET";
+  headers[":path"] = "/echo";
+  headers["invalid,header"] = "foo";
+
+  client_->SendMessage(headers, "", /*fin=*/false);
+  client_->WaitForResponse();
+  CheckResponseHeaders("400");
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 7c71abe..fa1e584 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -130,7 +130,7 @@
       "4040"  // 0x40 as the reserved frame type
       "01"    // 1 byte frame length
       "61");  //  payload "a"
-  if ((!GetQuicReloadableFlag(quic_verify_request_headers) ||
+  if ((!GetQuicReloadableFlag(quic_verify_request_headers_2) ||
        perspective() == Perspective::IS_CLIENT) &&
       QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
           HttpDatagramSupport::kDraft00And04) {
@@ -154,7 +154,7 @@
         "01"         // 1 byte frame length
         "61");       //  payload "a"
   }
-  if (GetQuicReloadableFlag(quic_verify_request_headers) &&
+  if (GetQuicReloadableFlag(quic_verify_request_headers_2) &&
       perspective() == Perspective::IS_SERVER &&
       QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
           HttpDatagramSupport::kNone) {
@@ -176,7 +176,7 @@
         "01"    // 1 byte frame length
         "61");  //  payload "a"
   }
-  if (GetQuicReloadableFlag(quic_verify_request_headers) &&
+  if (GetQuicReloadableFlag(quic_verify_request_headers_2) &&
       perspective() == Perspective::IS_SERVER &&
       QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) !=
           HttpDatagramSupport::kNone) {
diff --git a/quic/core/http/quic_spdy_client_stream.cc b/quic/core/http/quic_spdy_client_stream.cc
index 4ba3fc9..2491900 100644
--- a/quic/core/http/quic_spdy_client_stream.cc
+++ b/quic/core/http/quic_spdy_client_stream.cc
@@ -51,6 +51,12 @@
 
   QUICHE_DCHECK(headers_decompressed());
   header_bytes_read_ += frame_len;
+  if (rst_sent()) {
+    // QuicSpdyStream::OnInitialHeadersComplete already rejected invalid
+    // response header.
+    return;
+  }
+
   if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
                                          &response_headers_)) {
     QUIC_DLOG(ERROR) << "Failed to parse header list: "
@@ -191,7 +197,7 @@
 
 bool QuicSpdyClientStream::AreHeadersValid(
     const QuicHeaderList& header_list) const {
-  if (!GetQuicReloadableFlag(quic_verify_request_headers)) {
+  if (!GetQuicReloadableFlag(quic_verify_request_headers_2)) {
     return true;
   }
   if (!QuicSpdyStream::AreHeadersValid(header_list)) {
diff --git a/quic/core/http/quic_spdy_client_stream_test.cc b/quic/core/http/quic_spdy_client_stream_test.cc
index 11f81e5..32a0c0c 100644
--- a/quic/core/http/quic_spdy_client_stream_test.cc
+++ b/quic/core/http/quic_spdy_client_stream_test.cc
@@ -129,7 +129,8 @@
 }
 
 TEST_P(QuicSpdyClientStreamTest, InvalidResponseHeader) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   auto headers = AsHeaderList(std::vector<std::pair<std::string, std::string>>{
       {":status", "200"}, {":path", "/foo"}});
   EXPECT_CALL(*connection_,
@@ -141,7 +142,8 @@
 }
 
 TEST_P(QuicSpdyClientStreamTest, MissingStatusCode) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   auto headers = AsHeaderList(
       std::vector<std::pair<std::string, std::string>>{{"key", "value"}});
   EXPECT_CALL(*connection_,
diff --git a/quic/core/http/quic_spdy_server_stream_base.cc b/quic/core/http/quic_spdy_server_stream_base.cc
index 7431d86..8b57fb6 100644
--- a/quic/core/http/quic_spdy_server_stream_base.cc
+++ b/quic/core/http/quic_spdy_server_stream_base.cc
@@ -50,10 +50,10 @@
 
 bool QuicSpdyServerStreamBase::AreHeadersValid(
     const QuicHeaderList& header_list) const {
-  if (!GetQuicReloadableFlag(quic_verify_request_headers)) {
+  if (!GetQuicReloadableFlag(quic_verify_request_headers_2)) {
     return true;
   }
-  QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers, 3, 3);
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers_2, 2, 3);
   if (!QuicSpdyStream::AreHeadersValid(header_list)) {
     return false;
   }
diff --git a/quic/core/http/quic_spdy_server_stream_base_test.cc b/quic/core/http/quic_spdy_server_stream_base_test.cc
index 92c2d08..ae22781 100644
--- a/quic/core/http/quic_spdy_server_stream_base_test.cc
+++ b/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -109,7 +109,8 @@
   header_list.OnHeader(":scheme", "http");
   header_list.OnHeaderBlockEnd(128, 128);
   stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
-  EXPECT_EQ(GetQuicReloadableFlag(quic_verify_request_headers) &&
+  EXPECT_EQ(GetQuicReloadableFlag(quic_verify_request_headers_2) &&
+                GetQuicReloadableFlag(quic_act_upon_invalid_header) &&
                 !session_.allow_extended_connect(),
             stream_->rst_sent());
 }
@@ -124,7 +125,8 @@
   header_list.OnHeader(":scheme", "http");
   header_list.OnHeaderBlockEnd(128, 128);
   stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
-  EXPECT_EQ(GetQuicReloadableFlag(quic_verify_request_headers) &&
+  EXPECT_EQ(GetQuicReloadableFlag(quic_verify_request_headers_2) &&
+                GetQuicReloadableFlag(quic_act_upon_invalid_header) &&
                 !session_.allow_extended_connect(),
             stream_->rst_sent());
 }
@@ -133,7 +135,8 @@
   if (!session_.version().UsesHttp3()) {
     return;
   }
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
   header_list.OnHeader(":authority", "www.google.com:4433");
@@ -162,7 +165,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidVanillaConnect) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
   header_list.OnHeader(":authority", "www.google.com:4433");
@@ -180,7 +184,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidNonConnectWithProtocol) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
   header_list.OnHeader(":authority", "www.google.com:4433");
@@ -200,7 +205,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutScheme) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   // A request without :scheme should be rejected.
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
@@ -219,7 +225,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutAuthority) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   // A request without :authority should be rejected.
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
@@ -238,7 +245,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutMethod) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   // A request without :method should be rejected.
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
@@ -257,7 +265,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutPath) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   // A request without :path should be rejected.
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
@@ -276,7 +285,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestHeader) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   // A request without :path should be rejected.
   QuicHeaderList header_list;
   header_list.OnHeaderBlockStart();
@@ -296,7 +306,8 @@
 }
 
 TEST_F(QuicSpdyServerStreamBaseTest, EmptyHeaders) {
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
   spdy::SpdyHeaderBlock empty_header;
   quic::test::NoopQpackStreamSenderDelegate encoder_stream_sender_delegate;
   quic::test::NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 54fda98..2d7d906 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -455,7 +455,7 @@
       debug_visitor_(nullptr),
       destruction_indicator_(123456789),
       allow_extended_connect_(
-          GetQuicReloadableFlag(quic_verify_request_headers) &&
+          GetQuicReloadableFlag(quic_verify_request_headers_2) &&
           perspective() == Perspective::IS_SERVER &&
           VersionUsesHttp3(transport_version())) {
   h2_deframer_.set_visitor(spdy_framer_visitor_.get());
@@ -527,7 +527,7 @@
     settings_.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
   }
   if (allow_extended_connect()) {
-    QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers, 1, 3);
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers_2, 1, 3);
     settings_.values[SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
   }
 }
@@ -1758,7 +1758,7 @@
 bool QuicSpdySession::SupportsWebTransport() {
   return WillNegotiateWebTransport() && SupportsH3Datagram() &&
          peer_supports_webtransport_ &&
-         (!GetQuicReloadableFlag(quic_verify_request_headers) ||
+         (!GetQuicReloadableFlag(quic_verify_request_headers_2) ||
           allow_extended_connect_);
 }
 
@@ -1920,11 +1920,11 @@
 // Must not be called after Initialize().
 void QuicSpdySession::set_allow_extended_connect(bool allow_extended_connect) {
   QUIC_BUG_IF(extended connect wrong version,
-              !GetQuicReloadableFlag(quic_verify_request_headers) ||
+              !GetQuicReloadableFlag(quic_verify_request_headers_2) ||
                   !VersionUsesHttp3(transport_version()))
       << "Try to enable/disable extended CONNECT in Google QUIC";
   QUIC_BUG_IF(extended connect on client,
-              !GetQuicReloadableFlag(quic_verify_request_headers) ||
+              !GetQuicReloadableFlag(quic_verify_request_headers_2) ||
                   perspective() == Perspective::IS_CLIENT)
       << "Enabling/disabling extended CONNECT on the client side has no effect";
   if (ShouldNegotiateWebTransport()) {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 964f6fb..834cd18 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -237,6 +237,11 @@
               (override));
 
   MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override));
+
+ protected:
+  bool AreHeadersValid(const QuicHeaderList& /*header_list*/) const override {
+    return true;
+  }
 };
 
 class TestSession : public QuicSpdySession {
@@ -402,7 +407,7 @@
         session_(connection_) {
     if (perspective == Perspective::IS_SERVER &&
         VersionUsesHttp3(transport_version()) &&
-        GetQuicReloadableFlag(quic_verify_request_headers)) {
+        GetQuicReloadableFlag(quic_verify_request_headers_2)) {
       session_.set_allow_extended_connect(allow_extended_connect);
     }
     session_.Initialize();
@@ -3764,7 +3769,8 @@
   if (!version().UsesHttp3()) {
     return;
   }
-  SetQuicReloadableFlag(quic_verify_request_headers, true);
+  SetQuicReloadableFlag(quic_verify_request_headers_2, true);
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
 
   EXPECT_FALSE(session_.SupportsWebTransport());
   EXPECT_TRUE(session_.ShouldProcessIncomingRequests());
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 7cb50c0..8147ce0 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -13,6 +13,7 @@
 #include "absl/strings/numbers.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "http2/http2_constants.h"
 #include "quic/core/http/capsule.h"
 #include "quic/core/http/http_constants.h"
 #include "quic/core/http/http_decoder.h"
@@ -632,12 +633,15 @@
   // OnHeadersTooLarge() should have already handled it previously.
   if (!header_too_large && !AreHeadersValid(header_list)) {
     QUIC_CODE_COUNT_N(quic_validate_request_header, 1, 2);
-    OnInvalidHeaders();
-    return;
+    if (GetQuicReloadableFlag(quic_act_upon_invalid_header)) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_act_upon_invalid_header);
+      OnInvalidHeaders();
+      return;
+    }
   }
   QUIC_CODE_COUNT_N(quic_validate_request_header, 2, 2);
 
-  if (!GetQuicReloadableFlag(quic_verify_request_headers) ||
+  if (!GetQuicReloadableFlag(quic_verify_request_headers_2) ||
       !header_too_large) {
     MaybeProcessReceivedWebTransportHeaders();
     if (ShouldUseDatagramContexts()) {
@@ -1865,10 +1869,39 @@
   }
 }
 
-bool QuicSpdyStream::AreHeadersValid(
-    const QuicHeaderList& /*header_list*/) const {
-  // TODO(b/202433856) check each header name to be valid token and isn't a
-  // disallowed header.
+namespace {
+// Return true if |c| is not allowed in an HTTP/3 wire-encoded header and
+// pseudo-header names according to
+// https://datatracker.ietf.org/doc/html/draft-ietf-quic-http#section-4.1.1 and
+// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-semantics-19#section-5.6.2
+constexpr bool isInvalidHeaderNameCharacter(unsigned char c) {
+  if (c == '!' || c == '|' || c == '~' || c == '*' || c == '+' || c == '-' ||
+      c == '.' ||
+      // #, $, %, &, '
+      (c >= '#' && c <= '\'') ||
+      // [0-9], :
+      (c >= '0' && c <= ':') ||
+      // ^, _, `, [a-z]
+      (c >= '^' && c <= 'z')) {
+    return false;
+  }
+  return true;
+}
+}  // namespace
+
+bool QuicSpdyStream::AreHeadersValid(const QuicHeaderList& header_list) const {
+  QUICHE_DCHECK(GetQuicReloadableFlag(quic_verify_request_headers_2));
+  for (const std::pair<std::string, std::string>& pair : header_list) {
+    const std::string& name = pair.first;
+    if (std::any_of(name.begin(), name.end(), isInvalidHeaderNameCharacter)) {
+      QUIC_DLOG(ERROR) << "Invalid request header " << name;
+      return false;
+    }
+    if (http2::GetInvalidHttp2HeaderSet().contains(name)) {
+      QUIC_DLOG(ERROR) << name << " header is not allowed";
+      return false;
+    }
+  }
   return true;
 }
 
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 2732de2..3bad6ef 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -26,6 +26,7 @@
 #include "quic/core/quic_versions.h"
 #include "quic/core/quic_write_blocked_list.h"
 #include "quic/platform/api/quic_expect_bug.h"
+#include "quic/platform/api/quic_flags.h"
 #include "quic/platform/api/quic_test.h"
 #include "quic/test_tools/qpack/qpack_test_utils.h"
 #include "quic/test_tools/quic_config_peer.h"
@@ -251,6 +252,11 @@
 
   size_t headers_payload_length() const { return headers_payload_length_; }
 
+  bool AreHeadersValid(const QuicHeaderList& header_list) const override {
+    return !GetQuicReloadableFlag(quic_verify_request_headers_2) ||
+           QuicSpdyStream::AreHeadersValid(header_list);
+  }
+
  private:
   bool should_process_data_;
   spdy::SpdyHeaderBlock saved_headers_;
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index fb6032b..9ad90f4 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -93,10 +93,12 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, true)
 // If true, quic dispatcher supports one connection to use multiple connection IDs. 
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_dispatcher_support_multiple_cid_per_connection_v2, true)
-// If true, quic server will send ENABLE_CONNECT_PROTOCOL setting and and endpoint will validate required request/response headers and extended CONNECT mechanism.
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_verify_request_headers, false)
+// If true, quic server will send ENABLE_CONNECT_PROTOCOL setting and and endpoint will validate required request/response headers and extended CONNECT mechanism and update code counts of valid/invalid headers.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_verify_request_headers_2, false)
 // If true, record addresses that server has sent reset to recently, and do not send reset if the address lives in the set.
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_use_recent_reset_addresses, true)
+// If true, reject or send error response code upon receiving invalid request or response headers. This flag depends on --gfe2_reloadable_flag_quic_verify_request_headers_2.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_act_upon_invalid_header, false)
 // If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false)
 // If true, reset per packet state before processing undecryptable packets.
diff --git a/quic/test_tools/quic_test_server.cc b/quic/test_tools/quic_test_server.cc
index 1d85805..e8aee79 100644
--- a/quic/test_tools/quic_test_server.cc
+++ b/quic/test_tools/quic_test_server.cc
@@ -123,7 +123,7 @@
           compressed_certs_cache(), server_backend());
     }
     if (VersionUsesHttp3(version.transport_version) &&
-        GetQuicReloadableFlag(quic_verify_request_headers)) {
+        GetQuicReloadableFlag(quic_verify_request_headers_2)) {
       QUICHE_DCHECK(session->allow_extended_connect());
       // Do not allow extended CONNECT request if the backend doesn't support
       // it.
diff --git a/quic/tools/quic_simple_server_stream.cc b/quic/tools/quic_simple_server_stream.cc
index 84e7071..0ea0e50 100644
--- a/quic/tools/quic_simple_server_stream.cc
+++ b/quic/tools/quic_simple_server_stream.cc
@@ -56,14 +56,13 @@
     size_t frame_len,
     const QuicHeaderList& header_list) {
   QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
-  if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
+  // QuicSpdyStream::OnInitialHeadersComplete() may have already sent error
+  // response.
+  if (!response_sent_ &&
+      !SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
                                          &request_headers_)) {
     QUIC_DVLOG(1) << "Invalid headers";
-    if (!response_sent_) {
-      // QuicSpdyStream::OnInitialHeadersComplete() may have already sent error
-      // response.
       SendErrorResponse();
-    }
   }
   ConsumeHeaderList();
   if (!fin && !response_sent_) {