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