Reject invalid :path values in oghttp2.

This CL aligns oghttp2 more closely with the HTTP/2 spec WRT :path validation:
https://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3

In particular, the :path request pseudoheader must either begin with '/' or can
be '*' for OPTIONS requests (note that oghttp2 performs this validation
regardless of scheme).

Http2RequestsTest.InvalidPathPseudoheaderExternalIp with oghttp2:
http://sponge2/124d5146-d810-4416-8be9-db296b104a87

PiperOrigin-RevId: 424095088
diff --git a/http2/adapter/header_validator.cc b/http2/adapter/header_validator.cc
index 4111652..d018991 100644
--- a/http2/adapter/header_validator.cc
+++ b/http2/adapter/header_validator.cc
@@ -46,17 +46,30 @@
 }
 
 bool ValidateRequestHeaders(const std::vector<std::string>& pseudo_headers,
-                            absl::string_view method, bool allow_connect) {
+                            absl::string_view method, absl::string_view path,
+                            bool allow_connect) {
   QUICHE_VLOG(2) << "Request pseudo-headers: ["
                  << absl::StrJoin(pseudo_headers, ", ")
                  << "], allow_connect: " << allow_connect
-                 << ", method: " << method;
+                 << ", method: " << method << ", path: " << path;
   if (allow_connect && method == "CONNECT") {
     static const std::vector<std::string>* kConnectHeaders =
         new std::vector<std::string>(
             {":authority", ":method", ":path", ":protocol", ":scheme"});
     return pseudo_headers == *kConnectHeaders;
   }
+
+  if (path.empty()) {
+    return false;
+  }
+  if (path == "*") {
+    if (method != "OPTIONS") {
+      return false;
+    }
+  } else if (path[0] != '/') {
+    return false;
+  }
+
   static const std::vector<std::string>* kRequiredHeaders =
       new std::vector<std::string>(
           {":authority", ":method", ":path", ":scheme"});
@@ -83,6 +96,7 @@
   pseudo_headers_.clear();
   status_.clear();
   method_.clear();
+  path_.clear();
   content_length_.reset();
 }
 
