Update CONNECT-UDP code to draft-06
This code is not used in production is therefore not flag-protected.
PiperOrigin-RevId: 406233409
diff --git a/common/platform/api/quiche_url_utils.h b/common/platform/api/quiche_url_utils.h
new file mode 100644
index 0000000..126c17a
--- /dev/null
+++ b/common/platform/api/quiche_url_utils.h
@@ -0,0 +1,30 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_URL_UTILS_H_
+#define QUICHE_COMMON_PLATFORM_API_QUICHE_URL_UTILS_H_
+
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "quiche_platform_impl/quiche_url_utils_impl.h"
+
+namespace quiche {
+
+// Produces concrete URLs in |target| from templated ones in |uri_template|.
+// Parameters are URL-encoded. Collects the names of any expanded variables in
+// |vars_found|. Returns true if the template was parseable, false if it was
+// malformed.
+inline bool ExpandURITemplate(
+ const std::string& uri_template,
+ const absl::flat_hash_map<std::string, std::string>& parameters,
+ std::string* target,
+ absl::flat_hash_set<std::string>* vars_found = nullptr) {
+ return ExpandURITemplateImpl(uri_template, parameters, target, vars_found);
+}
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_PLATFORM_API_QUICHE_URL_UTILS_H_
diff --git a/common/platform/api/quiche_url_utils_test.cc b/common/platform/api/quiche_url_utils_test.cc
new file mode 100644
index 0000000..e99fb9d
--- /dev/null
+++ b/common/platform/api/quiche_url_utils_test.cc
@@ -0,0 +1,56 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "common/platform/api/quiche_url_utils.h"
+
+#include <set>
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace {
+
+void ValidateExpansion(
+ const std::string& uri_template,
+ const absl::flat_hash_map<std::string, std::string>& parameters,
+ const std::string& expected_expansion,
+ const absl::flat_hash_set<std::string>& expected_vars_found) {
+ absl::flat_hash_set<std::string> vars_found;
+ std::string target;
+ ASSERT_TRUE(
+ ExpandURITemplate(uri_template, parameters, &target, &vars_found));
+ EXPECT_EQ(expected_expansion, target);
+ EXPECT_EQ(vars_found, expected_vars_found);
+}
+
+TEST(QuicheUrlUtilsTest, Basic) {
+ ValidateExpansion("/{foo}/{bar}/", {{"foo", "123"}, {"bar", "456"}},
+ "/123/456/", {"foo", "bar"});
+}
+
+TEST(QuicheUrlUtilsTest, ExtraParameter) {
+ ValidateExpansion("/{foo}/{bar}/{baz}/", {{"foo", "123"}, {"bar", "456"}},
+ "/123/456/{baz}/", {"foo", "bar"});
+}
+
+TEST(QuicheUrlUtilsTest, MissingParameter) {
+ ValidateExpansion("/{foo}/{baz}/", {{"foo", "123"}, {"bar", "456"}},
+ "/123/{baz}/", {"foo"});
+}
+
+TEST(QuicheUrlUtilsTest, RepeatedParameter) {
+ ValidateExpansion("/{foo}/{bar}/{foo}/", {{"foo", "123"}, {"bar", "456"}},
+ "/123/456/123/", {"foo", "bar"});
+}
+
+TEST(QuicheUrlUtilsTest, URLEncoding) {
+ ValidateExpansion("/{foo}/{bar}/", {{"foo", "123"}, {"bar", ":"}},
+ "/123/%3A/", {"foo", "bar"});
+}
+
+} // namespace
+} // namespace quiche
diff --git a/common/platform/default/quiche_platform_impl/quiche_url_utils_impl.cc b/common/platform/default/quiche_platform_impl/quiche_url_utils_impl.cc
new file mode 100644
index 0000000..801d95d
--- /dev/null
+++ b/common/platform/default/quiche_platform_impl/quiche_url_utils_impl.cc
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche_platform_impl/quiche_url_utils_impl.h"
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_replace.h"
+#include "url/url_util.h"
+
+namespace quiche {
+
+bool ExpandURITemplateImpl(
+ const std::string& uri_template,
+ const absl::flat_hash_map<std::string, std::string>& parameters,
+ std::string* target, absl::flat_hash_set<std::string>* vars_found) {
+ absl::flat_hash_set<std::string> found;
+ std::string result = uri_template;
+ for (const auto& pair : parameters) {
+ const std::string& name = pair.first;
+ const std::string& value = pair.second;
+ std::string name_input = absl::StrCat("{", name, "}");
+ url::RawCanonOutputT<char> canon_value;
+ url::EncodeURIComponent(value.c_str(), value.length(), &canon_value);
+ std::string encoded_value(canon_value.data(), canon_value.length());
+ int num_replaced =
+ absl::StrReplaceAll({{name_input, encoded_value}}, &result);
+ if (num_replaced > 0) {
+ found.insert(name);
+ }
+ }
+ if (vars_found != nullptr) {
+ *vars_found = found;
+ }
+ *target = result;
+ return true;
+}
+
+} // namespace quiche
diff --git a/common/platform/default/quiche_platform_impl/quiche_url_utils_impl.h b/common/platform/default/quiche_platform_impl/quiche_url_utils_impl.h
new file mode 100644
index 0000000..45f0e42
--- /dev/null
+++ b/common/platform/default/quiche_platform_impl/quiche_url_utils_impl.h
@@ -0,0 +1,27 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_URL_UTILS_IMPL_H_
+#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_URL_UTILS_IMPL_H_
+
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+
+namespace quiche {
+
+// Produces concrete URLs in |target| from templated ones in |uri_template|.
+// Parameters are URL-encoded. Collects the names of any expanded variables in
+// |vars_found|. Supports level 1 templates as specified in RFC 6570. Returns
+// true if the template was parseable, false if it was malformed.
+bool ExpandURITemplateImpl(
+ const std::string& uri_template,
+ const absl::flat_hash_map<std::string, std::string>& parameters,
+ std::string* target,
+ absl::flat_hash_set<std::string>* vars_found = nullptr);
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_URL_UTILS_IMPL_H_
diff --git a/quic/masque/masque_client_bin.cc b/quic/masque/masque_client_bin.cc
index bb9964e..8af288f 100644
--- a/quic/masque/masque_client_bin.cc
+++ b/quic/masque/masque_client_bin.cc
@@ -8,9 +8,11 @@
// e.g.: masque_client $PROXY_HOST:$PROXY_PORT $URL1 $URL2
#include <memory>
+#include <string>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
+#include "url/third_party/mozilla/url_parse.h"
#include "quic/core/quic_server_id.h"
#include "quic/masque/masque_client_tools.h"
#include "quic/masque/masque_encapsulated_epoll_client.h"
@@ -21,7 +23,6 @@
#include "quic/platform/api/quic_socket_address.h"
#include "quic/platform/api/quic_system_event_loop.h"
#include "quic/tools/fake_proof_verifier.h"
-#include "quic/tools/quic_url.h"
DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
disable_certificate_verification,
@@ -42,8 +43,11 @@
QuicSystemEventLoop event_loop("masque_client");
const char* usage = "Usage: masque_client [options] <url>";
- // The first non-flag argument is the MASQUE server. All subsequent ones are
- // interpreted as URLs to fetch via the MASQUE server.
+ // The first non-flag argument is the URI template of the MASQUE server.
+ // All subsequent ones are interpreted as URLs to fetch via the MASQUE server.
+ // Note that the URI template expansion currently only supports string
+ // replacement of {target_host} and {target_port}, not
+ // {?target_host,target_port}.
std::vector<std::string> urls = QuicParseCommandLineFlags(usage, argc, argv);
if (urls.empty()) {
QuicPrintCommandLineFlagHelp(usage);
@@ -54,20 +58,29 @@
GetQuicFlag(FLAGS_disable_certificate_verification);
QuicEpollServer epoll_server;
- QuicUrl masque_url(urls[0], "https");
- if (masque_url.host().empty()) {
- masque_url = QuicUrl(absl::StrCat("https://", urls[0]), "https");
+ std::string uri_template = urls[0];
+ if (!absl::StrContains(uri_template, '/')) {
+ // Allow passing in authority instead of URI template.
+ uri_template =
+ absl::StrCat("https://", uri_template, "/{target_host}/{target_port}/");
}
- if (masque_url.host().empty()) {
- std::cerr << "Failed to parse MASQUE server address \"" << urls[0] << "\""
+ url::Parsed parsed_uri_template;
+ url::ParseStandardURL(uri_template.c_str(), uri_template.length(),
+ &parsed_uri_template);
+ if (!parsed_uri_template.scheme.is_nonempty() ||
+ !parsed_uri_template.host.is_nonempty() ||
+ !parsed_uri_template.path.is_nonempty()) {
+ std::cerr << "Failed to parse MASQUE URI template \"" << urls[0] << "\""
<< std::endl;
return 1;
}
+ std::string host = uri_template.substr(parsed_uri_template.host.begin,
+ parsed_uri_template.host.len);
std::unique_ptr<ProofVerifier> proof_verifier;
if (disable_certificate_verification) {
proof_verifier = std::make_unique<FakeProofVerifier>();
} else {
- proof_verifier = CreateDefaultProofVerifier(masque_url.host());
+ proof_verifier = CreateDefaultProofVerifier(host);
}
MasqueMode masque_mode = MasqueMode::kOpen;
std::string mode_string = GetQuicFlag(FLAGS_masque_mode);
@@ -78,8 +91,7 @@
return 1;
}
std::unique_ptr<MasqueEpollClient> masque_client = MasqueEpollClient::Create(
- masque_url.host(), masque_url.port(), masque_mode, &epoll_server,
- std::move(proof_verifier));
+ uri_template, masque_mode, &epoll_server, std::move(proof_verifier));
if (masque_client == nullptr) {
return 1;
}
diff --git a/quic/masque/masque_client_session.cc b/quic/masque/masque_client_session.cc
index 1926968..884cd20 100644
--- a/quic/masque/masque_client_session.cc
+++ b/quic/masque/masque_client_session.cc
@@ -4,23 +4,32 @@
#include "quic/masque/masque_client_session.h"
+#include <string>
+
#include "absl/algorithm/container.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
+#include "url/url_canon.h"
#include "quic/core/http/spdy_utils.h"
#include "quic/core/quic_data_reader.h"
#include "quic/core/quic_utils.h"
+#include "quic/platform/api/quic_socket_address.h"
+#include "quic/tools/quic_url.h"
+#include "common/platform/api/quiche_url_utils.h"
namespace quic {
MasqueClientSession::MasqueClientSession(
- MasqueMode masque_mode, const QuicConfig& config,
- const ParsedQuicVersionVector& supported_versions,
+ MasqueMode masque_mode, const std::string& uri_template,
+ const QuicConfig& config, const ParsedQuicVersionVector& supported_versions,
QuicConnection* connection, const QuicServerId& server_id,
QuicCryptoClientConfig* crypto_config,
QuicClientPushPromiseIndex* push_promise_index, Owner* owner)
: QuicSpdyClientSession(config, supported_versions, connection, server_id,
crypto_config, push_promise_index),
masque_mode_(masque_mode),
+ uri_template_(uri_template),
owner_(owner),
compression_engine_(this) {}
@@ -80,6 +89,51 @@
}
}
// No CONNECT-UDP request found, create a new one.
+
+ url::Parsed parsed_uri_template;
+ url::ParseStandardURL(uri_template_.c_str(), uri_template_.length(),
+ &parsed_uri_template);
+ if (!parsed_uri_template.path.is_nonempty()) {
+ QUIC_BUG(bad URI template path)
+ << "Cannot parse path from URI template " << uri_template_;
+ return nullptr;
+ }
+ std::string path = uri_template_.substr(parsed_uri_template.path.begin,
+ parsed_uri_template.path.len);
+ if (parsed_uri_template.query.is_valid()) {
+ absl::StrAppend(&path, "?",
+ uri_template_.substr(parsed_uri_template.query.begin,
+ parsed_uri_template.query.len));
+ }
+ absl::flat_hash_map<std::string, std::string> parameters;
+ parameters["target_host"] = target_server_address.host().ToString();
+ parameters["target_port"] = absl::StrCat(target_server_address.port());
+ std::string expanded_path;
+ absl::flat_hash_set<std::string> vars_found;
+ bool expanded =
+ quiche::ExpandURITemplate(path, parameters, &expanded_path, &vars_found);
+ if (!expanded || vars_found.find("target_host") == vars_found.end() ||
+ vars_found.find("target_port") == vars_found.end()) {
+ QUIC_DLOG(ERROR) << "Failed to expand URI template \"" << uri_template_
+ << "\" for " << target_server_address;
+ return nullptr;
+ }
+
+ url::Component expanded_path_component(0, expanded_path.length());
+ url::RawCanonOutput<1024> canonicalized_path_output;
+ url::Component canonicalized_path_component;
+ bool canonicalized = url::CanonicalizePath(
+ expanded_path.c_str(), expanded_path_component,
+ &canonicalized_path_output, &canonicalized_path_component);
+ if (!canonicalized || !canonicalized_path_component.is_nonempty()) {
+ QUIC_DLOG(ERROR) << "Failed to canonicalize URI template \""
+ << uri_template_ << "\" for " << target_server_address;
+ return nullptr;
+ }
+ std::string canonicalized_path(
+ canonicalized_path_output.data() + canonicalized_path_component.begin,
+ canonicalized_path_component.len);
+
QuicSpdyClientStream* stream = CreateOutgoingBidirectionalStream();
if (stream == nullptr) {
// Stream flow control limits prevented us from opening a new stream.
@@ -87,15 +141,23 @@
return nullptr;
}
+ QuicUrl url(uri_template_);
+ std::string scheme = url.scheme();
+ std::string authority = url.HostPort();
+
QUIC_DLOG(INFO) << "Sending CONNECT-UDP request for " << target_server_address
- << " on stream " << stream->id();
+ << " on stream " << stream->id() << " scheme=\"" << scheme
+ << "\" authority=\"" << authority << "\" path=\""
+ << canonicalized_path << "\"";
// Send the request.
spdy::Http2HeaderBlock headers;
- headers[":method"] = "CONNECT-UDP";
- headers[":scheme"] = "masque";
- headers[":path"] = "/";
- headers[":authority"] = target_server_address.ToString();
+ headers[":method"] = "CONNECT";
+ headers[":protocol"] = "connect-udp";
+ headers[":scheme"] = scheme;
+ headers[":authority"] = authority;
+ headers[":path"] = canonicalized_path;
+ headers["connect-udp-version"] = "6";
if (http_datagram_support() == HttpDatagramSupport::kDraft00) {
SpdyUtils::AddDatagramFlowIdHeader(&headers, stream->id());
}
diff --git a/quic/masque/masque_client_session.h b/quic/masque/masque_client_session.h
index 662783a..f8af4af 100644
--- a/quic/masque/masque_client_session.h
+++ b/quic/masque/masque_client_session.h
@@ -5,6 +5,8 @@
#ifndef QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_
#define QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_
+#include <string>
+
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "quic/core/http/quic_spdy_client_session.h"
@@ -56,11 +58,10 @@
// |push_promise_index| or |owner|. All pointers must be non-null. Caller
// must ensure that |push_promise_index| and |owner| stay valid for the
// lifetime of the newly created MasqueClientSession.
- MasqueClientSession(MasqueMode masque_mode,
+ MasqueClientSession(MasqueMode masque_mode, const std::string& uri_template,
const QuicConfig& config,
const ParsedQuicVersionVector& supported_versions,
- QuicConnection* connection,
- const QuicServerId& server_id,
+ QuicConnection* connection, const QuicServerId& server_id,
QuicCryptoClientConfig* crypto_config,
QuicClientPushPromiseIndex* push_promise_index,
Owner* owner);
@@ -170,6 +171,7 @@
EncapsulatedClientSession* encapsulated_client_session);
MasqueMode masque_mode_;
+ std::string uri_template_;
std::list<ConnectUdpClientState> connect_udp_client_states_;
absl::flat_hash_map<QuicConnectionId,
EncapsulatedClientSession*,
diff --git a/quic/masque/masque_epoll_client.cc b/quic/masque/masque_epoll_client.cc
index 2b835cf..bfb4636 100644
--- a/quic/masque/masque_epoll_client.cc
+++ b/quic/masque/masque_epoll_client.cc
@@ -3,26 +3,25 @@
// found in the LICENSE file.
#include "quic/masque/masque_epoll_client.h"
+
+#include <string>
+
#include "absl/memory/memory.h"
#include "quic/masque/masque_client_session.h"
#include "quic/masque/masque_utils.h"
+#include "quic/tools/quic_url.h"
namespace quic {
MasqueEpollClient::MasqueEpollClient(
- QuicSocketAddress server_address,
- const QuicServerId& server_id,
- MasqueMode masque_mode,
- QuicEpollServer* epoll_server,
+ QuicSocketAddress server_address, const QuicServerId& server_id,
+ MasqueMode masque_mode, QuicEpollServer* epoll_server,
std::unique_ptr<ProofVerifier> proof_verifier,
- const std::string& authority)
- : QuicClient(server_address,
- server_id,
- MasqueSupportedVersions(),
- epoll_server,
- std::move(proof_verifier)),
+ const std::string& uri_template)
+ : QuicClient(server_address, server_id, MasqueSupportedVersions(),
+ epoll_server, std::move(proof_verifier)),
masque_mode_(masque_mode),
- authority_(authority) {}
+ uri_template_(uri_template) {}
std::unique_ptr<QuicSession> MasqueEpollClient::CreateQuicClientSession(
const ParsedQuicVersionVector& supported_versions,
@@ -30,8 +29,8 @@
QUIC_DLOG(INFO) << "Creating MASQUE session for "
<< connection->connection_id();
return std::make_unique<MasqueClientSession>(
- masque_mode_, *config(), supported_versions, connection, server_id(),
- crypto_config(), push_promise_index(), this);
+ masque_mode_, uri_template_, *config(), supported_versions, connection,
+ server_id(), crypto_config(), push_promise_index(), this);
}
MasqueClientSession* MasqueEpollClient::masque_client_session() {
@@ -42,13 +41,19 @@
return masque_client_session()->connection_id();
}
+std::string MasqueEpollClient::authority() const {
+ QuicUrl url(uri_template_);
+ return absl::StrCat(url.host(), ":", url.port());
+}
+
// static
std::unique_ptr<MasqueEpollClient> MasqueEpollClient::Create(
- const std::string& host,
- int port,
- MasqueMode masque_mode,
+ const std::string& uri_template, MasqueMode masque_mode,
QuicEpollServer* epoll_server,
std::unique_ptr<ProofVerifier> proof_verifier) {
+ QuicUrl url(uri_template);
+ std::string host = url.host();
+ uint16_t port = url.port();
// Build the masque_client, and try to connect.
QuicSocketAddress addr = tools::LookupAddress(host, absl::StrCat(port));
if (!addr.IsInitialized()) {
@@ -59,9 +64,9 @@
// Use absl::WrapUnique(new MasqueEpollClient(...)) instead of
// std::make_unique<MasqueEpollClient>(...) because the constructor for
// MasqueEpollClient is private and therefore not accessible from make_unique.
- auto masque_client = absl::WrapUnique(new MasqueEpollClient(
- addr, server_id, masque_mode, epoll_server, std::move(proof_verifier),
- absl::StrCat(host, ":", port)));
+ auto masque_client = absl::WrapUnique(
+ new MasqueEpollClient(addr, server_id, masque_mode, epoll_server,
+ std::move(proof_verifier), uri_template));
if (masque_client == nullptr) {
QUIC_LOG(ERROR) << "Failed to create masque_client";
@@ -91,7 +96,7 @@
spdy::Http2HeaderBlock header_block;
header_block[":method"] = "POST";
header_block[":scheme"] = "https";
- header_block[":authority"] = masque_client->authority_;
+ header_block[":authority"] = masque_client->authority();
header_block[":path"] = "/.well-known/masque/init";
std::string body = "foo";
@@ -138,7 +143,7 @@
spdy::Http2HeaderBlock header_block;
header_block[":method"] = "POST";
header_block[":scheme"] = "https";
- header_block[":authority"] = authority_;
+ header_block[":authority"] = authority();
header_block[":path"] = "/.well-known/masque/unregister";
// Make sure to store the response, for later output.
diff --git a/quic/masque/masque_epoll_client.h b/quic/masque/masque_epoll_client.h
index 2f5faca..b6e0087 100644
--- a/quic/masque/masque_epoll_client.h
+++ b/quic/masque/masque_epoll_client.h
@@ -5,10 +5,13 @@
#ifndef QUICHE_QUIC_MASQUE_MASQUE_EPOLL_CLIENT_H_
#define QUICHE_QUIC_MASQUE_MASQUE_EPOLL_CLIENT_H_
+#include <string>
+
#include "quic/masque/masque_client_session.h"
#include "quic/masque/masque_utils.h"
#include "quic/platform/api/quic_export.h"
#include "quic/tools/quic_client.h"
+#include "quic/tools/quic_url.h"
namespace quic {
@@ -18,9 +21,7 @@
public:
// Constructs a MasqueEpollClient, performs a synchronous DNS lookup.
static std::unique_ptr<MasqueEpollClient> Create(
- const std::string& host,
- int port,
- MasqueMode masque_mode,
+ const std::string& uri_template, MasqueMode masque_mode,
QuicEpollServer* epoll_server,
std::unique_ptr<ProofVerifier> proof_verifier);
@@ -46,22 +47,23 @@
private:
// Constructor is private, use Create() instead.
MasqueEpollClient(QuicSocketAddress server_address,
- const QuicServerId& server_id,
- MasqueMode masque_mode,
+ const QuicServerId& server_id, MasqueMode masque_mode,
QuicEpollServer* epoll_server,
std::unique_ptr<ProofVerifier> proof_verifier,
- const std::string& authority);
+ const std::string& uri_template);
// Wait synchronously until we receive the peer's settings. Returns whether
// they were received.
bool WaitUntilSettingsReceived();
+ std::string authority() const;
+
// Disallow copy and assign.
MasqueEpollClient(const MasqueEpollClient&) = delete;
MasqueEpollClient& operator=(const MasqueEpollClient&) = delete;
MasqueMode masque_mode_;
- std::string authority_;
+ std::string uri_template_;
bool settings_received_ = false;
};
diff --git a/quic/masque/masque_server_backend.cc b/quic/masque/masque_server_backend.cc
index 9684ce1..280ab5e 100644
--- a/quic/masque/masque_server_backend.cc
+++ b/quic/masque/masque_server_backend.cc
@@ -50,7 +50,9 @@
masque_path = std::string(path.substr(sizeof("/.well-known/masque/") - 1));
} else {
QUICHE_DCHECK_EQ(masque_mode_, MasqueMode::kOpen);
- if (method != "CONNECT-UDP") {
+ auto protocol_pair = request_headers.find(":protocol");
+ if (method != "CONNECT" || protocol_pair == request_headers.end() ||
+ protocol_pair->second != "connect-udp") {
// This is not a MASQUE request.
return false;
}
diff --git a/quic/masque/masque_server_session.cc b/quic/masque/masque_server_session.cc
index 693f009..b3aa99e 100644
--- a/quic/masque/masque_server_session.cc
+++ b/quic/masque/masque_server_session.cc
@@ -6,9 +6,13 @@
#include <netdb.h>
+#include <cstddef>
+#include <limits>
+
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
+#include "url/url_util.h"
#include "quic/core/http/spdy_utils.h"
#include "quic/core/quic_data_reader.h"
#include "quic/core/quic_udp_socket.h"
@@ -68,6 +72,23 @@
return response;
}
+absl::optional<std::string> AsciiUrlDecode(absl::string_view input) {
+ std::string input_encoded = std::string(input);
+ url::RawCanonOutputW<1024> canon_output;
+ DecodeURLEscapeSequences(input_encoded.c_str(), input_encoded.length(),
+ &canon_output);
+ std::string output;
+ output.reserve(canon_output.length());
+ for (int i = 0; i < canon_output.length(); i++) {
+ const uint16_t c = reinterpret_cast<uint16_t*>(canon_output.data())[i];
+ if (c > std::numeric_limits<signed char>::max()) {
+ return absl::nullopt;
+ }
+ output += static_cast<char>(c);
+ }
+ return output;
+}
+
} // namespace
MasqueServerSession::MasqueServerSession(
@@ -169,6 +190,7 @@
auto path_pair = request_headers.find(":path");
auto scheme_pair = request_headers.find(":scheme");
auto method_pair = request_headers.find(":method");
+ auto protocol_pair = request_headers.find(":protocol");
auto authority_pair = request_headers.find(":authority");
if (path_pair == request_headers.end()) {
QUIC_DLOG(ERROR) << "MASQUE request is missing :path";
@@ -182,6 +204,10 @@
QUIC_DLOG(ERROR) << "MASQUE request is missing :method";
return CreateBackendErrorResponse("400", "Missing :method");
}
+ if (protocol_pair == request_headers.end()) {
+ QUIC_DLOG(ERROR) << "MASQUE request is missing :protocol";
+ return CreateBackendErrorResponse("400", "Missing :protocol");
+ }
if (authority_pair == request_headers.end()) {
QUIC_DLOG(ERROR) << "MASQUE request is missing :authority";
return CreateBackendErrorResponse("400", "Missing :authority");
@@ -189,6 +215,7 @@
absl::string_view path = path_pair->second;
absl::string_view scheme = scheme_pair->second;
absl::string_view method = method_pair->second;
+ absl::string_view protocol = protocol_pair->second;
absl::string_view authority = authority_pair->second;
if (path.empty()) {
QUIC_DLOG(ERROR) << "MASQUE request with empty path";
@@ -197,10 +224,15 @@
if (scheme.empty()) {
return CreateBackendErrorResponse("400", "Empty scheme");
}
- if (method != "CONNECT-UDP") {
+ if (method != "CONNECT") {
QUIC_DLOG(ERROR) << "MASQUE request with bad method \"" << method << "\"";
return CreateBackendErrorResponse("400", "Bad method");
}
+ if (protocol != "connect-udp") {
+ QUIC_DLOG(ERROR) << "MASQUE request with bad protocol \"" << protocol
+ << "\"";
+ return CreateBackendErrorResponse("400", "Bad protocol");
+ }
absl::optional<QuicDatagramStreamId> flow_id;
if (http_datagram_support() == HttpDatagramSupport::kDraft00) {
flow_id = SpdyUtils::ParseDatagramFlowIdHeader(request_headers);
@@ -211,20 +243,32 @@
"400", "Bad or missing DatagramFlowId header");
}
}
- QuicUrl url(absl::StrCat("https://", authority));
- if (!url.IsValid() || url.PathParamsQuery() != "/") {
- QUIC_DLOG(ERROR) << "MASQUE request with bad authority \"" << authority
- << "\"";
- return CreateBackendErrorResponse("400", "Bad authority");
+ // Extract target host and port from path using default template.
+ std::vector<absl::string_view> path_split = absl::StrSplit(path, '/');
+ if (path_split.size() != 4 || !path_split[0].empty() ||
+ path_split[1].empty() || path_split[2].empty() ||
+ !path_split[3].empty()) {
+ QUIC_DLOG(ERROR) << "MASQUE request with bad path \"" << path << "\"";
+ return CreateBackendErrorResponse("400", "Bad path");
+ }
+ absl::optional<std::string> host = AsciiUrlDecode(path_split[1]);
+ if (!host.has_value()) {
+ QUIC_DLOG(ERROR) << "Failed to decode host \"" << path_split[1] << "\"";
+ return CreateBackendErrorResponse("500", "Failed to decode host");
+ }
+ absl::optional<std::string> port = AsciiUrlDecode(path_split[2]);
+ if (!port.has_value()) {
+ QUIC_DLOG(ERROR) << "Failed to decode port \"" << path_split[2] << "\"";
+ return CreateBackendErrorResponse("500", "Failed to decode port");
}
- std::string port = absl::StrCat(url.port());
+ // Perform DNS resolution.
addrinfo hint = {};
hint.ai_protocol = IPPROTO_UDP;
addrinfo* info_list = nullptr;
- int result =
- getaddrinfo(url.host().c_str(), port.c_str(), &hint, &info_list);
+ int result = getaddrinfo(host.value().c_str(), port.value().c_str(), &hint,
+ &info_list);
if (result != 0) {
QUIC_DLOG(ERROR) << "Failed to resolve " << authority << ": "
<< gai_strerror(result);