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);