@@ -128,6 +142,12 @@
       method_ = std::string(value);
     } else if (key == ":authority" && !IsValidAuthority(value)) {
       return HEADER_FIELD_INVALID;
+    } else if (key == ":path") {
+      if (value.empty()) {
+        // For now, reject an empty path regardless of scheme.
+        return HEADER_FIELD_INVALID;
+      }
+      path_ = std::string(value);
     }
     pseudo_headers_.push_back(std::string(key));
   } else if (key == "content-length") {
@@ -145,7 +165,8 @@
   std::sort(pseudo_headers_.begin(), pseudo_headers_.end());
   switch (type) {
     case HeaderType::REQUEST:
-      return ValidateRequestHeaders(pseudo_headers_, method_, allow_connect_);
+      return ValidateRequestHeaders(pseudo_headers_, method_, path_,
+                                    allow_connect_);
     case HeaderType::REQUEST_TRAILER:
       return ValidateRequestTrailers(pseudo_headers_);
     case HeaderType::RESPONSE_100:
diff --git a/http2/adapter/header_validator.h b/http2/adapter/header_validator.h
index ddcabdf..30f16d5 100644
--- a/http2/adapter/header_validator.h
+++ b/http2/adapter/header_validator.h
@@ -54,6 +54,7 @@
   std::vector<std::string> pseudo_headers_;
   std::string status_;
   std::string method_;
+  std::string path_;
   absl::optional<size_t> max_field_size_;
   absl::optional<size_t> content_length_;
   bool allow_connect_ = false;
diff --git a/http2/adapter/header_validator_test.cc b/http2/adapter/header_validator_test.cc
index ba6d06a..c76a770 100644
--- a/http2/adapter/header_validator_test.cc
+++ b/http2/adapter/header_validator_test.cc
@@ -10,6 +10,12 @@
 
 using ::testing::Optional;
 
+using Header = std::pair<absl::string_view, absl::string_view>;
+constexpr Header kSampleRequestPseudoheaders[] = {{":authority", "www.foo.com"},
+                                                  {":method", "GET"},
+                                                  {":path", "/foo"},
+                                                  {":scheme", "https"}};
+
 TEST(HeaderValidatorTest, HeaderNameEmpty) {
   HeaderValidator v;
   HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("", "value");
@@ -145,14 +151,12 @@
 
 TEST(HeaderValidatorTest, RequestPseudoHeaders) {
   HeaderValidator v;
-  const absl::string_view headers[] = {":authority", ":method", ":path",
-                                       ":scheme"};
-  for (absl::string_view to_skip : headers) {
+  for (Header to_skip : kSampleRequestPseudoheaders) {
     v.StartHeaderBlock();
-    for (absl::string_view to_add : headers) {
+    for (Header to_add : kSampleRequestPseudoheaders) {
       if (to_add != to_skip) {
         EXPECT_EQ(HeaderValidator::HEADER_OK,
-                  v.ValidateSingleHeader(to_add, "foo"));
+                  v.ValidateSingleHeader(to_add.first, to_add.second));
       }
     }
     // When any pseudo-header is missing, final validation will fail.
@@ -161,31 +165,31 @@
 
   // When all pseudo-headers are present, final validation will succeed.
   v.StartHeaderBlock();
-  for (absl::string_view to_add : headers) {
+  for (Header to_add : kSampleRequestPseudoheaders) {
     EXPECT_EQ(HeaderValidator::HEADER_OK,
-              v.ValidateSingleHeader(to_add, "foo"));
+              v.ValidateSingleHeader(to_add.first, to_add.second));
   }
   EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST));
 
   // When an extra pseudo-header is present, final validation will fail.
   v.StartHeaderBlock();
-  for (absl::string_view to_add : headers) {
+  for (Header to_add : kSampleRequestPseudoheaders) {
     EXPECT_EQ(HeaderValidator::HEADER_OK,
-              v.ValidateSingleHeader(to_add, "foo"));
+              v.ValidateSingleHeader(to_add.first, to_add.second));
   }
   EXPECT_EQ(HeaderValidator::HEADER_OK,
             v.ValidateSingleHeader(":extra", "blah"));
   EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
 
   // When a required pseudo-header is repeated, final validation will fail.
-  for (absl::string_view to_repeat : headers) {
+  for (Header to_repeat : kSampleRequestPseudoheaders) {
     v.StartHeaderBlock();
-    for (absl::string_view to_add : headers) {
+    for (Header to_add : kSampleRequestPseudoheaders) {
       EXPECT_EQ(HeaderValidator::HEADER_OK,
-                v.ValidateSingleHeader(to_add, "foo"));
+                v.ValidateSingleHeader(to_add.first, to_add.second));
       if (to_add == to_repeat) {
         EXPECT_EQ(HeaderValidator::HEADER_OK,
-                  v.ValidateSingleHeader(to_add, "foo"));
+                  v.ValidateSingleHeader(to_add.first, to_add.second));
       }
     }
     EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
@@ -194,12 +198,10 @@
 
 TEST(HeaderValidatorTest, WebsocketPseudoHeaders) {
   HeaderValidator v;
-  const absl::string_view headers[] = {":authority", ":method", ":path",
-                                       ":scheme"};
   v.StartHeaderBlock();
-  for (absl::string_view to_add : headers) {
+  for (Header to_add : kSampleRequestPseudoheaders) {
     EXPECT_EQ(HeaderValidator::HEADER_OK,
-              v.ValidateSingleHeader(to_add, "foo"));
+              v.ValidateSingleHeader(to_add.first, to_add.second));
   }
   EXPECT_EQ(HeaderValidator::HEADER_OK,
             v.ValidateSingleHeader(":protocol", "websocket"));
@@ -211,24 +213,24 @@
   v.AllowConnect();
 
   v.StartHeaderBlock();
-  for (absl::string_view to_add : headers) {
+  for (Header to_add : kSampleRequestPseudoheaders) {
     EXPECT_EQ(HeaderValidator::HEADER_OK,
-              v.ValidateSingleHeader(to_add, "foo"));
+              v.ValidateSingleHeader(to_add.first, to_add.second));
   }
   EXPECT_EQ(HeaderValidator::HEADER_OK,
             v.ValidateSingleHeader(":protocol", "websocket"));
-  // The method is "foo", not "CONNECT", so `:protocol` is still treated as an
-  // extra pseudo-header.
+  // The method is not "CONNECT", so `:protocol` is still treated as an extra
+  // pseudo-header.
   EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
 
   v.StartHeaderBlock();
-  for (absl::string_view to_add : headers) {
-    if (to_add == ":method") {
+  for (Header to_add : kSampleRequestPseudoheaders) {
+    if (to_add.first == ":method") {
       EXPECT_EQ(HeaderValidator::HEADER_OK,
-                v.ValidateSingleHeader(to_add, "CONNECT"));
+                v.ValidateSingleHeader(to_add.first, "CONNECT"));
     } else {
       EXPECT_EQ(HeaderValidator::HEADER_OK,
-                v.ValidateSingleHeader(to_add, "foo"));
+                v.ValidateSingleHeader(to_add.first, to_add.second));
     }
   }
   EXPECT_EQ(HeaderValidator::HEADER_OK,
@@ -237,6 +239,69 @@
   EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST));
 }
 
+TEST(HeaderValidatorTest, AsteriskPathPseudoHeader) {
+  HeaderValidator v;
+
+  // An asterisk :path should not be allowed for non-OPTIONS requests.
+  v.StartHeaderBlock();
+  for (Header to_add : kSampleRequestPseudoheaders) {
+    if (to_add.first == ":path") {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, "*"));
+    } else {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, to_add.second));
+    }
+  }
+  EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+
+  // An asterisk :path should be allowed for OPTIONS requests.
+  v.StartHeaderBlock();
+  for (Header to_add : kSampleRequestPseudoheaders) {
+    if (to_add.first == ":path") {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, "*"));
+    } else if (to_add.first == ":method") {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, "OPTIONS"));
+    } else {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, to_add.second));
+    }
+  }
+  EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST));
+}
+
+TEST(HeaderValidatorTest, InvalidPathPseudoHeader) {
+  HeaderValidator v;
+
+  // An empty path should fail on single header validation and finish.
+  v.StartHeaderBlock();
+  for (Header to_add : kSampleRequestPseudoheaders) {
+    if (to_add.first == ":path") {
+      EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID,
+                v.ValidateSingleHeader(to_add.first, ""));
+    } else {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, to_add.second));
+    }
+  }
+  EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+
+  // A path that does not start with a slash should fail on finish.
+  v.StartHeaderBlock();
+  for (Header to_add : kSampleRequestPseudoheaders) {
+    if (to_add.first == ":path") {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, "shawarma"));
+    } else {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add.first, to_add.second));
+    }
+  }
+  EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+}
+
 TEST(HeaderValidatorTest, ResponsePseudoHeaders) {
   HeaderValidator v;
 
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index b16241b..dbab8c6 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -4910,6 +4910,134 @@
                                             spdy::SpdyFrameType::RST_STREAM}));
 }
 
