Adds unit test cases covering the case where a content-length overshoot causes a PROTOCOL_ERROR.
Both nghttp2 and oghttp2 appear to treat this case the same.
PiperOrigin-RevId: 700101380
diff --git a/quiche/http2/adapter/nghttp2_adapter_test.cc b/quiche/http2/adapter/nghttp2_adapter_test.cc
index 0674441..99cab9e 100644
--- a/quiche/http2/adapter/nghttp2_adapter_test.cc
+++ b/quiche/http2/adapter/nghttp2_adapter_test.cc
@@ -7502,6 +7502,94 @@
SpdyFrameType::RST_STREAM}));
}
+TEST(NgHttp2AdapterTest, ServerHandlesContentLengthMismatchWithDataPending) {
+ TestVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ // Server preface
+ adapter->SubmitSettings({});
+
+ testing::InSequence s;
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ClientPreface()
+ .Headers(1, {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"},
+ {"content-length", "4"}})
+ .Data(1, "ok", /*fin=*/false)
+ .Serialize();
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ // Stream 1: content-length is smaller than actual data, but not yet
+ // Headers and the beginning of data is delivered to the visitor.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, 2, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 2));
+ EXPECT_CALL(visitor, OnDataForStream(1, _));
+
+ const int64_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
+
+ // Initial response data for stream 1.
+ visitor.AppendPayloadForStream(
+ 1, "Here is some response data, and there will be more. ");
+ adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), false);
+
+ // Server preface (SETTINGS)
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ // SETTINGS ack
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, ACK_FLAG));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, ACK_FLAG, 0));
+ // Stream 1, with some DATA
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ EXPECT_TRUE(adapter->want_write());
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS,
+ SpdyFrameType::HEADERS, SpdyFrameType::DATA}));
+
+ visitor.Clear();
+
+ // Final response data and fin for stream 1.
+ visitor.AppendPayloadForStream(1, "Last data!");
+ visitor.SetEndData(1, true);
+ adapter->ResumeStream(1);
+
+ const std::string client_fin =
+ TestFrameSequence().Data(1, "ay!", /*fin=*/true).Serialize();
+
+ // The library does not deliver the actual data or fin from the client to the
+ // visitor.
+ EXPECT_CALL(visitor, OnFrameHeader(1, 3, DATA, 0x1));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 3));
+
+ const int64_t fin_result = adapter->ProcessBytes(client_fin);
+ ASSERT_EQ(client_fin.size(), fin_result);
+
+ // The library sends the RST_STREAM but not the end of data.
+ 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));
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+}
+
TEST(NgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) {
TestVisitor visitor;
auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc
index 82980cb..bf11371 100644
--- a/quiche/http2/adapter/oghttp2_adapter_test.cc
+++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -8279,6 +8279,94 @@
SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM}));
}
+TEST(OgHttp2AdapterTest, ServerHandlesContentLengthMismatchWithDataPending) {
+ TestVisitor visitor;
+ OgHttp2Adapter::Options options;
+ options.perspective = Perspective::kServer;
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ClientPreface()
+ .Headers(1, {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"},
+ {"content-length", "4"}})
+ .Data(1, "ok", /*fin=*/false)
+ .Serialize();
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ // Stream 1: content-length is smaller than actual data, but not yet
+ // Headers and the beginning of data is delivered to the visitor.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, 2, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 2));
+ EXPECT_CALL(visitor, OnDataForStream(1, _));
+
+ const int64_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
+
+ // Initial response data for stream 1.
+ visitor.AppendPayloadForStream(
+ 1, "Here is some response data, and there will be more. ");
+ adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), false);
+
+ // Server preface (SETTINGS)
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ // SETTINGS ack
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, ACK_FLAG));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, ACK_FLAG, 0));
+ // Stream 1, with some DATA
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ EXPECT_TRUE(adapter->want_write());
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS,
+ SpdyFrameType::HEADERS, SpdyFrameType::DATA}));
+
+ visitor.Clear();
+
+ // Final response data and fin for stream 1.
+ visitor.AppendPayloadForStream(1, "Last data!");
+ visitor.SetEndData(1, true);
+ adapter->ResumeStream(1);
+
+ const std::string client_fin =
+ TestFrameSequence().Data(1, "ay!", /*fin=*/true).Serialize();
+
+ // The library does not deliver the actual data or fin from the client to the
+ // visitor.
+ EXPECT_CALL(visitor, OnFrameHeader(1, 3, DATA, 0x1));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 3));
+
+ const int64_t fin_result = adapter->ProcessBytes(client_fin);
+ ASSERT_EQ(client_fin.size(), fin_result);
+
+ // The library sends the RST_STREAM but not the end of data.
+ 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));
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+}
+
TEST(OgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) {
TestVisitor visitor;
OgHttp2Adapter::Options options;