Fix QuicSpdyStream to allow "host" header in response header according to https://github.com/envoyproxy/envoy/issues/9873. Also rename ValidatedRequestHeaders() to ValidatedReceivedHeaders().

The behavioral change is client side only, and does not affect GFE backend.

Protected by FLAGS_quic_reloadable_flag_quic_allow_host_header_in_response with default true.

PiperOrigin-RevId: 549684429
diff --git a/quiche/quic/core/http/quic_spdy_client_stream.cc b/quiche/quic/core/http/quic_spdy_client_stream.cc
index 30ddbe2..e5da71c 100644
--- a/quiche/quic/core/http/quic_spdy_client_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_client_stream.cc
@@ -197,9 +197,9 @@
   return bytes_sent;
 }
 
-bool QuicSpdyClientStream::ValidatedRequestHeaders(
+bool QuicSpdyClientStream::ValidatedReceivedHeaders(
     const QuicHeaderList& header_list) {
-  if (!QuicSpdyStream::ValidatedRequestHeaders(header_list)) {
+  if (!QuicSpdyStream::ValidatedReceivedHeaders(header_list)) {
     return false;
   }
   // Verify the presence of :status header.
diff --git a/quiche/quic/core/http/quic_spdy_client_stream.h b/quiche/quic/core/http/quic_spdy_client_stream.h
index 089ad74..3f35d7d 100644
--- a/quiche/quic/core/http/quic_spdy_client_stream.h
+++ b/quiche/quic/core/http/quic_spdy_client_stream.h
@@ -82,7 +82,7 @@
   using QuicSpdyStream::SetPriority;
 
  protected:
-  bool ValidatedRequestHeaders(const QuicHeaderList& header_list) override;
+  bool ValidatedReceivedHeaders(const QuicHeaderList& header_list) override;
 
   // Called by OnInitialHeadersComplete to set response_header_. Returns false
   // on error.
diff --git a/quiche/quic/core/http/quic_spdy_client_stream_test.cc b/quiche/quic/core/http/quic_spdy_client_stream_test.cc
index 2735b01..7310486 100644
--- a/quiche/quic/core/http/quic_spdy_client_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_client_stream_test.cc
@@ -160,6 +160,17 @@
   EXPECT_EQ(body_, stream_->data());
 }
 
+TEST_P(QuicSpdyClientStreamTest, HostAllowedInResponseHeader) {
+  SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
+  SetQuicReloadableFlag(quic_allow_host_header_in_response, true);
+  auto headers = AsHeaderList(std::vector<std::pair<std::string, std::string>>{
+      {":status", "200"}, {"host", "example.com"}});
+  EXPECT_CALL(*connection_, OnStreamReset(stream_->id(), _)).Times(0u);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_STREAM_NO_ERROR));
+}
+
 TEST_P(QuicSpdyClientStreamTest, Test100ContinueBeforeSuccessful) {
   // First send 100 Continue.
   headers_[":status"] = "100";
diff --git a/quiche/quic/core/http/quic_spdy_server_stream_base.cc b/quiche/quic/core/http/quic_spdy_server_stream_base.cc
index 6176032..18e52a1 100644
--- a/quiche/quic/core/http/quic_spdy_server_stream_base.cc
+++ b/quiche/quic/core/http/quic_spdy_server_stream_base.cc
@@ -49,9 +49,9 @@
   QuicSpdyStream::StopReading();
 }
 