+TEST(NgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) {
+  DataSavingVisitor visitor;
+  auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+  testing::InSequence s;
+
+  const std::string stream_frames = TestFrameSequence()
+                                        .ClientPreface()
+                                        .Headers(1,
+                                                 {{":scheme", "https"},
+                                                  {":authority", "example.com"},
+                                                  {":path", "*"},
+                                                  {":method", "OPTIONS"}},
+                                                 /*fin=*/true)
+                                        .Serialize();
+
+  // Client preface (empty SETTINGS)
+  EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+  EXPECT_CALL(visitor, OnSettingsStart());
+  EXPECT_CALL(visitor, OnSettingsEnd());
+
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+  EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+  EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+  EXPECT_CALL(visitor, OnEndStream(1));
+
+  const int64_t stream_result = adapter->ProcessBytes(stream_frames);
+  EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+  EXPECT_TRUE(adapter->want_write());
+  int result = adapter->Send();
+  EXPECT_EQ(0, result);
+  EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(NgHttp2AdapterTest, ServerHandlesInvalidPath) {
+  DataSavingVisitor visitor;
+  auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+  testing::InSequence s;
+
+  const std::string stream_frames =
+      TestFrameSequence()
+          .ClientPreface()
+          .Headers(1,
+                   {{":scheme", "https"},
+                    {":authority", "example.com"},
+                    {":path", "*"},
+                    {":method", "GET"}},
+                   /*fin=*/true)
+          .Headers(3,
+                   {{":scheme", "https"},
+                    {":authority", "example.com"},
+                    {":path", "other/non/slash/starter"},
+                    {":method", "GET"}},
+                   /*fin=*/true)
+          .Headers(5,
+                   {{":scheme", "https"},
+                    {":authority", "example.com"},
+                    {":path", ""},
+                    {":method", "GET"}},
+                   /*fin=*/true)
+          .Serialize();
+
+  // Client preface (empty SETTINGS)
+  EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+  EXPECT_CALL(visitor, OnSettingsStart());
+  EXPECT_CALL(visitor, OnSettingsEnd());
+
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+  EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+  EXPECT_CALL(visitor,
+              OnInvalidFrame(
+                  1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging));
+
+  EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(3));
+  EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4);
+  EXPECT_CALL(visitor,
+              OnInvalidFrame(
+                  3, Http2VisitorInterface::InvalidFrameError::kHttpMessaging));
+
+  EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(5));
+  EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(2);
+  EXPECT_CALL(
+      visitor,
+      OnErrorDebug("Invalid HTTP header field was received: frame type: 1, "
+                   "stream: 5, name: [:path], value: []"));
+  EXPECT_CALL(
+      visitor,
+      OnInvalidFrame(5, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
+
+  const int64_t stream_result = adapter->ProcessBytes(stream_frames);
+  EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0));
+  EXPECT_CALL(visitor,
+              OnFrameSent(RST_STREAM, 1, _, 0x0,
+                          static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0));
+  EXPECT_CALL(visitor,
+              OnFrameSent(RST_STREAM, 3, _, 0x0,
+                          static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+  EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0));
+  EXPECT_CALL(visitor,
+              OnFrameSent(RST_STREAM, 5, _, 0x0,
+                          static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+  EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::PROTOCOL_ERROR));
+
+  EXPECT_TRUE(adapter->want_write());
+  int result = adapter->Send();
+  EXPECT_EQ(0, result);
+  EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+                                            spdy::SpdyFrameType::RST_STREAM,
+                                            spdy::SpdyFrameType::RST_STREAM,
+                                            spdy::SpdyFrameType::RST_STREAM}));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace adapter
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 3596ce0..672f6fb 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -5103,6 +5103,138 @@
            spdy::SpdyFrameType::RST_STREAM, spdy::SpdyFrameType::RST_STREAM}));
 }
 
