Rename QuicSpdyStream::AreHeadersValid() to ValidatedRequestHeaders() and retain error details in the stream if it returns false.

PiperOrigin-RevId: 532920884
diff --git a/quiche/quic/core/http/quic_spdy_client_stream.cc b/quiche/quic/core/http/quic_spdy_client_stream.cc
index 2f6eb8d..9ac5897 100644
--- a/quiche/quic/core/http/quic_spdy_client_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_client_stream.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "quiche/quic/core/http/quic_client_promised_info.h"
 #include "quiche/quic/core/http/quic_spdy_client_session.h"
@@ -195,9 +196,9 @@
   return bytes_sent;
 }
 
-bool QuicSpdyClientStream::AreHeadersValid(
-    const QuicHeaderList& header_list) const {
-  if (!QuicSpdyStream::AreHeadersValid(header_list)) {
+bool QuicSpdyClientStream::ValidatedRequestHeaders(
+    const QuicHeaderList& header_list) {
+  if (!QuicSpdyStream::ValidatedRequestHeaders(header_list)) {
     return false;
   }
   // Verify the presence of :status header.
@@ -206,10 +207,17 @@
     if (pair.first == ":status") {
       saw_status = true;
     } else if (absl::StrContains(pair.first, ":")) {
-      QUIC_DLOG(ERROR) << "Unexpected ':' in header " << pair.first << ".";
+      set_invalid_request_details(
+          absl::StrCat("Unexpected ':' in header ", pair.first, "."));
+      QUIC_DLOG(ERROR) << invalid_request_details();
       return false;
     }
   }
+  if (!saw_status) {
+    set_invalid_request_details("Missing :status in response header.");
+    QUIC_DLOG(ERROR) << invalid_request_details();
+    return false;
+  }
   return saw_status;
 }
 
diff --git a/quiche/quic/core/http/quic_spdy_client_stream.h b/quiche/quic/core/http/quic_spdy_client_stream.h
index 02956c6..35446b0 100644
--- a/quiche/quic/core/http/quic_spdy_client_stream.h
+++ b/quiche/quic/core/http/quic_spdy_client_stream.h
@@ -72,7 +72,7 @@
   using QuicSpdyStream::SetPriority;
 
  protected:
-  bool AreHeadersValid(const QuicHeaderList& header_list) const override;
+  bool ValidatedRequestHeaders(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_server_stream_base.cc b/quiche/quic/core/http/quic_spdy_server_stream_base.cc
index 9a23c26..6176032 100644
--- a/quiche/quic/core/http/quic_spdy_server_stream_base.cc
+++ b/quiche/quic/core/http/quic_spdy_server_stream_base.cc
@@ -4,6 +4,7 @@
 
 #include "quiche/quic/core/http/quic_spdy_server_stream_base.h"
 
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "quiche/quic/core/http/quic_spdy_session.h"
 #include "quiche/quic/core/quic_error_codes.h"
@@ -48,9 +49,9 @@
   QuicSpdyStream::StopReading();
 }
 
-bool QuicSpdyServerStreamBase::AreHeadersValid(
-    const QuicHeaderList& header_list) const {
-  if (!QuicSpdyStream::AreHeadersValid(header_list)) {
+bool QuicSpdyServerStreamBase::ValidatedRequestHeaders(
+    const QuicHeaderList& header_list) {
+  if (!QuicSpdyStream::ValidatedRequestHeaders(header_list)) {
     return false;
   }
 
@@ -84,17 +85,22 @@
     } else if (pair.first == ":authority") {
       saw_authority = true;
     } else if (absl::StrContains(pair.first, ":")) {
-      QUIC_DLOG(ERROR) << "Unexpected ':' in header " << pair.first << ".";
+      set_invalid_request_details(
+          absl::StrCat("Unexpected ':' in header ", pair.first, "."));
+      QUIC_DLOG(ERROR) << invalid_request_details();
       return false;
     }
     if (is_extended_connect) {
       if (!spdy_session()->allow_extended_connect()) {
-        QUIC_DLOG(ERROR)
-            << "Received extended-CONNECT request while it is disabled.";
+        set_invalid_request_details(
+            "Received extended-CONNECT request while it is disabled.");
+        QUIC_DLOG(ERROR) << invalid_request_details();
         return false;
       }
     } else if (saw_method && !saw_connect) {
       if (saw_protocol) {
+        set_invalid_request_details(
+            "Received non-CONNECT request with :protocol header.");
         QUIC_DLOG(ERROR) << "Receive non-CONNECT request with :protocol.";
         return false;
       }
@@ -106,15 +112,18 @@
       // Saw all the required pseudo headers.
       return true;
     }
-    QUIC_DLOG(ERROR) << "Missing required pseudo headers for extended-CONNECT.";
+    set_invalid_request_details(
+        "Missing required pseudo headers for extended-CONNECT.");
+    QUIC_DLOG(ERROR) << invalid_request_details();
     return false;
   }
   // This is a vanilla CONNECT or non-CONNECT request.
   if (saw_connect) {
     // Check vanilla CONNECT.
     if (saw_path || saw_scheme) {
-      QUIC_DLOG(ERROR)
-          << "Received invalid CONNECT request with disallowed pseudo header.";
+      set_invalid_request_details(
+          "Received invalid CONNECT request with disallowed pseudo header.");
+      QUIC_DLOG(ERROR) << invalid_request_details();
       return false;
     }
     return true;
@@ -123,7 +132,8 @@
   if (saw_method && saw_authority && saw_path && saw_scheme) {
     return true;
   }
-  QUIC_LOG(ERROR) << "Missing required pseudo headers.";
+  set_invalid_request_details("Missing required pseudo headers.");
+  QUIC_LOG(ERROR) << invalid_request_details();
   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 dd3423b..24133d5 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 AreHeadersValid(const QuicHeaderList& header_list) const override;
+  bool ValidatedRequestHeaders(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 86c4712..e778864 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -257,7 +257,7 @@
   MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override));
 
  protected:
-  bool AreHeadersValid(const QuicHeaderList& /*header_list*/) const override {
+  bool ValidatedRequestHeaders(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 8f8a5b1..8727363 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -622,8 +622,11 @@
   }
   // Validate request headers if it did not exceed size limit. If it did,
   // OnHeadersTooLarge() should have already handled it previously.
-  if (!header_too_large && !AreHeadersValid(header_list)) {
+  if (!header_too_large && !ValidatedRequestHeaders(header_list)) {
     QUIC_CODE_COUNT_N(quic_validate_request_header, 1, 2);
+    QUICHE_DCHECK(!invalid_request_details().empty())
+        << "ValidatedRequestHeaders() returns false without populating "
+           "invalid_request_details_";
     if (GetQuicReloadableFlag(quic_act_upon_invalid_header)) {
       QUIC_RELOADABLE_FLAG_COUNT(quic_act_upon_invalid_header);
       OnInvalidHeaders();
@@ -1626,21 +1629,32 @@
 }
 }  // namespace
 
-bool QuicSpdyStream::AreHeadersValid(const QuicHeaderList& header_list) const {
+bool QuicSpdyStream::ValidatedRequestHeaders(
+    const QuicHeaderList& header_list) {
   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;
+      invalid_request_details_ = absl::StrCat("Invalid request header ", name);
+      QUIC_DLOG(ERROR) << invalid_request_details_;
       return false;
     }
     if (http2::GetInvalidHttp2HeaderSet().contains(name)) {
-      QUIC_DLOG(ERROR) << name << " header is not allowed";
+      invalid_request_details_ = absl::StrCat(name, " header is not allowed");
+      QUIC_DLOG(ERROR) << invalid_request_details_;
       return false;
     }
   }
   return true;
 }
 
+void QuicSpdyStream::set_invalid_request_details(
+    std::string invalid_request_details) {
+  QUIC_BUG_IF(
+      empty invalid request detail,
+      !invalid_request_details_.empty() || invalid_request_details.empty());
+  invalid_request_details_ = std::move(invalid_request_details);
+}
+
 bool QuicSpdyStream::AreHeaderFieldValuesValid(
     const QuicHeaderList& header_list) const {
   if (!VersionUsesHttp3(transport_version())) {
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h
index 301df24..98b1a0e 100644
--- a/quiche/quic/core/http/quic_spdy_stream.h
+++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -328,6 +328,10 @@
 
   void WriteGreaseCapsule();
 
+  const std::string& invalid_request_details() const {
+    return invalid_request_details_;
+  }
+
  protected:
   // Called when the received headers are too large. By default this will
   // reset the stream.
@@ -359,15 +363,18 @@
 
   void OnWriteSideInDataRecvdState() override;
 
-  virtual bool AreHeadersValid(const QuicHeaderList& header_list) const;
-  // TODO(b/202433856) Merge AreHeaderFieldValueValid into AreHeadersValid once
-  // all flags guarding the behavior of AreHeadersValid has been rolled out.
+  virtual bool ValidatedRequestHeaders(const QuicHeaderList& header_list);
+  // TODO(b/202433856) Merge AreHeaderFieldValueValid into
+  // ValidatedRequestHeaders once all flags guarding the behavior of
+  // ValidatedRequestHeaders has been rolled out.
   virtual bool AreHeaderFieldValuesValid(
       const QuicHeaderList& header_list) const;
 
   // Reset stream upon invalid request headers.
   virtual void OnInvalidHeaders();
 
+  void set_invalid_request_details(std::string invalid_request_details);
+
  private:
   friend class test::QuicSpdyStreamPeer;
   friend class test::QuicStreamPeer;
@@ -493,6 +500,9 @@
   Http3DatagramVisitor* datagram_visitor_ = nullptr;
   // CONNECT-IP support.
   ConnectIpVisitor* connect_ip_visitor_ = nullptr;
+
+  // Empty if the headers are valid.
+  std::string invalid_request_details_;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index e7d68cd..400eeff 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -276,10 +276,6 @@
 
   size_t headers_payload_length() const { return headers_payload_length_; }
 
-  bool AreHeadersValid(const QuicHeaderList& header_list) const override {
-    return QuicSpdyStream::AreHeadersValid(header_list);
-  }
-
  private:
   bool should_process_data_;
   spdy::Http2HeaderBlock saved_headers_;