-bool QuicSpdyServerStreamBase::ValidatedRequestHeaders(
+bool QuicSpdyServerStreamBase::ValidatedReceivedHeaders(
     const QuicHeaderList& header_list) {
-  if (!QuicSpdyStream::ValidatedRequestHeaders(header_list)) {
+  if (!QuicSpdyStream::ValidatedReceivedHeaders(header_list)) {
     return false;
   }
 
diff --git a/quiche/quic/core/http/quic_spdy_server_stream_base.h b/quiche/quic/core/http/quic_spdy_server_stream_base.h
index 24133d5..d547841 100644
--- a/quiche/quic/core/http/quic_spdy_server_stream_base.h
+++ b/quiche/quic/core/http/quic_spdy_server_stream_base.h
@@ -23,7 +23,7 @@
   void StopReading() override;
 
  protected:
-  bool ValidatedRequestHeaders(const QuicHeaderList& header_list) override;
+  bool ValidatedReceivedHeaders(const QuicHeaderList& header_list) override;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index ba1b6e3..749ac81 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -257,7 +257,8 @@
   MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override));
 
  protected:
-  bool ValidatedRequestHeaders(const QuicHeaderList& /*header_list*/) override {
+  bool ValidatedReceivedHeaders(
+      const QuicHeaderList& /*header_list*/) override {
     return true;
   }
 };
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index 9646d21..28b509a 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -621,7 +621,7 @@
   }
   // Validate request headers if it did not exceed size limit. If it did,
   // OnHeadersTooLarge() should have already handled it previously.
-  if (!header_too_large && !ValidatedRequestHeaders(header_list)) {
+  if (!header_too_large && !ValidatedReceivedHeaders(header_list)) {
     QUIC_CODE_COUNT_N(quic_validate_request_header, 1, 2);
     QUICHE_DCHECK(!invalid_request_details().empty())
         << "ValidatedRequestHeaders() returns false without populating "
@@ -1678,7 +1678,7 @@
 }
 }  // namespace
 
-bool QuicSpdyStream::ValidatedRequestHeaders(
+bool QuicSpdyStream::ValidatedReceivedHeaders(
     const QuicHeaderList& header_list) {
   bool force_fail_validation = false;
   AdjustTestValue("quic::QuicSpdyStream::request_header_validation_adjust",
@@ -1689,6 +1689,7 @@
     QUIC_DLOG(ERROR) << invalid_request_details_;
     return false;
   }
+  bool is_response = false;
   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)) {
@@ -1696,6 +1697,16 @@
       QUIC_DLOG(ERROR) << invalid_request_details_;
       return false;
     }
+    if (GetQuicReloadableFlag(quic_allow_host_header_in_response)) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_allow_host_header_in_response);
+      if (name == ":status") {
+        is_response = !pair.second.empty();
+      }
+      if (is_response && name == "host") {
+        // Host header is allowed in response.
+        continue;
+      }
+    }
     if (http2::GetInvalidHttp2HeaderSet().contains(name)) {
       invalid_request_details_ = absl::StrCat(name, " header is not allowed");
       QUIC_DLOG(ERROR) << invalid_request_details_;
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h
index 66439ae..a43e9fb 100644
--- a/quiche/quic/core/http/quic_spdy_stream.h
+++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -365,10 +365,10 @@
 
   void OnWriteSideInDataRecvdState() override;
 
-  virtual bool ValidatedRequestHeaders(const QuicHeaderList& header_list);
+  virtual bool ValidatedReceivedHeaders(const QuicHeaderList& header_list);
   // TODO(b/202433856) Merge AreHeaderFieldValueValid into
-  // ValidatedRequestHeaders once all flags guarding the behavior of
-  // ValidatedRequestHeaders has been rolled out.
+  // ValidatedReceivedHeaders once all flags guarding the behavior of
+  // ValidatedReceivedHeaders has been rolled out.
   virtual bool AreHeaderFieldValuesValid(
       const QuicHeaderList& header_list) const;
 
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 4528846..136d470 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -19,6 +19,8 @@
 QUIC_FLAG(quic_reloadable_flag_quic_block_until_settings_received_copt, false)
 // If trrue, early return before write control frame in OnCanWrite() if the connection is already closed.
 QUIC_FLAG(quic_reloadable_flag_quic_no_write_control_frame_upon_connection_close, true)
+// If true, HTTP/3 client will allow host header in HTTP/3 response.
+QUIC_FLAG(quic_reloadable_flag_quic_allow_host_header_in_response, true)
 // If true, QUIC BBR2 will ignore non-positive RTT samples.
 QUIC_FLAG(quic_reloadable_flag_quic_bbr2_ignore_bad_rtt_sample, false)
 // If true, QUIC server will not respond to gQUIC probing packet(PING + PADDING) but treat it as a regular packet.