Signal error on PUSH_PROMISE and CANCEL_PUSH frames.
Since HttpEncoder is not capable of sending MAX_PUSH_ID frames, receiving these
frames is a violation of the spec.
For simplicity, use the error code H3_FRAME_ERROR. This is incorrect, because
receiving a PUSH_PROMISE frame by the client on the control stream, or
receiving a CANCEL_PUSH frame either by the client or by the server on the control
stream should trigger an H3_ID_ERROR, and these frames in other cases should
trigger a H3_FRAME_UNEXPECTED error. However, it is expected to be exceedingly
rare for any endpoint to send such frames without receiving a MAX_PUSH_ID frame,
so the error code does not matter as much as actually closing the connection.
Protected by FLAGS_quic_reloadable_flag_quic_error_on_http3_push.
PiperOrigin-RevId: 370762371
Change-Id: I18d4d29e95faf9919858bada73ef0b5912afbd8b
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index 859d174..6d9290a 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -38,11 +38,15 @@
error_(QUIC_NO_ERROR),
error_detail_(""),
ignore_old_priority_update_(
- GetQuicReloadableFlag(quic_ignore_old_priority_update_frame)) {
+ GetQuicReloadableFlag(quic_ignore_old_priority_update_frame)),
+ error_on_http3_push_(GetQuicReloadableFlag(quic_error_on_http3_push)) {
QUICHE_DCHECK(visitor_);
if (ignore_old_priority_update_) {
QUIC_RELOADABLE_FLAG_COUNT(quic_ignore_old_priority_update_frame);
}
+ if (error_on_http3_push_) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_error_on_http3_push);
+ }
}
HttpDecoder::~HttpDecoder() {}
@@ -177,6 +181,20 @@
current_frame_type_));
return false;
}
+
+ if (error_on_http3_push_) {
+ if (current_frame_type_ ==
+ static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR, "CANCEL_PUSH frame received.");
+ return false;
+ }
+ if (current_frame_type_ ==
+ static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR, "PUSH_PROMISE frame received.");
+ return false;
+ }
+ }
+
state_ = STATE_READING_FRAME_LENGTH;
return true;
}
@@ -243,11 +261,19 @@
visitor_->OnHeadersFrameStart(header_length, current_frame_length_);
break;
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH):
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ break;
+ }
break;
case static_cast<uint64_t>(HttpFrameType::SETTINGS):
continue_processing = visitor_->OnSettingsFrameStart(header_length);
break;
case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE):
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ break;
+ }
// This edge case needs to be handled here, because ReadFramePayload()
// does not get called if |current_frame_length_| is zero.
if (current_frame_length_ == 0) {
@@ -318,7 +344,11 @@
break;
}
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): {
- continue_processing = BufferOrParsePayload(reader);
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ } else {
+ continue_processing = BufferOrParsePayload(reader);
+ }
break;
}
case static_cast<uint64_t>(HttpFrameType::SETTINGS): {
@@ -326,6 +356,10 @@
break;
}
case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE): {
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ break;
+ }
PushId push_id;
if (current_frame_length_ == remaining_frame_length_) {
// A new Push Promise frame just arrived.
@@ -443,9 +477,13 @@
break;
}
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): {
- // If frame payload is not empty, FinishParsing() is skipped.
- QUICHE_DCHECK_EQ(0u, current_frame_length_);
- continue_processing = BufferOrParsePayload(reader);
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ } else {
+ // If frame payload is not empty, FinishParsing() is skipped.
+ QUICHE_DCHECK_EQ(0u, current_frame_length_);
+ continue_processing = BufferOrParsePayload(reader);
+ }
break;
}
case static_cast<uint64_t>(HttpFrameType::SETTINGS): {
@@ -455,7 +493,11 @@
break;
}
case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE): {
- continue_processing = visitor_->OnPushPromiseFrameEnd();
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ } else {
+ continue_processing = visitor_->OnPushPromiseFrameEnd();
+ }
break;
}
case static_cast<uint64_t>(HttpFrameType::GOAWAY): {
@@ -578,6 +620,10 @@
switch (current_frame_type_) {
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): {
+ if (error_on_http3_push_) {
+ QUICHE_NOTREACHED();
+ return false;
+ }
CancelPushFrame frame;
if (!reader->ReadVarInt62(&frame.push_id)) {
RaiseError(QUIC_HTTP_FRAME_ERROR,
@@ -791,6 +837,7 @@
QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) {
switch (frame_type) {
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH):
+ // TODO(b/171463363): Remove.
return sizeof(PushId);
case static_cast<uint64_t>(HttpFrameType::SETTINGS):
// This limit is arbitrary.
@@ -798,6 +845,7 @@
case static_cast<uint64_t>(HttpFrameType::GOAWAY):
return VARIABLE_LENGTH_INTEGER_LENGTH_8;
case static_cast<uint64_t>(HttpFrameType::MAX_PUSH_ID):
+ // TODO(b/171463363): Remove.
return sizeof(PushId);
case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
// This limit is arbitrary.
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
index 5580d44..3a970fb 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -45,6 +45,7 @@
// processed. At that point it is safe to consume |header_length| bytes.
// Called when a CANCEL_PUSH frame has been successfully parsed.
+ // TODO(b/171463363): Remove.
virtual bool OnCancelPushFrame(const CancelPushFrame& frame) = 0;
// Called when a MAX_PUSH_ID frame has been successfully parsed.
@@ -83,6 +84,7 @@
// Called when a HEADERS frame has been completely processed.
virtual bool OnHeadersFrameEnd() = 0;
+ // TODO(b/171463363): Remove all.
// Called when a PUSH_PROMISE frame has been received.
virtual bool OnPushPromiseFrameStart(QuicByteCount header_length) = 0;
// Called when the Push ID field of a PUSH_PROMISE frame has been parsed.
@@ -230,7 +232,7 @@
void BufferFrameType(QuicDataReader* reader);
// Buffers at most |remaining_push_id_length_| from |reader| to
- // |push_id_buffer_|.
+ // |push_id_buffer_|. TODO(b/171463363): Remove.
void BufferPushId(QuicDataReader* reader);
// Sets |error_| and |error_detail_| accordingly.
@@ -278,8 +280,10 @@
// Remaining length that's needed for the frame's type field.
QuicByteCount remaining_type_field_length_;
// Length of PUSH_PROMISE frame's push id.
+ // TODO(b/171463363): Remove.
QuicByteCount current_push_id_length_;
// Remaining length that's needed for PUSH_PROMISE frame's push id field.
+ // TODO(b/171463363): Remove.
QuicByteCount remaining_push_id_length_;
// Last error.
QuicErrorCode error_;
@@ -292,11 +296,16 @@
// Remaining unparsed type field data.
std::array<char, sizeof(uint64_t)> type_buffer_;
// Remaining unparsed push id data.
+ // TODO(b/171463363): Remove.
std::array<char, sizeof(uint64_t)> push_id_buffer_;
// Latched value of
// gfe2_reloadable_flag_quic_ignore_old_priority_update_frame.
const bool ignore_old_priority_update_;
+
+ // Latched value of
+ // gfe2_reloadable_flag_quic_error_on_http3_push.
+ const bool error_on_http3_push_;
};
} // namespace quic
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index ab0e31f..95c1c5c 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -249,6 +249,14 @@
"01" // length
"01"); // Push Id
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ EXPECT_CALL(visitor_, OnError(&decoder_));
+ EXPECT_EQ(1u, ProcessInput(input));
+ EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ("CANCEL_PUSH frame received.", decoder_.error_detail());
+ return;
+ }
+
// Visitor pauses processing.
EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1})))
.WillOnce(Return(false));
@@ -277,6 +285,14 @@
"C000000000000101"), // push id 257
"Headers"); // headers
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ EXPECT_CALL(visitor_, OnError(&decoder_));
+ EXPECT_EQ(1u, ProcessInput(input));
+ EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ("PUSH_PROMISE frame received.", decoder_.error_detail());
+ return;
+ }
+
// Visitor pauses processing.
EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)).WillOnce(Return(false));
EXPECT_CALL(visitor_, OnPushPromiseFramePushId(257, 8, 7))
@@ -338,6 +354,10 @@
}
TEST_F(HttpDecoderTest, CorruptPushPromiseFrame) {
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
+
InSequence s;
std::string input = absl::HexStringToBytes(
@@ -733,6 +753,10 @@
}
TEST_F(HttpDecoderTest, PushPromiseFrameNoHeaders) {
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
+
InSequence s;
std::string input = absl::HexStringToBytes(
"05" // type (PUSH_PROMISE)
@@ -768,6 +792,10 @@
}
TEST_F(HttpDecoderTest, MalformedFrameWithOverlyLargePayload) {
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
+
std::string input = absl::HexStringToBytes(
"03" // type (CANCEL_PUSH)
"10" // length
@@ -841,97 +869,183 @@
}
TEST_F(HttpDecoderTest, CorruptFrame) {
- InSequence s;
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ InSequence s;
- struct {
- const char* const input;
- const char* const error_message;
- } kTestData[] = {{"\x03" // type (CANCEL_PUSH)
- "\x01" // length
- "\x40", // first byte of two-byte varint push id
- "Unable to read CANCEL_PUSH push_id."},
- {"\x03" // type (CANCEL_PUSH)
- "\x04" // length
- "\x05" // valid push id
- "foo", // superfluous data
- "Superfluous data in CANCEL_PUSH frame."},
- {"\x0D" // type (MAX_PUSH_ID)
- "\x01" // length
- "\x40", // first byte of two-byte varint push id
- "Unable to read MAX_PUSH_ID push_id."},
- {"\x0D" // type (MAX_PUSH_ID)
- "\x04" // length
- "\x05" // valid push id
- "foo", // superfluous data
- "Superfluous data in MAX_PUSH_ID frame."},
- {"\x07" // type (GOAWAY)
- "\x01" // length
- "\x40", // first byte of two-byte varint stream id
- "Unable to read GOAWAY ID."},
- {"\x07" // type (GOAWAY)
- "\x04" // length
- "\x05" // valid stream id
- "foo", // superfluous data
- "Superfluous data in GOAWAY frame."},
- {"\x40\x89" // type (ACCEPT_CH)
- "\x01" // length
- "\x40", // first byte of two-byte varint origin length
- "Unable to read ACCEPT_CH origin."},
- {"\x40\x89" // type (ACCEPT_CH)
- "\x01" // length
- "\x05", // valid origin length but no origin string
- "Unable to read ACCEPT_CH origin."},
- {"\x40\x89" // type (ACCEPT_CH)
- "\x04" // length
- "\x05" // valid origin length
- "foo", // payload ends before origin ends
- "Unable to read ACCEPT_CH origin."},
- {"\x40\x89" // type (ACCEPT_CH)
- "\x04" // length
- "\x03" // valid origin length
- "foo", // payload ends at end of origin: no value
- "Unable to read ACCEPT_CH value."},
- {"\x40\x89" // type (ACCEPT_CH)
- "\x05" // length
- "\x03" // valid origin length
- "foo" // payload ends at end of origin: no value
- "\x40", // first byte of two-byte varint value length
- "Unable to read ACCEPT_CH value."},
- {"\x40\x89" // type (ACCEPT_CH)
- "\x08" // length
- "\x03" // valid origin length
- "foo" // origin
- "\x05" // valid value length
- "bar", // payload ends before value ends
- "Unable to read ACCEPT_CH value."}};
+ struct {
+ const char* const input;
+ const char* const error_message;
+ } kTestData[] = {{"\x0D" // type (MAX_PUSH_ID)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint push id
+ "Unable to read MAX_PUSH_ID push_id."},
+ {"\x0D" // type (MAX_PUSH_ID)
+ "\x04" // length
+ "\x05" // valid push id
+ "foo", // superfluous data
+ "Superfluous data in MAX_PUSH_ID frame."},
+ {"\x07" // type (GOAWAY)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint stream id
+ "Unable to read GOAWAY ID."},
+ {"\x07" // type (GOAWAY)
+ "\x04" // length
+ "\x05" // valid stream id
+ "foo", // superfluous data
+ "Superfluous data in GOAWAY frame."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint origin length
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x01" // length
+ "\x05", // valid origin length but no origin string
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x04" // length
+ "\x05" // valid origin length
+ "foo", // payload ends before origin ends
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x04" // length
+ "\x03" // valid origin length
+ "foo", // payload ends at end of origin: no value
+ "Unable to read ACCEPT_CH value."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x05" // length
+ "\x03" // valid origin length
+ "foo" // payload ends at end of origin: no value
+ "\x40", // first byte of two-byte varint value length
+ "Unable to read ACCEPT_CH value."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x08" // length
+ "\x03" // valid origin length
+ "foo" // origin
+ "\x05" // valid value length
+ "bar", // payload ends before value ends
+ "Unable to read ACCEPT_CH value."}};
- for (const auto& test_data : kTestData) {
- {
- HttpDecoder decoder(&visitor_);
- EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
- EXPECT_CALL(visitor_, OnError(&decoder));
+ for (const auto& test_data : kTestData) {
+ {
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
+ EXPECT_CALL(visitor_, OnError(&decoder));
- absl::string_view input(test_data.input);
- decoder.ProcessInput(input.data(), input.size());
- EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
- EXPECT_EQ(test_data.error_message, decoder.error_detail());
- }
- {
- HttpDecoder decoder(&visitor_);
- EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
- EXPECT_CALL(visitor_, OnError(&decoder));
-
- absl::string_view input(test_data.input);
- for (auto c : input) {
- decoder.ProcessInput(&c, 1);
+ absl::string_view input(test_data.input);
+ decoder.ProcessInput(input.data(), input.size());
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
}
- EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
- EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ {
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
+ EXPECT_CALL(visitor_, OnError(&decoder));
+
+ absl::string_view input(test_data.input);
+ for (auto c : input) {
+ decoder.ProcessInput(&c, 1);
+ }
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
+ }
+ } else {
+ InSequence s;
+
+ struct {
+ const char* const input;
+ const char* const error_message;
+ } kTestData[] = {{"\x03" // type (CANCEL_PUSH)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint push id
+ "Unable to read CANCEL_PUSH push_id."},
+ {"\x03" // type (CANCEL_PUSH)
+ "\x04" // length
+ "\x05" // valid push id
+ "foo", // superfluous data
+ "Superfluous data in CANCEL_PUSH frame."},
+ {"\x0D" // type (MAX_PUSH_ID)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint push id
+ "Unable to read MAX_PUSH_ID push_id."},
+ {"\x0D" // type (MAX_PUSH_ID)
+ "\x04" // length
+ "\x05" // valid push id
+ "foo", // superfluous data
+ "Superfluous data in MAX_PUSH_ID frame."},
+ {"\x07" // type (GOAWAY)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint stream id
+ "Unable to read GOAWAY ID."},
+ {"\x07" // type (GOAWAY)
+ "\x04" // length
+ "\x05" // valid stream id
+ "foo", // superfluous data
+ "Superfluous data in GOAWAY frame."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint origin length
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x01" // length
+ "\x05", // valid origin length but no origin string
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x04" // length
+ "\x05" // valid origin length
+ "foo", // payload ends before origin ends
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x04" // length
+ "\x03" // valid origin length
+ "foo", // payload ends at end of origin: no value
+ "Unable to read ACCEPT_CH value."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x05" // length
+ "\x03" // valid origin length
+ "foo" // payload ends at end of origin: no value
+ "\x40", // first byte of two-byte varint value length
+ "Unable to read ACCEPT_CH value."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x08" // length
+ "\x03" // valid origin length
+ "foo" // origin
+ "\x05" // valid value length
+ "bar", // payload ends before value ends
+ "Unable to read ACCEPT_CH value."}};
+
+ for (const auto& test_data : kTestData) {
+ {
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
+ EXPECT_CALL(visitor_, OnError(&decoder));
+
+ absl::string_view input(test_data.input);
+ decoder.ProcessInput(input.data(), input.size());
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
+ {
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
+ EXPECT_CALL(visitor_, OnError(&decoder));
+
+ absl::string_view input(test_data.input);
+ for (auto c : input) {
+ decoder.ProcessInput(&c, 1);
+ }
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
}
}
}
TEST_F(HttpDecoderTest, EmptyCancelPushFrame) {
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
+
std::string input = absl::HexStringToBytes(
"03" // type (CANCEL_PUSH)
"00"); // frame length
@@ -959,6 +1073,10 @@
// Regression test for https://crbug.com/1001823.
TEST_F(HttpDecoderTest, EmptyPushPromiseFrame) {
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
+
std::string input = absl::HexStringToBytes(
"05" // type (PUSH_PROMISE)
"00"); // frame length
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index fbd7b2e..83a8940 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -70,7 +70,6 @@
spdy_session()->debug_visitor()->OnCancelPushFrameReceived(frame);
}
- // TODO(b/151841240): Handle CANCEL_PUSH frames instead of ignoring them.
return ValidateFrameType(HttpFrameType::CANCEL_PUSH);
}
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index 1c24808..b81d0ba 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -311,7 +311,10 @@
push_promise_frame);
EXPECT_CALL(
*connection_,
- CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _))
+ CloseConnection(GetQuicReloadableFlag(quic_error_on_http3_push)
+ ? QUIC_HTTP_FRAME_ERROR
+ : QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM,
+ _, _))
.WillOnce(
Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _, _));
@@ -382,13 +385,21 @@
"01" // payload length
"01"); // push ID
- EXPECT_CALL(*connection_,
- CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME,
- "First frame received on control stream is type "
- "3, but it must be SETTINGS.",
- _))
- .WillOnce(
- Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR,
+ "CANCEL_PUSH frame received.", _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ } else {
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "First frame received on control stream is type "
+ "3, but it must be SETTINGS.",
+ _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ }
EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _, _));
EXPECT_CALL(session_, OnConnectionClosed(_, _));
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index c638a0a..dbf5def 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2894,10 +2894,7 @@
session_.OnHttp3GoAway(stream_id2);
}
-// Test that receipt of CANCEL_PUSH frame does not result in closing the
-// connection.
-// TODO(b/151841240): Handle CANCEL_PUSH frames instead of ignoring them.
-TEST_P(QuicSpdySessionTestClient, IgnoreCancelPush) {
+TEST_P(QuicSpdySessionTestClient, CloseConnectionOnCancelPush) {
if (!VersionUsesHttp3(transport_version())) {
return;
}
@@ -2934,7 +2931,18 @@
"00"); // push ID
QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset,
cancel_push_frame);
- EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_));
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR,
+ "CANCEL_PUSH frame received.", _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ EXPECT_CALL(*connection_,
+ SendConnectionClosePacket(QUIC_HTTP_FRAME_ERROR, _,
+ "CANCEL_PUSH frame received."));
+ } else {
+ // CANCEL_PUSH is ignored.
+ EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_));
+ }
session_.OnStreamFrame(data3);
}
@@ -3093,10 +3101,7 @@
session_.OnStopSendingFrame(stop_sending_encoder_stream);
}
-// Test that receipt of CANCEL_PUSH frame does not result in closing the
-// connection.
-// TODO(b/151841240): Handle CANCEL_PUSH frames instead of ignoring them.
-TEST_P(QuicSpdySessionTestServer, IgnoreCancelPush) {
+TEST_P(QuicSpdySessionTestServer, CloseConnectionOnCancelPush) {
if (!VersionUsesHttp3(transport_version())) {
return;
}
@@ -3133,7 +3138,18 @@
"00"); // push ID
QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset,
cancel_push_frame);
- EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_));
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR,
+ "CANCEL_PUSH frame received.", _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ EXPECT_CALL(*connection_,
+ SendConnectionClosePacket(QUIC_HTTP_FRAME_ERROR, _,
+ "CANCEL_PUSH frame received."));
+ } else {
+ // CANCEL_PUSH is ignored.
+ EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_));
+ }
session_.OnStreamFrame(data3);
}
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index bb464bd..8b814b8 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -2772,6 +2772,9 @@
// TODO(b/171463363): Remove.
TEST_P(QuicSpdyStreamTest, PushPromiseOnDataStream) {
Initialize(kShouldProcessData);
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
if (!UsesHttp3()) {
return;
}
@@ -2802,6 +2805,9 @@
TEST_P(QuicSpdyStreamTest,
OnStreamHeaderBlockArgumentDoesNotIncludePushedHeaderBlock) {
Initialize(kShouldProcessData);
+ if (GetQuicReloadableFlag(quic_error_on_http3_push)) {
+ return;
+ }
if (!UsesHttp3()) {
return;
}
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 32816b8..9fa422f 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -459,8 +459,12 @@
// Received multiple close offset.
QUIC_STREAM_MULTIPLE_OFFSET = 130,
- // Internal error codes for HTTP/3 errors.
+ // HTTP/3 errors.
+
+ // Frame payload larger than what HttpDecoder is willing to buffer.
QUIC_HTTP_FRAME_TOO_LARGE = 131,
+ // Malformed HTTP/3 frame, or PUSH_PROMISE or CANCEL_PUSH received (which is
+ // an error because MAX_PUSH_ID is never sent).
QUIC_HTTP_FRAME_ERROR = 132,
// A frame that is never allowed on a request stream is received.
QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM = 133,
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 3f7ca67..4ed13ff 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -44,6 +44,7 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_version_rfcv1, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_control_frames, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_goaway, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_error_on_http3_push, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_dispatcher_sent_error_code, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_on_stream_reset, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_stateless_reset, true)