+TEST(NgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) {
+  DataSavingVisitor visitor;
+  OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+  auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+  testing::InSequence s;
+
+  const std::string stream_frames = TestFrameSequence()
+                                        .ClientPreface()
+                                        .Headers(1,
+                                                 {{":scheme", "https"},
+                                                  {":authority", "example.com"},
+                                                  {":path", "*"},
+                                                  {":method", "OPTIONS"}},
+                                                 /*fin=*/true)
+                                        .Serialize();
+
+  // Client preface (empty SETTINGS)
+  EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+  EXPECT_CALL(visitor, OnSettingsStart());
+  EXPECT_CALL(visitor, OnSettingsEnd());
+
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+  EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+  EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+  EXPECT_CALL(visitor, OnEndStream(1));
+
+  const int64_t stream_result = adapter->ProcessBytes(stream_frames);
+  EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+  EXPECT_TRUE(adapter->want_write());
+  int result = adapter->Send();
+  EXPECT_EQ(0, result);
+  EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+                                            spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(NgHttp2AdapterTest, ServerHandlesInvalidPath) {
+  DataSavingVisitor visitor;
+  OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+  auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+  testing::InSequence s;
+
+  const std::string stream_frames =
+      TestFrameSequence()
+          .ClientPreface()
+          .Headers(1,
+                   {{":scheme", "https"},
+                    {":authority", "example.com"},
+                    {":path", "*"},
+                    {":method", "GET"}},
+                   /*fin=*/true)
+          .Headers(3,
+                   {{":scheme", "https"},
+                    {":authority", "example.com"},
+                    {":path", "other/non/slash/starter"},
+                    {":method", "GET"}},
+                   /*fin=*/true)
+          .Headers(5,
+                   {{":scheme", "https"},
+                    {":authority", "example.com"},
+                    {":path", ""},
+                    {":method", "GET"}},
+                   /*fin=*/true)
+          .Serialize();
+
+  // Client preface (empty SETTINGS)
+  EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+  EXPECT_CALL(visitor, OnSettingsStart());
+  EXPECT_CALL(visitor, OnSettingsEnd());
+
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+  EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+  EXPECT_CALL(visitor,
+              OnInvalidFrame(
+                  1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging));
+
+  EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(3));
+  EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4);
+  EXPECT_CALL(visitor,
+              OnInvalidFrame(
+                  3, Http2VisitorInterface::InvalidFrameError::kHttpMessaging));
+
+  EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(5));
+  EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(2);
+  EXPECT_CALL(
+      visitor,
+      OnInvalidFrame(5, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
+
+  const int64_t stream_result = adapter->ProcessBytes(stream_frames);
+  EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0));
+  EXPECT_CALL(visitor,
+              OnFrameSent(RST_STREAM, 1, _, 0x0,
+                          static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0));
+  EXPECT_CALL(visitor,
+              OnFrameSent(RST_STREAM, 3, _, 0x0,
+                          static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+  EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::HTTP2_NO_ERROR));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0));
+  EXPECT_CALL(visitor,
+              OnFrameSent(RST_STREAM, 5, _, 0x0,
+                          static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+  EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR));
+
+  EXPECT_TRUE(adapter->want_write());
+  int result = adapter->Send();
+  EXPECT_EQ(0, result);
+  EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+                                            spdy::SpdyFrameType::SETTINGS,
+                                            spdy::SpdyFrameType::RST_STREAM,
+                                            spdy::SpdyFrameType::RST_STREAM,
+                                            spdy::SpdyFrameType::RST_STREAM}));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace adapter