Fix content-length validation for trailers.
This CL fixes a bug in content-length validation when trailers end the stream:
1. End of headers, stores content-length for validation
2. End of data, does not end stream and does not match content-length
3. End of trailers, ends stream and stores content-length for validation (!)
The re-storing of content-length at the end of trailers erases the previous
validation at the end of data. This CL fixes Step 3 to avoid storing
content-length for trailers.
MultiplexedIntegrationTest.InconsistentContentLength:
http://sponge2/e572060c-8ff8-45a8-b3b3-25994345b2bb (demonstrates RST_STREAM)
PiperOrigin-RevId: 422904538
diff --git a/http2/adapter/header_validator.cc b/http2/adapter/header_validator.cc
index d256156..d5ae7a2 100644
--- a/http2/adapter/header_validator.cc
+++ b/http2/adapter/header_validator.cc
@@ -136,6 +136,7 @@
return false;
}
+ // TODO(diannahu): Do the same for 1xx responses.
if (status_ == "204" && value != "0") {
// There should be no body in a "204 No Content" response.
return false;
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 0bf7295..206d8e2 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -4402,6 +4402,16 @@
{":path", "/this/is/request/four"},
{"content-length", "2"}},
/*fin=*/true)
+ .Headers(7,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/four"},
+ {"content-length", "2"}},
+ /*fin=*/false)
+ .Data(7, "h", /*fin=*/false)
+ .Headers(7, {{"extra-info", "Trailers with content-length mismatch"}},
+ /*fin=*/true)
.Serialize();
// Client preface (empty SETTINGS)
@@ -4438,6 +4448,22 @@
OnInvalidFrame(
5, Http2VisitorInterface::InvalidFrameError::kHttpMessaging));
+ // Stream 7: content-length is invalid and trailers end the stream
+ // When the stream ends with trailers, nghttp2 invokes OnInvalidFrame().
+ EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(7));
+ EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(7));
+ EXPECT_CALL(visitor, OnFrameHeader(7, _, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(7, 1));
+ EXPECT_CALL(visitor, OnDataForStream(7, "h"));
+ EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(7));
+ EXPECT_CALL(visitor, OnHeaderForStream(7, _, _));
+ EXPECT_CALL(visitor,
+ OnInvalidFrame(
+ 7, Http2VisitorInterface::InvalidFrameError::kHttpMessaging));
+
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
@@ -4458,6 +4484,11 @@
OnFrameSent(RST_STREAM, 5, _, 0x0,
static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::PROTOCOL_ERROR));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 7, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 7, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(7, Http2ErrorCode::PROTOCOL_ERROR));
EXPECT_TRUE(adapter->want_write());
int result = adapter->Send();
@@ -4465,6 +4496,7 @@
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
spdy::SpdyFrameType::RST_STREAM,
spdy::SpdyFrameType::RST_STREAM,
+ spdy::SpdyFrameType::RST_STREAM,
spdy::SpdyFrameType::RST_STREAM}));
}
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 3660d20..d3f235d 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -4564,6 +4564,16 @@
{":path", "/this/is/request/four"},
{"content-length", "2"}},
/*fin=*/true)
+ .Headers(7,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/four"},
+ {"content-length", "2"}},
+ /*fin=*/false)
+ .Data(7, "h", /*fin=*/false)
+ .Headers(7, {{"extra-info", "Trailers with content-length mismatch"}},
+ /*fin=*/true)
.Serialize();
// Client preface (empty SETTINGS)
@@ -4600,6 +4610,21 @@
EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(5);
EXPECT_CALL(visitor, OnEndHeadersForStream(5));
+ // Stream 7: content-length is invalid and trailers end the stream
+ // Only oghttp2 invokes OnEndHeadersForStream(). Only nghttp2 invokes
+ // OnInvalidFrame().
+ EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(7));
+ EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(7));
+ EXPECT_CALL(visitor, OnFrameHeader(7, _, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(7, 1));
+ EXPECT_CALL(visitor, OnDataForStream(7, "h"));
+ EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(7));
+ EXPECT_CALL(visitor, OnHeaderForStream(7, _, _));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(7));
+
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result));
@@ -4622,15 +4647,21 @@
OnFrameSent(RST_STREAM, 5, _, 0x0,
static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 7, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 7, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(7, 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}));
+ EXPECT_THAT(
+ visitor.data(),
+ EqualsFrames(
+ {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM, spdy::SpdyFrameType::RST_STREAM,
+ spdy::SpdyFrameType::RST_STREAM, spdy::SpdyFrameType::RST_STREAM}));
}
} // namespace
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 827e80b..4c14b1b 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -6,6 +6,7 @@
#include "absl/memory/memory.h"
#include "absl/strings/escaping.h"
+#include "http2/adapter/header_validator.h"
#include "http2/adapter/http2_protocol.h"
#include "http2/adapter/http2_util.h"
#include "http2/adapter/http2_visitor_interface.h"
@@ -1117,7 +1118,10 @@
} else {
it->second.received_header_type = headers_handler_.header_type();
}
- it->second.remaining_content_length = headers_handler_.content_length();
+ if (headers_handler_.header_type() == HeaderType::REQUEST ||
+ headers_handler_.header_type() == HeaderType::RESPONSE) {
+ it->second.remaining_content_length = headers_handler_.content_length();
+ }
headers_handler_.set_stream_id(0);
}
}