Properly ignore HTTP Infomational 1XX status codes in QUIC

Our code already handled status code 100 correctly, but incorrectly treated 101-199 as the final response. This CL ensures that we parse 100 as before, but properly ignore any other 1XX header, except 101 which is disallowed.

This issue was reported as <https://crbug.com/1096414>.

Client-only

PiperOrigin-RevId: 317169918
Change-Id: I199780f1d6862c3e5b8ff74b2135ecf7c1cc638e
diff --git a/quic/core/http/quic_spdy_client_stream.cc b/quic/core/http/quic_spdy_client_stream.cc
index 7232f76..f50e756 100644
--- a/quic/core/http/quic_spdy_client_stream.cc
+++ b/quic/core/http/quic_spdy_client_stream.cc
@@ -53,24 +53,41 @@
   if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
                                          &response_headers_)) {
     QUIC_DLOG(ERROR) << "Failed to parse header list: "
-                     << header_list.DebugString();
+                     << header_list.DebugString() << " on stream " << id();
     Reset(QUIC_BAD_APPLICATION_PAYLOAD);
     return;
   }
 
   if (!ParseHeaderStatusCode(response_headers_, &response_code_)) {
     QUIC_DLOG(ERROR) << "Received invalid response code: "
-                     << response_headers_[":status"].as_string();
+                     << response_headers_[":status"].as_string()
+                     << " on stream " << id();
     Reset(QUIC_BAD_APPLICATION_PAYLOAD);
     return;
   }
 
-  if (response_code_ == 100 && !has_preliminary_headers_) {
-    // These are preliminary 100 Continue headers, not the actual response
-    // headers.
+  if (response_code_ == 101) {
+    // 101 "Switching Protocols" is forbidden in HTTP/3 as per the
+    // "HTTP Upgrade" section of draft-ietf-quic-http.
+    QUIC_DLOG(ERROR) << "Received forbidden 101 response code"
+                     << " on stream " << id();
+    Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+    return;
+  }
+
+  if (response_code_ >= 100 && response_code_ < 200) {
+    // These are Informational 1xx headers, not the actual response headers.
+    QUIC_DLOG(INFO) << "Received informational response code: "
+                    << response_headers_[":status"].as_string() << " on stream "
+                    << id();
     set_headers_decompressed(false);
-    has_preliminary_headers_ = true;
-    preliminary_headers_ = std::move(response_headers_);
+    if (response_code_ == 100 && !has_preliminary_headers_) {
+      // This is 100 Continue, save it to enable "Expect: 100-continue".
+      has_preliminary_headers_ = true;
+      preliminary_headers_ = std::move(response_headers_);
+    } else {
+      response_headers_.clear();
+    }
   }
 
   ConsumeHeaderList();
diff --git a/quic/core/http/quic_spdy_client_stream_test.cc b/quic/core/http/quic_spdy_client_stream_test.cc
index fdc0e29..1aba000 100644
--- a/quic/core/http/quic_spdy_client_stream_test.cc
+++ b/quic/core/http/quic_spdy_client_stream_test.cc
@@ -133,17 +133,77 @@
   EXPECT_EQ(body_, stream_->data());
 }
 
-TEST_P(QuicSpdyClientStreamTest, TestFraming100Continue) {
+TEST_P(QuicSpdyClientStreamTest, Test100ContinueBeforeSuccessful) {
+  // First send 100 Continue.
   headers_[":status"] = "100";
   auto headers = AsHeaderList(headers_);
   stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
                               headers);
-  stream_->OnStreamFrame(
-      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, body_));
   EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
   EXPECT_EQ(0u, stream_->response_headers().size());
   EXPECT_EQ(100, stream_->response_code());
   EXPECT_EQ("", stream_->data());
+  // Then send 200 OK.
+  headers_[":status"] = "200";
+  headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer);
+  std::string header = std::string(buffer.get(), header_length);
+  std::string data =
+      connection_->version().UsesHttp3() ? header + body_ : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  // Make sure the 200 response got parsed correctly.
+  EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+  EXPECT_EQ(200, stream_->response_code());
+  EXPECT_EQ(body_, stream_->data());
+  // Make sure the 100 response is still available.
+  EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestUnknownInformationalBeforeSuccessful) {
+  // First send 199, an unknown Informational (1XX).
+  headers_[":status"] = "199";
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  EXPECT_EQ(0u, stream_->response_headers().size());
+  EXPECT_EQ(199, stream_->response_code());
+  EXPECT_EQ("", stream_->data());
+  // Then send 200 OK.
+  headers_[":status"] = "200";
+  headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer);
+  std::string header = std::string(buffer.get(), header_length);
+  std::string data =
+      connection_->version().UsesHttp3() ? header + body_ : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  // Make sure the 200 response got parsed correctly.
+  EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+  EXPECT_EQ(200, stream_->response_code());
+  EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestReceiving101) {
+  // 101 "Switching Protocols" is forbidden in HTTP/3 as per the
+  // "HTTP Upgrade" section of draft-ietf-quic-http.
+  headers_[":status"] = "101";
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  EXPECT_THAT(stream_->stream_error(),
+              IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
 }
 
 TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) {