Switch WebTransport from using tokens to using strings
See https://github.com/ietf-wg-webtrans/draft-ietf-webtrans-http3/pull/181
This is a backwards-incompatible change
PiperOrigin-RevId: 773667376
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index cc935f0..7b4ae68 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -7327,7 +7327,7 @@
WebTransportHttp3* session = CreateWebTransportSession(
"/selected-subprotocol", /*wait_for_server_response=*/true,
- {{webtransport::kSubprotocolRequestHeader, "a, b, c, d"},
+ {{webtransport::kSubprotocolRequestHeader, R"("a", "b", "c", "d")"},
{"subprotocol-index", "1"}});
ASSERT_NE(session, nullptr);
NiceMock<MockWebTransportSessionVisitor>& visitor =
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 1536a57..95e3ffb 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -3309,7 +3309,7 @@
quiche::HttpHeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":protocol"] = "webtransport";
- request_headers["wt-available-protocols"] = "moqt-00, moqt-01; foo=bar";
+ request_headers["wt-available-protocols"] = R"("moqt-00", "moqt-01"; a=b)";
stream_->WriteHeaders(std::move(request_headers), /*fin=*/false, nullptr);
ASSERT_TRUE(stream_->web_transport() != nullptr);
EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
@@ -3318,7 +3318,7 @@
quiche::HttpHeaderBlock response_headers;
response_headers[":status"] = "200";
- response_headers["wt-protocol"] = "moqt-01";
+ response_headers["wt-protocol"] = "\"moqt-01\"";
stream_->web_transport()->HeadersReceived(response_headers);
EXPECT_EQ(stream_->web_transport()->rejection_reason(),
WebTransportHttp3RejectionReason::kNone);
@@ -3345,13 +3345,13 @@
quiche::HttpHeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":protocol"] = "webtransport";
- request_headers["wt-available-protocols"] = "moqt-00, moqt-01; foo=bar";
+ request_headers["wt-available-protocols"] = R"("moqt-00", "moqt-01"; a=b)";
stream_->WriteHeaders(std::move(request_headers), /*fin=*/false, nullptr);
ASSERT_TRUE(stream_->web_transport() != nullptr);
quiche::HttpHeaderBlock response_headers;
response_headers[":status"] = "200";
- response_headers["wt-protocol"] = "moqt-02";
+ response_headers["wt-protocol"] = "\"moqt-02\"";
stream_->web_transport()->HeadersReceived(response_headers);
EXPECT_EQ(stream_->web_transport()->rejection_reason(),
WebTransportHttp3RejectionReason::kSubprotocolMismatch);
@@ -3378,7 +3378,7 @@
quiche::HttpHeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":protocol"] = "webtransport";
- request_headers["wt-available-protocols"] = "moqt-00, moqt-01; foo=bar";
+ request_headers["wt-available-protocols"] = R"("moqt-00", "moqt-01"; a=b)";
stream_->WriteHeaders(std::move(request_headers), /*fin=*/false, nullptr);
ASSERT_TRUE(stream_->web_transport() != nullptr);
@@ -3405,7 +3405,7 @@
headers_[":method"] = "CONNECT";
headers_[":protocol"] = "webtransport";
- headers_["wt-available-protocols"] = "moqt-00, moqt-01; foo=bar";
+ headers_["wt-available-protocols"] = R"("moqt-00", "moqt-01"; a=b)";
stream_->OnStreamHeadersPriority(
spdy::SpdyStreamPrecedence(kV3HighestPriority));
@@ -3421,7 +3421,7 @@
.Times(AnyNumber());
quiche::HttpHeaderBlock response_headers;
response_headers[":status"] = "200";
- response_headers["wt-protocol"] = "moqt-01";
+ response_headers["wt-protocol"] = "\"moqt-01\"";
stream_->WriteHeaders(std::move(response_headers), /*fin=*/false, nullptr);
EXPECT_EQ(stream_->web_transport()->GetNegotiatedSubprotocol(), "moqt-01");
}
diff --git a/quiche/quic/test_tools/quic_test_backend.cc b/quiche/quic/test_tools/quic_test_backend.cc
index c40db49..c901984 100644
--- a/quiche/quic/test_tools/quic_test_backend.cc
+++ b/quiche/quic/test_tools/quic_test_backend.cc
@@ -183,10 +183,18 @@
return response;
}
}
+ absl::StatusOr<std::string> response_subprotocol =
+ webtransport::SerializeSubprotocolResponseHeader(
+ (*subprotocols)[subprotocol_index]);
+ if (!response_subprotocol.ok()) {
+ WebTransportResponse response;
+ response.response_headers[":status"] = "400";
+ return response;
+ }
WebTransportResponse response;
response.response_headers[":status"] = "200";
response.response_headers[webtransport::kSubprotocolResponseHeader] =
- (*subprotocols)[subprotocol_index];
+ *response_subprotocol;
response.visitor = std::make_unique<SubprotocolStreamVisitor>(session);
return response;
}
diff --git a/quiche/web_transport/web_transport_headers.cc b/quiche/web_transport/web_transport_headers.cc
index 2ae7bf1..ed99cac 100644
--- a/quiche/web_transport/web_transport_headers.cc
+++ b/quiche/web_transport/web_transport_headers.cc
@@ -11,12 +11,13 @@
#include <utility>
#include <vector>
+#include "absl/algorithm/container.h"
#include "absl/base/attributes.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
+#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/common/quiche_status_utils.h"
@@ -70,7 +71,7 @@
std::vector<std::string> result;
result.reserve(parsed->size());
for (ParameterizedMember& member : *parsed) {
- QUICHE_RETURN_IF_ERROR(CheckMemberType(member, Item::kTokenType));
+ QUICHE_RETURN_IF_ERROR(CheckMemberType(member, Item::kStringType));
result.push_back(std::move(member.member[0].item).TakeString());
}
return result;
@@ -78,14 +79,18 @@
absl::StatusOr<std::string> SerializeSubprotocolRequestHeader(
absl::Span<const std::string> subprotocols) {
- // Serialize tokens manually via a simple StrJoin call; this lets us provide
- // better error messages, and is probably more efficient too.
- for (const std::string& token : subprotocols) {
- if (!quiche::structured_headers::IsValidToken(token)) {
- return absl::InvalidArgumentError(absl::StrCat("Invalid token: ", token));
- }
+ quiche::structured_headers::List list;
+ list.reserve(subprotocols.size());
+ for (const std::string& subprotocol : subprotocols) {
+ list.push_back(ParameterizedMember(Item(subprotocol), {}));
}
- return absl::StrJoin(subprotocols, ", ");
+
+ std::optional<std::string> serialized =
+ quiche::structured_headers::SerializeList(list);
+ if (!serialized.has_value()) {
+ return absl::InvalidArgumentError("Invalid subprotocol list supplied");
+ }
+ return *std::move(serialized);
}
absl::StatusOr<std::string> ParseSubprotocolResponseHeader(
@@ -95,20 +100,24 @@
if (!parsed.has_value()) {
return absl::InvalidArgumentError("Failed to parse sf-item");
}
- QUICHE_RETURN_IF_ERROR(CheckItemType(*parsed, Item::kTokenType));
+ QUICHE_RETURN_IF_ERROR(CheckItemType(*parsed, Item::kStringType));
return std::move(parsed->item).TakeString();
}
absl::StatusOr<std::string> SerializeSubprotocolResponseHeader(
absl::string_view subprotocol) {
- if (!quiche::structured_headers::IsValidToken(subprotocol)) {
- return absl::InvalidArgumentError("Invalid token value supplied");
+ Item item(std::string(subprotocol), Item::kStringType);
+ std::optional<std::string> serialized =
+ quiche::structured_headers::SerializeItem(item);
+ if (!serialized.has_value()) {
+ return absl::InvalidArgumentError("Invalid subprotocol name supplied");
}
- return std::string(subprotocol);
+ return *std::move(serialized);
}
bool ValidateSubprotocolName(absl::string_view name) {
- return !name.empty() && quiche::structured_headers::IsValidToken(name);
+ return !name.empty() &&
+ absl::c_all_of(name, [](char c) { return absl::ascii_isprint(c); });
}
template <typename S>
diff --git a/quiche/web_transport/web_transport_headers_test.cc b/quiche/web_transport/web_transport_headers_test.cc
index d85e1f8..da910d0 100644
--- a/quiche/web_transport/web_transport_headers_test.cc
+++ b/quiche/web_transport/web_transport_headers_test.cc
@@ -22,27 +22,30 @@
using ::testing::HasSubstr;
TEST(WebTransportHeaders, ParseSubprotocolRequestHeader) {
- EXPECT_THAT(ParseSubprotocolRequestHeader("test"),
- IsOkAndHolds(ElementsAre("test")));
- EXPECT_THAT(ParseSubprotocolRequestHeader("moqt-draft01, moqt-draft02"),
- IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
- EXPECT_THAT(ParseSubprotocolRequestHeader("moqt-draft01; a=b, moqt-draft02"),
- IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
- EXPECT_THAT(ParseSubprotocolRequestHeader("moqt-draft01, moqt-draft02; a=b"),
- IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
EXPECT_THAT(ParseSubprotocolRequestHeader("\"test\""),
+ IsOkAndHolds(ElementsAre("test")));
+ EXPECT_THAT(
+ ParseSubprotocolRequestHeader(R"("moqt-draft01", "moqt-draft02")"),
+ IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
+ EXPECT_THAT(
+ ParseSubprotocolRequestHeader(R"("moqt-draft01"; a=b, "moqt-draft02")"),
+ IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
+ EXPECT_THAT(
+ ParseSubprotocolRequestHeader(R"("moqt-draft01", "moqt-draft02"; a=b)"),
+ IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
+ EXPECT_THAT(ParseSubprotocolRequestHeader("test"),
StatusIs(absl::StatusCode::kInvalidArgument,
- HasSubstr("found string instead")));
+ HasSubstr("found token instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("42"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found integer instead")));
- EXPECT_THAT(ParseSubprotocolRequestHeader("a, (b)"),
+ EXPECT_THAT(ParseSubprotocolRequestHeader("\"a\", (b)"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found a nested list instead")));
- EXPECT_THAT(ParseSubprotocolRequestHeader("a, (b c)"),
+ EXPECT_THAT(ParseSubprotocolRequestHeader("\"a\", (b c)"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found a nested list instead")));
- EXPECT_THAT(ParseSubprotocolRequestHeader("foo, ?1, bar"),
+ EXPECT_THAT(ParseSubprotocolRequestHeader("\"foo\", ?1, bar"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found boolean instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("(a"),
@@ -52,19 +55,19 @@
TEST(WebTransportHeaders, SerializeSubprotocolRequestHeader) {
EXPECT_THAT(SerializeSubprotocolRequestHeader({"test"}),
- IsOkAndHolds("test"));
+ IsOkAndHolds(R"("test")"));
EXPECT_THAT(SerializeSubprotocolRequestHeader({"foo", "bar"}),
- IsOkAndHolds("foo, bar"));
- EXPECT_THAT(SerializeSubprotocolRequestHeader({"moqt-draft01", "a/b/c"}),
- IsOkAndHolds("moqt-draft01, a/b/c"));
- EXPECT_THAT(
- SerializeSubprotocolRequestHeader({"abcd", "0123", "efgh"}),
- StatusIs(absl::StatusCode::kInvalidArgument, "Invalid token: 0123"));
+ IsOkAndHolds(R"("foo", "bar")"));
+ EXPECT_THAT(SerializeSubprotocolRequestHeader({"a\"b", "a/b/c"}),
+ IsOkAndHolds(R"("a\"b", "a/b/c")"));
+ EXPECT_THAT(SerializeSubprotocolRequestHeader({"abcd", "\n", "efgh"}),
+ StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(WebTransportHeader, ParseSubprotocolResponseHeader) {
- EXPECT_THAT(ParseSubprotocolResponseHeader("foo"), IsOkAndHolds("foo"));
- EXPECT_THAT(ParseSubprotocolResponseHeader("foo; a=b"), IsOkAndHolds("foo"));
+ EXPECT_THAT(ParseSubprotocolResponseHeader("\"foo\""), IsOkAndHolds("foo"));
+ EXPECT_THAT(ParseSubprotocolResponseHeader("\"foo\"; a=b"),
+ IsOkAndHolds("foo"));
EXPECT_THAT(
ParseSubprotocolResponseHeader("1234"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("found integer")));
@@ -74,10 +77,11 @@
}
TEST(WebTransportHeader, SerializeSubprotocolResponseHeader) {
- EXPECT_THAT(SerializeSubprotocolResponseHeader("foo"), IsOkAndHolds("foo"));
+ EXPECT_THAT(SerializeSubprotocolResponseHeader("foo"),
+ IsOkAndHolds("\"foo\""));
EXPECT_THAT(SerializeSubprotocolResponseHeader("moqt-draft01"),
- IsOkAndHolds("moqt-draft01"));
- EXPECT_THAT(SerializeSubprotocolResponseHeader("123abc"),
+ IsOkAndHolds("\"moqt-draft01\""));
+ EXPECT_THAT(SerializeSubprotocolResponseHeader("\xff"),
StatusIs(absl::StatusCode::kInvalidArgument));
}
@@ -133,12 +137,13 @@
TEST(WebTransportHeaders, ValidateSubprotocolName) {
EXPECT_TRUE(ValidateSubprotocolName("test"));
- EXPECT_FALSE(ValidateSubprotocolName("123"));
+ EXPECT_TRUE(ValidateSubprotocolName("123"));
+ EXPECT_FALSE(ValidateSubprotocolName("\n"));
EXPECT_FALSE(ValidateSubprotocolName(""));
EXPECT_TRUE(ValidateSubprotocolListHelper({}));
EXPECT_TRUE(ValidateSubprotocolListHelper({"a", "b", "c"}));
- EXPECT_FALSE(ValidateSubprotocolListHelper({"a", "1", "c"}));
+ EXPECT_FALSE(ValidateSubprotocolListHelper({"a", "\n", "c"}));
EXPECT_FALSE(ValidateSubprotocolListHelper({"a", "b", "a"}));
std::vector<std::string> vec = {"a", "b"};