Support Http3 extended CONNECT. When QuicSpdySession::allow_extended_connect_ is true, ENABLE_CONNECT_PROTOCOL will be 1 in SETTINGS frame. The client sets allow_extended_connect_ to true once the received SETTINGS frame has ENABLE_CONNECT_PROTOCOL = 1. QuicSpdySession::allow_extended_connect_ is behind --gfe2_reloadable_flag_quic_verify_request_headers.
Validate request pseudo headers, especially CONNECT with :protocol header when allow_extended_connect_ is true.
Make webtransport support depends on allow_extended_connect_. And on server side, allow_extended_connect_ is always true if ShouldNegotiateWebTransport() returns true.
Protected by quic_reloadable_flag_quic_verify_request_headers.
PiperOrigin-RevId: 402397389
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 54f6fa9..ba3c7a7 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -136,7 +136,10 @@
// Since QuicSpdyStream uses QuicHeaderList::empty() to detect too large
// headers, it also fails when receiving empty headers.
SpdyHeaderBlock headers;
- headers["foo"] = "bar";
+ headers[":authority"] = "test.example.com:443";
+ headers[":path"] = "/path";
+ headers[":method"] = "GET";
+ headers[":scheme"] = "https";
stream->WriteHeaders(std::move(headers), /* fin = */ false, nullptr);
}
@@ -6530,6 +6533,58 @@
}));
}
+TEST_P(EndToEndTest, InvalidExtendedConnect) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ ASSERT_TRUE(Initialize());
+
+ if (!version_.UsesHttp3()) {
+ return;
+ }
+ // Missing :path header.
+ spdy::SpdyHeaderBlock headers;
+ headers[":scheme"] = "https";
+ headers[":authority"] = "localhost";
+ headers[":method"] = "CONNECT";
+ headers[":protocol"] = "webtransport";
+
+ client_->SendMessage(headers, "", /*fin=*/false);
+ client_->WaitForResponse();
+ // An early response should be received.
+ CheckResponseHeaders("400");
+}
+
+TEST_P(EndToEndTest, RejectExtendedConnect) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ // Disable extended CONNECT.
+ memory_cache_backend_.set_enable_extended_connect(false);
+ ASSERT_TRUE(Initialize());
+
+ if (!version_.UsesHttp3()) {
+ return;
+ }
+ // This extended CONNECT should be rejected.
+ spdy::SpdyHeaderBlock headers;
+ headers[":scheme"] = "https";
+ headers[":authority"] = "localhost";
+ headers[":method"] = "CONNECT";
+ headers[":path"] = "/echo";
+ headers[":protocol"] = "webtransport";
+
+ client_->SendMessage(headers, "", /*fin=*/false);
+ client_->WaitForResponse();
+ CheckResponseHeaders("400");
+
+ // Vanilla CONNECT should be accepted.
+ spdy::SpdyHeaderBlock headers2;
+ headers2[":authority"] = "localhost";
+ headers2[":method"] = "CONNECT";
+
+ client_->SendMessage(headers2, "body", /*fin=*/true);
+ client_->WaitForResponse();
+ // No :path header, so 404.
+ CheckResponseHeaders("404");
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/http/http_constants.cc b/quic/core/http/http_constants.cc
index 851fd91..94d42fe 100644
--- a/quic/core/http/http_constants.cc
+++ b/quic/core/http/http_constants.cc
@@ -20,6 +20,7 @@
RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM_DRAFT00);
RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM_DRAFT04);
RETURN_STRING_LITERAL(SETTINGS_WEBTRANS_DRAFT00);
+ RETURN_STRING_LITERAL(SETTINGS_ENABLE_CONNECT_PROTOCOL);
}
return absl::StrCat("UNSUPPORTED_SETTINGS_TYPE(", identifier, ")");
}
diff --git a/quic/core/http/http_constants.h b/quic/core/http/http_constants.h
index ce03a1e..213b7d9 100644
--- a/quic/core/http/http_constants.h
+++ b/quic/core/http/http_constants.h
@@ -44,6 +44,8 @@
SETTINGS_H3_DATAGRAM_DRAFT04 = 0xffd277,
// draft-ietf-webtrans-http3-00
SETTINGS_WEBTRANS_DRAFT00 = 0x2b603742,
+ // draft-ietf-httpbis-h3-websockets
+ SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08,
};
// Returns HTTP/3 SETTINGS identifier as a string.
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 5943585..7c71abe 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -130,8 +130,10 @@
"4040" // 0x40 as the reserved frame type
"01" // 1 byte frame length
"61"); // payload "a"
- if (QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
- HttpDatagramSupport::kDraft00And04) {
+ if ((!GetQuicReloadableFlag(quic_verify_request_headers) ||
+ perspective() == Perspective::IS_CLIENT) &&
+ QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
+ HttpDatagramSupport::kDraft00And04) {
expected_write_data = absl::HexStringToBytes(
"00" // stream type: control stream
"04" // frame type: SETTINGS frame
@@ -152,6 +154,54 @@
"01" // 1 byte frame length
"61"); // payload "a"
}
+ if (GetQuicReloadableFlag(quic_verify_request_headers) &&
+ perspective() == Perspective::IS_SERVER &&
+ QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
+ HttpDatagramSupport::kNone) {
+ expected_write_data = absl::HexStringToBytes(
+ "00" // stream type: control stream
+ "04" // frame type: SETTINGS frame
+ "0d" // frame length
+ "01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
+ "40ff" // 255
+ "06" // SETTINGS_MAX_HEADER_LIST_SIZE
+ "4400" // 1024
+ "07" // SETTINGS_QPACK_BLOCKED_STREAMS
+ "10" // 16
+ "08" // SETTINGS_ENABLE_CONNECT_PROTOCOL
+ "01" // 1
+ "4040" // 0x40 as the reserved settings id
+ "14" // 20
+ "4040" // 0x40 as the reserved frame type
+ "01" // 1 byte frame length
+ "61"); // payload "a"
+ }
+ if (GetQuicReloadableFlag(quic_verify_request_headers) &&
+ perspective() == Perspective::IS_SERVER &&
+ QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) !=
+ HttpDatagramSupport::kNone) {
+ expected_write_data = absl::HexStringToBytes(
+ "00" // stream type: control stream
+ "04" // frame type: SETTINGS frame
+ "11" // frame length
+ "01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
+ "40ff" // 255
+ "06" // SETTINGS_MAX_HEADER_LIST_SIZE
+ "4400" // 1024
+ "07" // SETTINGS_QPACK_BLOCKED_STREAMS
+ "10" // 16
+ "08" // SETTINGS_ENABLE_CONNECT_PROTOCOL
+ "01" // 1
+ "4040" // 0x40 as the reserved settings id
+ "14" // 20
+ "4276" // SETTINGS_H3_DATAGRAM_DRAFT00
+ "01" // 1
+ "800ffd277" // SETTINGS_H3_DATAGRAM_DRAFT04
+ "01" // 1
+ "4040" // 0x40 as the reserved frame type
+ "01" // 1 byte frame length
+ "61"); // payload "a"
+ }
auto buffer = std::make_unique<char[]>(expected_write_data.size());
QuicDataWriter writer(expected_write_data.size(), buffer.get());
diff --git a/quic/core/http/quic_spdy_client_stream.cc b/quic/core/http/quic_spdy_client_stream.cc
index 7391fdc..4ba3fc9 100644
--- a/quic/core/http/quic_spdy_client_stream.cc
+++ b/quic/core/http/quic_spdy_client_stream.cc
@@ -13,6 +13,7 @@
#include "quic/core/http/web_transport_http3.h"
#include "quic/core/quic_alarm.h"
#include "quic/platform/api/quic_logging.h"
+#include "common/quiche_text_utils.h"
#include "spdy/core/spdy_protocol.h"
using spdy::SpdyHeaderBlock;
@@ -188,4 +189,25 @@
return bytes_sent;
}
+bool QuicSpdyClientStream::AreHeadersValid(
+ const QuicHeaderList& header_list) const {
+ if (!GetQuicReloadableFlag(quic_verify_request_headers)) {
+ return true;
+ }
+ if (!QuicSpdyStream::AreHeadersValid(header_list)) {
+ return false;
+ }
+ // Verify the presence of :status header.
+ bool saw_status = false;
+ for (const std::pair<std::string, std::string>& pair : header_list) {
+ if (pair.first == ":status") {
+ saw_status = true;
+ } else if (absl::StrContains(pair.first, ":")) {
+ QUIC_DLOG(ERROR) << "Unexpected ':' in header " << pair.first << ".";
+ return false;
+ }
+ }
+ return saw_status;
+}
+
} // namespace quic
diff --git a/quic/core/http/quic_spdy_client_stream.h b/quic/core/http/quic_spdy_client_stream.h
index 8637d11..7d420f5 100644
--- a/quic/core/http/quic_spdy_client_stream.h
+++ b/quic/core/http/quic_spdy_client_stream.h
@@ -74,6 +74,9 @@
// of client-side streams should be able to set the priority.
using QuicSpdyStream::SetPriority;
+ protected:
+ bool AreHeadersValid(const QuicHeaderList& header_list) const override;
+
private:
// The parsed headers received from the server.
spdy::SpdyHeaderBlock response_headers_;
diff --git a/quic/core/http/quic_spdy_client_stream_test.cc b/quic/core/http/quic_spdy_client_stream_test.cc
index 9043d0b..11f81e5 100644
--- a/quic/core/http/quic_spdy_client_stream_test.cc
+++ b/quic/core/http/quic_spdy_client_stream_test.cc
@@ -128,6 +128,30 @@
IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
}
+TEST_P(QuicSpdyClientStreamTest, InvalidResponseHeader) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ auto headers = AsHeaderList(std::vector<std::pair<std::string, std::string>>{
+ {":status", "200"}, {":path", "/foo"}});
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ EXPECT_THAT(stream_->stream_error(),
+ IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
+}
+
+TEST_P(QuicSpdyClientStreamTest, MissingStatusCode) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ auto headers = AsHeaderList(
+ std::vector<std::pair<std::string, std::string>>{{"key", "value"}});
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ EXPECT_THAT(stream_->stream_error(),
+ IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
+}
+
TEST_P(QuicSpdyClientStreamTest, TestFraming) {
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
diff --git a/quic/core/http/quic_spdy_server_stream_base.cc b/quic/core/http/quic_spdy_server_stream_base.cc
index 93f1c2b..7431d86 100644
--- a/quic/core/http/quic_spdy_server_stream_base.cc
+++ b/quic/core/http/quic_spdy_server_stream_base.cc
@@ -4,9 +4,13 @@
#include "quic/core/http/quic_spdy_server_stream_base.h"
+#include "absl/strings/string_view.h"
+#include "quic/core/http/quic_spdy_session.h"
#include "quic/core/quic_error_codes.h"
-#include "quic/core/quic_session.h"
+#include "quic/platform/api/quic_flag_utils.h"
+#include "quic/platform/api/quic_flags.h"
#include "quic/platform/api/quic_logging.h"
+#include "common/quiche_text_utils.h"
namespace quic {
@@ -44,4 +48,87 @@
QuicSpdyStream::StopReading();
}
+bool QuicSpdyServerStreamBase::AreHeadersValid(
+ const QuicHeaderList& header_list) const {
+ if (!GetQuicReloadableFlag(quic_verify_request_headers)) {
+ return true;
+ }
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers, 3, 3);
+ if (!QuicSpdyStream::AreHeadersValid(header_list)) {
+ return false;
+ }
+
+ bool saw_connect = false;
+ bool saw_protocol = false;
+ bool saw_path = false;
+ bool saw_scheme = false;
+ bool saw_method = false;
+ bool saw_authority = false;
+ bool is_extended_connect = false;
+ // Check if it is missing any required headers and if there is any disallowed
+ // ones.
+ for (const std::pair<std::string, std::string>& pair : header_list) {
+ if (pair.first == ":method") {
+ saw_method = true;
+ if (pair.second == "CONNECT") {
+ saw_connect = true;
+ if (saw_protocol) {
+ is_extended_connect = true;
+ }
+ }
+ } else if (pair.first == ":protocol") {
+ saw_protocol = true;
+ if (saw_connect) {
+ is_extended_connect = true;
+ }
+ } else if (pair.first == ":scheme") {
+ saw_scheme = true;
+ } else if (pair.first == ":path") {
+ saw_path = true;
+ } else if (pair.first == ":authority") {
+ saw_authority = true;
+ } else if (absl::StrContains(pair.first, ":")) {
+ QUIC_DLOG(ERROR) << "Unexpected ':' in header " << pair.first << ".";
+ return false;
+ }
+ if (is_extended_connect) {
+ if (!spdy_session()->allow_extended_connect()) {
+ QUIC_DLOG(ERROR)
+ << "Received extended-CONNECT request while it is disabled.";
+ return false;
+ }
+ } else if (saw_method && !saw_connect) {
+ if (saw_protocol) {
+ QUIC_DLOG(ERROR) << "Receive non-CONNECT request with :protocol.";
+ return false;
+ }
+ }
+ }
+
+ if (is_extended_connect) {
+ if (saw_scheme && saw_path && saw_authority) {
+ // Saw all the required pseudo headers.
+ return true;
+ }
+ QUIC_DLOG(ERROR) << "Missing required pseudo headers for extended-CONNECT.";
+ return false;
+ }
+ // This is a vanilla CONNECT or non-CONNECT request.
+ if (saw_connect) {
+ // Check vanilla CONNECT.
+ if (saw_path || saw_scheme) {
+ QUIC_DLOG(ERROR)
+ << "Received invalid CONNECT request with disallowed pseudo header.";
+ return false;
+ }
+ return true;
+ }
+ // Check non-CONNECT request.
+ if (saw_method && saw_authority && saw_path && saw_scheme) {
+ return true;
+ }
+ QUIC_LOG(ERROR) << "Missing required pseudo headers.";
+ return false;
+}
+
} // namespace quic
diff --git a/quic/core/http/quic_spdy_server_stream_base.h b/quic/core/http/quic_spdy_server_stream_base.h
index 21ecc0b..d10a6d4 100644
--- a/quic/core/http/quic_spdy_server_stream_base.h
+++ b/quic/core/http/quic_spdy_server_stream_base.h
@@ -22,6 +22,9 @@
// when the stream has not received all the data.
void CloseWriteSide() override;
void StopReading() override;
+
+ protected:
+ bool AreHeadersValid(const QuicHeaderList& header_list) const override;
};
} // namespace quic
diff --git a/quic/core/http/quic_spdy_server_stream_base_test.cc b/quic/core/http/quic_spdy_server_stream_base_test.cc
index aa41b3c..92c2d08 100644
--- a/quic/core/http/quic_spdy_server_stream_base_test.cc
+++ b/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -6,7 +6,9 @@
#include "absl/memory/memory.h"
#include "quic/core/crypto/null_encrypter.h"
+#include "quic/platform/api/quic_flags.h"
#include "quic/platform/api/quic_test.h"
+#include "quic/test_tools/qpack/qpack_encoder_test_utils.h"
#include "quic/test_tools/quic_spdy_session_peer.h"
#include "quic/test_tools/quic_stream_peer.h"
#include "quic/test_tools/quic_test_utils.h"
@@ -97,6 +99,230 @@
EXPECT_TRUE(stream_->write_side_closed());
}
+TEST_F(QuicSpdyServerStreamBaseTest, AllowExtendedConnect) {
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ header_list.OnHeader(":protocol", "webtransport");
+ header_list.OnHeader(":path", "/path");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeaderBlockEnd(128, 128);
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_EQ(GetQuicReloadableFlag(quic_verify_request_headers) &&
+ !session_.allow_extended_connect(),
+ stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, AllowExtendedConnectProtocolFirst) {
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":protocol", "webtransport");
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ header_list.OnHeader(":path", "/path");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeaderBlockEnd(128, 128);
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_EQ(GetQuicReloadableFlag(quic_verify_request_headers) &&
+ !session_.allow_extended_connect(),
+ stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidExtendedConnect) {
+ if (!session_.version().UsesHttp3()) {
+ return;
+ }
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ header_list.OnHeader(":protocol", "webtransport");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, VanillaConnectAllowed) {
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ header_list.OnHeaderBlockEnd(128, 128);
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_FALSE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidVanillaConnect) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidNonConnectWithProtocol) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "GET");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeader(":path", "/path");
+ header_list.OnHeader(":protocol", "webtransport");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutScheme) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ // A request without :scheme should be rejected.
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "GET");
+ header_list.OnHeader(":path", "/path");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutAuthority) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ // A request without :authority should be rejected.
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeader(":method", "GET");
+ header_list.OnHeader(":path", "/path");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutMethod) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ // A request without :method should be rejected.
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeader(":path", "/path");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestWithoutPath) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ // A request without :path should be rejected.
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeader(":method", "POST");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, InvalidRequestHeader) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ // A request without :path should be rejected.
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":scheme", "http");
+ header_list.OnHeader(":method", "POST");
+ header_list.OnHeader("invalid:header", "value");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamHeaderList(/*fin=*/false, 0, header_list);
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest, EmptyHeaders) {
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+ spdy::SpdyHeaderBlock empty_header;
+ quic::test::NoopQpackStreamSenderDelegate encoder_stream_sender_delegate;
+ quic::test::NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
+ auto qpack_encoder =
+ std::make_unique<quic::QpackEncoder>(&decoder_stream_error_delegate);
+ qpack_encoder->set_qpack_stream_sender_delegate(
+ &encoder_stream_sender_delegate);
+ std::string payload =
+ qpack_encoder->EncodeHeaderList(stream_->id(), empty_header, nullptr);
+ std::unique_ptr<char[]> headers_buffer;
+ quic::QuicByteCount headers_frame_header_length =
+ quic::HttpEncoder::SerializeHeadersFrameHeader(payload.length(),
+ &headers_buffer);
+ absl::string_view headers_frame_header(headers_buffer.get(),
+ headers_frame_header_length);
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD),
+ _));
+ stream_->OnStreamFrame(QuicStreamFrame(
+ stream_->id(), true, 0, absl::StrCat(headers_frame_header, payload)));
+ EXPECT_TRUE(stream_->rst_sent());
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 0cd2644..929e369 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -453,7 +453,11 @@
spdy_framer_(SpdyFramer::ENABLE_COMPRESSION),
spdy_framer_visitor_(new SpdyFramerVisitor(this)),
debug_visitor_(nullptr),
- destruction_indicator_(123456789) {
+ destruction_indicator_(123456789),
+ allow_extended_connect_(
+ GetQuicReloadableFlag(quic_verify_request_headers) &&
+ perspective() == Perspective::IS_SERVER &&
+ VersionUsesHttp3(transport_version())) {
h2_deframer_.set_visitor(spdy_framer_visitor_.get());
h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
@@ -522,6 +526,10 @@
if (WillNegotiateWebTransport()) {
settings_.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
}
+ if (allow_extended_connect()) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers, 1, 3);
+ settings_.values[SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
+ }
}
void QuicSpdySession::OnDecoderStreamError(QuicErrorCode error_code,
@@ -1034,9 +1042,7 @@
H3SettingsToString(static_cast<Http3AndQpackSettingsIdentifiers>(id)),
" with invalid value ", value);
QUIC_PEER_BUG(bad received setting) << ENDPOINT << error_details;
- // TODO(dschinazi) use QUIC_HTTP_INVALID_SETTING_VALUE instead of
- // QUIC_HTTP_RECEIVE_SPDY_SETTING once cl/396439351 lands.
- CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SPDY_SETTING, error_details);
+ CloseConnectionWithDetails(QUIC_HTTP_INVALID_SETTING_VALUE, error_details);
return false;
}
@@ -1112,6 +1118,18 @@
}
break;
}
+ case SETTINGS_ENABLE_CONNECT_PROTOCOL: {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_ENABLE_CONNECT_PROTOCOL received with value "
+ << value;
+ if (!VerifySettingIsZeroOrOne(id, value)) {
+ return false;
+ }
+ if (perspective() == Perspective::IS_CLIENT) {
+ allow_extended_connect_ = value != 0;
+ }
+ break;
+ }
case spdy::SETTINGS_ENABLE_PUSH:
ABSL_FALLTHROUGH_INTENDED;
case spdy::SETTINGS_MAX_CONCURRENT_STREAMS:
@@ -1735,7 +1753,9 @@
bool QuicSpdySession::SupportsWebTransport() {
return WillNegotiateWebTransport() && SupportsH3Datagram() &&
- peer_supports_webtransport_;
+ peer_supports_webtransport_ &&
+ (!GetQuicReloadableFlag(quic_verify_request_headers) ||
+ allow_extended_connect_);
}
bool QuicSpdySession::SupportsH3Datagram() const {
@@ -1893,6 +1913,25 @@
return os;
}
+// Must not be called after Initialize().
+void QuicSpdySession::set_allow_extended_connect(bool allow_extended_connect) {
+ QUIC_BUG_IF(extended connect wrong version,
+ !GetQuicReloadableFlag(quic_verify_request_headers) ||
+ !VersionUsesHttp3(transport_version()))
+ << "Try to enable/disable extended CONNECT in Google QUIC";
+ QUIC_BUG_IF(extended connect on client,
+ !GetQuicReloadableFlag(quic_verify_request_headers) ||
+ perspective() == Perspective::IS_CLIENT)
+ << "Enabling/disabling extended CONNECT on the client side has no effect";
+ if (ShouldNegotiateWebTransport()) {
+ QUIC_BUG_IF(disable extended connect, !allow_extended_connect)
+ << "Disabling extended CONNECT with web transport enabled has no "
+ "effect.";
+ return;
+ }
+ allow_extended_connect_ = allow_extended_connect;
+}
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index d8503cc..b84f5a1 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -283,12 +283,16 @@
qpack_maximum_blocked_streams_ = qpack_maximum_blocked_streams;
}
+ // Should only be used by IETF QUIC server side.
// Must not be called after Initialize().
// TODO(bnc): Move to constructor argument.
void set_max_inbound_header_list_size(size_t max_inbound_header_list_size) {
max_inbound_header_list_size_ = max_inbound_header_list_size;
}
+ // Must not be called after Initialize().
+ void set_allow_extended_connect(bool allow_extended_connect);
+
size_t max_outbound_header_list_size() const {
return max_outbound_header_list_size_;
}
@@ -297,6 +301,8 @@
return max_inbound_header_list_size_;
}
+ bool allow_extended_connect() const { return allow_extended_connect_; }
+
// Returns true if the session has active request streams.
bool HasActiveRequestStreams() const;
@@ -684,6 +690,10 @@
// Limited to kMaxUnassociatedWebTransportStreams; when the list is full,
// oldest streams are evicated first.
std::list<BufferedWebTransportStream> buffered_streams_;
+
+ // On the server side, if true, advertise and accept extended CONNECT method.
+ // On the client side, true if the peer advertised extended CONNECT.
+ bool allow_extended_connect_;
};
} // namespace quic
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 5c3e525..23611b3 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -238,7 +238,6 @@
CurrentSupportedVersions()),
crypto_stream_(this),
writev_consumes_all_data_(false) {
- Initialize();
this->connection()->SetEncrypter(
ENCRYPTION_FORWARD_SECURE,
std::make_unique<NullEncrypter>(connection->perspective()));
@@ -387,11 +386,18 @@
}
protected:
- explicit QuicSpdySessionTestBase(Perspective perspective)
+ explicit QuicSpdySessionTestBase(Perspective perspective,
+ bool allow_extended_connect)
: connection_(new StrictMock<MockQuicConnection>(
&helper_, &alarm_factory_, perspective,
SupportedVersions(GetParam()))),
session_(connection_) {
+ if (perspective == Perspective::IS_SERVER &&
+ VersionUsesHttp3(transport_version()) &&
+ GetQuicReloadableFlag(quic_verify_request_headers)) {
+ session_.set_allow_extended_connect(allow_extended_connect);
+ }
+ session_.Initialize();
session_.config()->SetInitialStreamFlowControlWindowToSend(
kInitialStreamFlowControlWindowForTest);
session_.config()->SetInitialSessionFlowControlWindowToSend(
@@ -547,6 +553,7 @@
SettingsFrame settings;
settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
settings.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
+ settings.values[SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
std::string data =
std::string(1, kControlStream) + EncodeSettings(settings);
QuicStreamId control_stream_id =
@@ -612,7 +619,7 @@
class QuicSpdySessionTestServer : public QuicSpdySessionTestBase {
protected:
QuicSpdySessionTestServer()
- : QuicSpdySessionTestBase(Perspective::IS_SERVER) {}
+ : QuicSpdySessionTestBase(Perspective::IS_SERVER, true) {}
};
INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdySessionTestServer,
@@ -1858,7 +1865,7 @@
class QuicSpdySessionTestClient : public QuicSpdySessionTestBase {
protected:
QuicSpdySessionTestClient()
- : QuicSpdySessionTestBase(Perspective::IS_CLIENT) {}
+ : QuicSpdySessionTestBase(Perspective::IS_CLIENT, false) {}
};
INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdySessionTestClient,
@@ -3550,17 +3557,10 @@
session_.set_debug_visitor(&debug_visitor);
CompleteHandshake();
- SettingsFrame server_settings;
- server_settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
- server_settings.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
- std::string data =
- std::string(1, kControlStream) + EncodeSettings(server_settings);
- QuicStreamId stream_id =
- GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3);
- QuicStreamFrame frame(stream_id, /*fin=*/false, /*offset=*/0, data);
- EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(stream_id));
- EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(server_settings));
- session_.OnStreamFrame(frame);
+ EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(_));
+ EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+ ReceiveWebTransportSettings();
+ EXPECT_TRUE(session_.ShouldProcessIncomingRequests());
EXPECT_TRUE(session_.SupportsWebTransport());
}
@@ -3720,6 +3720,63 @@
EXPECT_EQ(web_transport->NumberOfAssociatedStreams(), 0u);
}
+class QuicSpdySessionTestServerNoExtendedConnect
+ : public QuicSpdySessionTestBase {
+ public:
+ QuicSpdySessionTestServerNoExtendedConnect()
+ : QuicSpdySessionTestBase(Perspective::IS_SERVER, false) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdySessionTestServerNoExtendedConnect,
+ ::testing::ValuesIn(AllSupportedVersions()),
+ ::testing::PrintToStringParamName());
+
+// Tests that receiving SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 doesn't enable
+// server session to support extended CONNECT.
+TEST_P(QuicSpdySessionTestServerNoExtendedConnect,
+ WebTransportSettingNoEffect) {
+ if (!version().UsesHttp3()) {
+ return;
+ }
+
+ EXPECT_FALSE(session_.SupportsWebTransport());
+ EXPECT_TRUE(session_.ShouldProcessIncomingRequests());
+
+ CompleteHandshake();
+
+ ReceiveWebTransportSettings();
+ EXPECT_FALSE(session_.allow_extended_connect());
+ EXPECT_FALSE(session_.SupportsWebTransport());
+ EXPECT_TRUE(session_.ShouldProcessIncomingRequests());
+}
+
+TEST_P(QuicSpdySessionTestServerNoExtendedConnect, BadExtendedConnectSetting) {
+ if (!version().UsesHttp3()) {
+ return;
+ }
+ SetQuicReloadableFlag(quic_verify_request_headers, true);
+
+ EXPECT_FALSE(session_.SupportsWebTransport());
+ EXPECT_TRUE(session_.ShouldProcessIncomingRequests());
+
+ CompleteHandshake();
+
+ // ENABLE_CONNECT_PROTOCOL setting value has to be 1 or 0;
+ SettingsFrame settings;
+ settings.values[SETTINGS_ENABLE_CONNECT_PROTOCOL] = 2;
+ std::string data = std::string(1, kControlStream) + EncodeSettings(settings);
+ QuicStreamId control_stream_id =
+ session_.perspective() == Perspective::IS_SERVER
+ ? GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3)
+ : GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3);
+ QuicStreamFrame frame(control_stream_id, /*fin=*/false, /*offset=*/0, data);
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_INVALID_SETTING_VALUE, _, _));
+ EXPECT_QUIC_PEER_BUG(
+ session_.OnStreamFrame(frame),
+ "Received SETTINGS_ENABLE_CONNECT_PROTOCOL with invalid value");
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index c25d33a..284a180 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -606,8 +606,22 @@
// TODO(b/134706391): remove |fin| argument.
headers_decompressed_ = true;
header_list_ = header_list;
+ bool header_too_large = VersionUsesHttp3(transport_version())
+ ? header_list_size_limit_exceeded_
+ : header_list.empty();
+ // Validate request headers if it did not exceed size limit. If it did,
+ // OnHeadersTooLarge() should have already handled it previously.
+ if (!header_too_large && !AreHeadersValid(header_list)) {
+ QUIC_CODE_COUNT_N(quic_validate_request_header, 1, 2);
+ OnInvalidHeaders();
+ return;
+ }
+ QUIC_CODE_COUNT_N(quic_validate_request_header, 2, 2);
- MaybeProcessReceivedWebTransportHeaders();
+ if (!GetQuicReloadableFlag(quic_verify_request_headers) ||
+ !header_too_large) {
+ MaybeProcessReceivedWebTransportHeaders();
+ }
if (VersionUsesHttp3(transport_version())) {
if (fin) {
@@ -1760,5 +1774,14 @@
}
}
+bool QuicSpdyStream::AreHeadersValid(
+ const QuicHeaderList& /*header_list*/) const {
+ // TODO(b/202433856) check each header name to be valid token and isn't a
+ // disallowed header.
+ return true;
+}
+
+void QuicSpdyStream::OnInvalidHeaders() { Reset(QUIC_BAD_APPLICATION_PAYLOAD); }
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index 7ea2d1d..cd9d99b 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -371,6 +371,11 @@
void OnWriteSideInDataRecvdState() override;
+ virtual bool AreHeadersValid(const QuicHeaderList& header_list) const;
+
+ // Reset stream upon invalid request headers.
+ virtual void OnInvalidHeaders();
+
private:
friend class test::QuicSpdyStreamPeer;
friend class test::QuicStreamPeer;
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 48b654b..b5905e7 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -3047,6 +3047,7 @@
InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04);
session_->EnableWebTransport();
+ session_->OnSetting(SETTINGS_ENABLE_CONNECT_PROTOCOL, 1);
QuicSpdySessionPeer::EnableWebTransport(session_.get());
QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
HttpDatagramSupport::kDraft00);
@@ -3072,6 +3073,7 @@
InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04);
session_->EnableWebTransport();
+ session_->OnSetting(SETTINGS_ENABLE_CONNECT_PROTOCOL, 1);
QuicSpdySessionPeer::EnableWebTransport(session_.get());
QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
HttpDatagramSupport::kDraft04);
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 162cb04..6ecc76b 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -239,6 +239,7 @@
RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_SETTING);
RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_FRAME);
RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SERVER_PUSH);
+ RETURN_STRING_LITERAL(QUIC_HTTP_INVALID_SETTING_VALUE);
RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR);
@@ -694,6 +695,8 @@
return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::ID_ERROR)};
case QUIC_HTTP_RECEIVE_SPDY_SETTING:
return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)};
+ case QUIC_HTTP_INVALID_SETTING_VALUE:
+ return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)};
case QUIC_HTTP_RECEIVE_SPDY_FRAME:
return {false,
static_cast<uint64_t>(QuicHttp3ErrorCode::FRAME_UNEXPECTED)};
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 860f107..7e4fd09 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -513,6 +513,8 @@
// HTTP/3 session received SERVER_PUSH stream, which is an error because
// PUSH_PROMISE is not accepted.
QUIC_HTTP_RECEIVE_SERVER_PUSH = 205,
+ // HTTP/3 session received invalid SETTING value.
+ QUIC_HTTP_INVALID_SETTING_VALUE = 207,
// HPACK header block decoding errors.
// Index varint beyond implementation limit.
@@ -603,7 +605,7 @@
QUIC_INVALID_CHARACTER_IN_FIELD_VALUE = 206,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 207,
+ QUIC_LAST_ERROR = 208,
};
// QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
// or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 4331652..994bbdd 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -85,6 +85,8 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, true)
// If true, quic dispatcher supports one connection to use multiple connection IDs.
QUIC_FLAG(FLAGS_quic_restart_flag_quic_dispatcher_support_multiple_cid_per_connection_v2, true)
+// If true, quic server will send ENABLE_CONNECT_PROTOCOL setting and and endpoint will validate required request/response headers and extended CONNECT mechanism.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_verify_request_headers, false)
// If true, record addresses that server has sent reset to recently, and do not send reset if the address lives in the set.
QUIC_FLAG(FLAGS_quic_restart_flag_quic_use_recent_reset_addresses, true)
// If true, refactor how QUIC TLS server disables resumption. No behavior change.
diff --git a/quic/test_tools/quic_test_backend.h b/quic/test_tools/quic_test_backend.h
index 66e1213..6aa175e 100644
--- a/quic/test_tools/quic_test_backend.h
+++ b/quic/test_tools/quic_test_backend.h
@@ -6,6 +6,7 @@
#define QUICHE_QUIC_TEST_TOOLS_QUIC_TEST_BACKEND_H_
#include "quic/tools/quic_memory_cache_backend.h"
+#include "common/platform/api/quiche_logging.h"
namespace quic {
namespace test {
@@ -21,11 +22,19 @@
bool SupportsWebTransport() override { return enable_webtransport_; }
void set_enable_webtransport(bool enable_webtransport) {
+ QUICHE_DCHECK(!enable_webtransport || enable_extended_connect_);
enable_webtransport_ = enable_webtransport;
}
+ bool SupportsExtendedConnect() override { return enable_extended_connect_; }
+
+ void set_enable_extended_connect(bool enable_extended_connect) {
+ enable_extended_connect_ = enable_extended_connect;
+ }
+
private:
bool enable_webtransport_ = false;
+ bool enable_extended_connect_ = true;
};
} // namespace test
diff --git a/quic/test_tools/quic_test_server.cc b/quic/test_tools/quic_test_server.cc
index f6e2c34..020a34c 100644
--- a/quic/test_tools/quic_test_server.cc
+++ b/quic/test_tools/quic_test_server.cc
@@ -94,25 +94,24 @@
crypto_stream_factory_(nullptr) {}
std::unique_ptr<QuicSession> CreateQuicSession(
- QuicConnectionId id,
- const QuicSocketAddress& self_address,
- const QuicSocketAddress& peer_address,
- absl::string_view alpn,
- const ParsedQuicVersion& version,
- absl::string_view sni) override {
+ QuicConnectionId id, const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address, absl::string_view /*alpn*/,
+ const ParsedQuicVersion& version, absl::string_view /*sni*/) override {
QuicReaderMutexLock lock(&factory_lock_);
- if (session_factory_ == nullptr && stream_factory_ == nullptr &&
- crypto_stream_factory_ == nullptr) {
- return QuicSimpleDispatcher::CreateQuicSession(
- id, self_address, peer_address, alpn, version, sni);
- }
+ // The QuicServerSessionBase takes ownership of |connection| below.
QuicConnection* connection = new QuicConnection(
id, self_address, peer_address, helper(), alarm_factory(), writer(),
/* owns_writer= */ false, Perspective::IS_SERVER,
ParsedQuicVersionVector{version});
std::unique_ptr<QuicServerSessionBase> session;
- if (stream_factory_ != nullptr || crypto_stream_factory_ != nullptr) {
+ if (session_factory_ == nullptr && stream_factory_ == nullptr &&
+ crypto_stream_factory_ == nullptr) {
+ session = std::make_unique<QuicSimpleServerSession>(
+ config(), GetSupportedVersions(), connection, this, session_helper(),
+ crypto_config(), compressed_certs_cache(), server_backend());
+ } else if (stream_factory_ != nullptr ||
+ crypto_stream_factory_ != nullptr) {
session = std::make_unique<CustomStreamSession>(
config(), GetSupportedVersions(), connection, this, session_helper(),
crypto_config(), compressed_certs_cache(), stream_factory_,
@@ -122,6 +121,14 @@
config(), connection, this, session_helper(), crypto_config(),
compressed_certs_cache(), server_backend());
}
+ if (VersionUsesHttp3(version.transport_version) &&
+ GetQuicReloadableFlag(quic_verify_request_headers)) {
+ QUICHE_DCHECK(session->allow_extended_connect());
+ // Do not allow extended CONNECT request if the backend doesn't support
+ // it.
+ session->set_allow_extended_connect(
+ server_backend()->SupportsExtendedConnect());
+ }
session->Initialize();
return session;
}
diff --git a/quic/tools/quic_simple_server_backend.h b/quic/tools/quic_simple_server_backend.h
index 6411567..67aeac9 100644
--- a/quic/tools/quic_simple_server_backend.h
+++ b/quic/tools/quic_simple_server_backend.h
@@ -67,6 +67,7 @@
return response;
}
virtual bool SupportsWebTransport() { return false; }
+ virtual bool SupportsExtendedConnect() { return true; }
};
} // namespace quic
diff --git a/quic/tools/quic_simple_server_stream.cc b/quic/tools/quic_simple_server_stream.cc
index d7e2a70..84e7071 100644
--- a/quic/tools/quic_simple_server_stream.cc
+++ b/quic/tools/quic_simple_server_stream.cc
@@ -59,7 +59,11 @@
if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
&request_headers_)) {
QUIC_DVLOG(1) << "Invalid headers";
- SendErrorResponse();
+ if (!response_sent_) {
+ // QuicSpdyStream::OnInitialHeadersComplete() may have already sent error
+ // response.
+ SendErrorResponse();
+ }
}
ConsumeHeaderList();
if (!fin && !response_sent_) {
@@ -344,6 +348,9 @@
void QuicSimpleServerStream::SendErrorResponse(int resp_code) {
QUIC_DVLOG(1) << "Stream " << id() << " sending error response.";
+ if (!reading_stopped()) {
+ StopReading();
+ }
Http2HeaderBlock headers;
if (resp_code <= 0) {
headers[":status"] = "500";
@@ -411,6 +418,11 @@
WriteTrailers(std::move(response_trailers), nullptr);
}
+void QuicSimpleServerStream::OnInvalidHeaders() {
+ QUIC_DVLOG(1) << "Invalid headers";
+ SendErrorResponse(400);
+}
+
const char* const QuicSimpleServerStream::kErrorResponseBody = "bad";
const char* const QuicSimpleServerStream::kNotFoundResponseBody =
"file not found";
diff --git a/quic/tools/quic_simple_server_stream.h b/quic/tools/quic_simple_server_stream.h
index d138492..cd04bd7 100644
--- a/quic/tools/quic_simple_server_stream.h
+++ b/quic/tools/quic_simple_server_stream.h
@@ -43,6 +43,8 @@
// data (or a FIN) to be read.
void OnBodyAvailable() override;
+ void OnInvalidHeaders() override;
+
// Make this stream start from as if it just finished parsing an incoming
// request whose headers are equivalent to |push_request_headers|.
// Doing so will trigger this toy stream to fetch response and send it back.
@@ -66,7 +68,7 @@
// Sends a basic 500 response using SendHeaders for the headers and WriteData
// for the body.
virtual void SendErrorResponse();
- void SendErrorResponse(int resp_code);
+ virtual void SendErrorResponse(int resp_code);
// Sends a basic 404 response using SendHeaders for the headers and WriteData
// for the body.
diff --git a/quic/tools/quic_simple_server_stream_test.cc b/quic/tools/quic_simple_server_stream_test.cc
index aa07703..b75246f 100644
--- a/quic/tools/quic_simple_server_stream_test.cc
+++ b/quic/tools/quic_simple_server_stream_test.cc
@@ -71,7 +71,7 @@
// Expose protected QuicSimpleServerStream methods.
void DoSendResponse() { SendResponse(); }
- void DoSendErrorResponse() { SendErrorResponse(); }
+ void DoSendErrorResponse() { QuicSimpleServerStream::SendErrorResponse(); }
spdy::Http2HeaderBlock* mutable_headers() { return &request_headers_; }
void set_body(std::string body) { body_ = std::move(body); }
@@ -94,9 +94,9 @@
QuicSimpleServerStream::SendResponse();
}
- void SendErrorResponse() override {
+ void SendErrorResponse(int resp_code) override {
send_error_response_was_called_ = true;
- QuicSimpleServerStream::SendErrorResponse();
+ QuicSimpleServerStream::SendErrorResponse(resp_code);
}
private:
@@ -208,6 +208,7 @@
header_list_.OnHeader(":authority", "www.google.com");
header_list_.OnHeader(":path", "/");
header_list_.OnHeader(":method", "POST");
+ header_list_.OnHeader(":scheme", "https");
header_list_.OnHeader("content-length", "11");
header_list_.OnHeaderBlockEnd(128, 128);
@@ -737,7 +738,7 @@
QuicHeaderList header_list;
header_list.OnHeaderBlockStart();
header_list.OnHeader(":authority", "www.google.com:4433");
- header_list.OnHeader(":method", "CONNECT-SILLY");
+ header_list.OnHeader(":method", "CONNECT");
header_list.OnHeaderBlockEnd(128, 128);
EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false));
stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list);
@@ -747,7 +748,7 @@
UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
- EXPECT_EQ("CONNECT-SILLY", StreamHeadersValue(":method"));
+ EXPECT_EQ("CONNECT", StreamHeadersValue(":method"));
EXPECT_EQ(body_, StreamBody());
EXPECT_TRUE(stream_->send_response_was_called());
EXPECT_FALSE(stream_->send_error_response_was_called());
@@ -760,20 +761,25 @@
QuicHeaderList header_list;
header_list.OnHeaderBlockStart();
header_list.OnHeader(":authority", "www.google.com:4433");
- header_list.OnHeader(":method", "CONNECT-SILLY");
+ header_list.OnHeader(":method", "CONNECT");
// QUIC requires lower-case header names.
header_list.OnHeader("InVaLiD-HeAdEr", "Well that's just wrong!");
header_list.OnHeaderBlockEnd(128, 128);
+
+ if (UsesHttp3()) {
+ EXPECT_CALL(session_,
+ MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+ QUIC_STREAM_NO_ERROR)))
+ .Times(1);
+ } else {
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_STREAM_NO_ERROR), _))
+ .Times(1);
+ }
EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false));
stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list);
- QuicBuffer header = HttpEncoder::SerializeDataFrameHeader(
- body_.length(), SimpleBufferAllocator::Get());
- std::string data =
- UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
- stream_->OnStreamFrame(
- QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
- EXPECT_EQ("CONNECT-SILLY", StreamHeadersValue(":method"));
- EXPECT_EQ(body_, StreamBody());
EXPECT_FALSE(stream_->send_response_was_called());
EXPECT_TRUE(stream_->send_error_response_was_called());
}