Plumb 103 responses from QuicHttpStream

This CL adds 1xx responses support to QuicChromiumClientStream, mainly
to plumb 103 Early Hints.

The basic approach is similar to quic::QuicSpdyClientStream. When
OnInitialHeadersComplete() is called, QuicChromiumClientStream parses
the ":status" pseudo-header then perform the followings:
* If status is invalid, reset the stream.
* Otherwise, if status is non-informational (>=200), treat the header as
  the final response.
* Otherwise, if status == 101, reset the stream.
* Otherwise, if status == 103, notify the corresponding handle that an
  Early Hints is available.
* Otherwise, ignore the informational response.

The handle (QuicChroimiumClientStream::Handle) delivers Early Hints to
the owner via ReadInitialHeaders(). In production code, the owner of the
handle is QuicHttpStream, which is used by HttpNetworkTransaction.

The reason the handle doesn't provide a separate method like
ReadEarlyHints() is that HttpNetworkTransaction handles Early Hints in
DoREadHeadersComplete(), which indirectly calls
QuicChromiumClientStream::Handle::ReadInitialHeaders().

Bug: 1096414

Change-Id: I1ba0c8d5f2fba39ee691d05e0363972ae9428bc0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2759798
Commit-Queue: Kenichi Ishibashi <[email protected]>
Reviewed-by: Bence Béky <[email protected]>
Cr-Commit-Position: refs/heads/master@{#864173}
diff --git a/net/quic/quic_chromium_client_stream.cc b/net/quic/quic_chromium_client_stream.cc
index 9675697..ad86cd3 100644
--- a/net/quic/quic_chromium_client_stream.cc
+++ b/net/quic/quic_chromium_client_stream.cc
@@ -15,6 +15,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
 #include "net/log/net_log_event_type.h"
 #include "net/quic/quic_chromium_client_session.h"
 #include "net/quic/quic_http_utils.h"
@@ -61,6 +62,17 @@
   }
 }
 
+void QuicChromiumClientStream::Handle::OnEarlyHintsAvailable() {
+  if (!read_headers_callback_)
+    return;  // Wait for ReadInitialHeaders to be called.
+
+  DCHECK(read_headers_buffer_);
+  int rv = stream_->DeliverEarlyHints(read_headers_buffer_);
+  DCHECK_NE(ERR_IO_PENDING, rv);
+
+  ResetAndRun(std::move(read_headers_callback_), rv);
+}
+
 void QuicChromiumClientStream::Handle::OnInitialHeadersAvailable() {
   if (!read_headers_callback_)
     return;  // Wait for ReadInitialHeaders to be called.
@@ -160,12 +172,19 @@
   if (!stream_)
     return net_error_;
 
-  int rv = stream_->DeliverInitialHeaders(header_block);
+  // Check Early Hints first.
+  int rv = stream_->DeliverEarlyHints(header_block);
+  if (rv != ERR_IO_PENDING) {
+    return rv;
+  }
+
+  rv = stream_->DeliverInitialHeaders(header_block);
   if (rv != ERR_IO_PENDING) {
     return rv;
   }
 
   read_headers_buffer_ = header_block;
+  DCHECK(!read_headers_callback_);
   SetCallback(std::move(callback), &read_headers_callback_);
   return ERR_IO_PENDING;
 }
@@ -463,6 +482,7 @@
     bool fin,
     size_t frame_len,
     const quic::QuicHeaderList& header_list) {
+  DCHECK(!initial_headers_arrived_);
   quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
 
   spdy::Http2HeaderBlock header_block;
@@ -475,6 +495,43 @@
     return;
   }
 
+  // Handle informational response. If the response is an Early Hints response,
+  // deliver the response to the owner of the handle. Otherwise ignore the
+  // response.
+  // TODO(crbug.com/1096414): Fix tests and treat an empty list as an error.
+  // Some tests fail when we treat an empty header list as error. All valid
+  // responses must have the ":status" field.
+  if (!header_list.empty()) {
+    int response_code;
+    if (!ParseHeaderStatusCode(header_block, &response_code)) {
+      DLOG(ERROR) << "Received invalid response code: '"
+                  << header_block[":status"].as_string() << "' on stream "
+                  << id();
+      Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD);
+      return;
+    }
+
+    if (response_code == HTTP_SWITCHING_PROTOCOLS) {
+      DLOG(ERROR) << "Received forbidden 101 response code on stream " << id();
+      Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD);
+      return;
+    }
+
+    if (response_code >= 100 && response_code < 200) {
+      set_headers_decompressed(false);
+      ConsumeHeaderList();
+      if (response_code == HTTP_EARLY_HINTS) {
+        early_hints_.emplace_back(std::move(header_block), frame_len);
+        if (handle_)
+          handle_->OnEarlyHintsAvailable();
+      } else {
+        DVLOG(1) << "Ignore informational response " << response_code
+                 << " on stream" << id();
+      }
+      return;
+    }
+  }
+
   ConsumeHeaderList();
   session_->OnInitialHeadersComplete(id(), header_block);
 
@@ -679,6 +736,30 @@
   handle_->OnTrailingHeadersAvailable();
 }
 
+int QuicChromiumClientStream::DeliverEarlyHints(
+    spdy::Http2HeaderBlock* headers) {
+  if (early_hints_.empty()) {
+    return ERR_IO_PENDING;
+  }
+
+  DCHECK(!headers_delivered_);
+
+  EarlyHints& hints = early_hints_.front();
+  *headers = std::move(hints.headers);
+  size_t frame_len = hints.frame_len;
+  early_hints_.pop_front();
+
+  net_log_.AddEvent(
+      NetLogEventType::
+          QUIC_CHROMIUM_CLIENT_STREAM_READ_EARLY_HINTS_RESPONSE_HEADERS,
+      [&](NetLogCaptureMode capture_mode) {
+        return QuicResponseNetLogParams(id(), fin_received(), headers,
+                                        capture_mode);
+      });
+
+  return frame_len;
+}
+
 int QuicChromiumClientStream::DeliverInitialHeaders(
     spdy::Http2HeaderBlock* headers) {
   if (!initial_headers_arrived_) {