URI parser: fix percent-encoding and add ToString() method (#28485)

* fix URI percent-encoding character set

* clean up and fix edge cases

* add ToString() method to URI parser with appropriate percent-encoding logic

* clang-format

* fix ordering for URI::QueryParam

* fix decoding edge cases in parsing

* generate upper-case hex digits, as per the RFC

* clang-format

* reuse offset variable
pull/28515/head
Mark D. Roth 3 years ago committed by GitHub
parent 97b87da470
commit 90340c24c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc
  2. 14
      src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc
  3. 272
      src/core/lib/uri/uri_parser.cc
  4. 58
      src/core/lib/uri/uri_parser.h
  5. 359
      test/core/uri/uri_parser_test.cc

@ -91,9 +91,9 @@ bool ParseUri(const URI& uri,
// Construct addresses. // Construct addresses.
bool errors_found = false; bool errors_found = false;
for (absl::string_view ith_path : absl::StrSplit(uri.path(), ',')) { for (absl::string_view ith_path : absl::StrSplit(uri.path(), ',')) {
URI ith_uri(uri.scheme(), "", std::string(ith_path), {}, ""); auto ith_uri = URI::Create(uri.scheme(), "", std::string(ith_path), {}, "");
grpc_resolved_address addr; grpc_resolved_address addr;
if (!parse(ith_uri, &addr)) { if (!ith_uri.ok() || !parse(*ith_uri, &addr)) {
errors_found = true; errors_found = true;
break; break;
} }

@ -81,11 +81,10 @@ class XdsResolver : public Resolver {
uri_(std::move(args.uri)), uri_(std::move(args.uri)),
data_plane_authority_(GetDataPlaneAuthority(*args.args, uri_)) { data_plane_authority_(GetDataPlaneAuthority(*args.args, uri_)) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_resolver_trace)) { if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_resolver_trace)) {
gpr_log(GPR_INFO, gpr_log(
"[xds_resolver %p] created for URI scheme %s path %s authority " GPR_INFO,
"%s data plane authority %s", "[xds_resolver %p] created for URI %s; data plane authority is %s",
this, args.uri.scheme().c_str(), args.uri.path().c_str(), this, uri_.ToString().c_str(), data_plane_authority_.c_str());
args.uri.authority().c_str(), data_plane_authority_.c_str());
} }
} }
@ -738,7 +737,8 @@ void XdsResolver::StartLocked() {
"/envoy.config.listener.v3.Listener/%s"); "/envoy.config.listener.v3.Listener/%s");
} }
lds_resource_name_ = absl::StrReplaceAll( lds_resource_name_ = absl::StrReplaceAll(
name_template, {{"%s", URI::PercentEncode(resource_name_fragment)}}); name_template,
{{"%s", URI::PercentEncodePath(resource_name_fragment)}});
} else { } else {
// target_uri.authority not set // target_uri.authority not set
absl::string_view name_template = absl::string_view name_template =
@ -748,7 +748,7 @@ void XdsResolver::StartLocked() {
name_template = "%s"; name_template = "%s";
} }
if (absl::StartsWith(name_template, "xdstp:")) { if (absl::StartsWith(name_template, "xdstp:")) {
resource_name_fragment = URI::PercentEncode(resource_name_fragment); resource_name_fragment = URI::PercentEncodePath(resource_name_fragment);
} }
lds_resource_name_ = lds_resource_name_ =
absl::StrReplaceAll(name_template, {{"%s", resource_name_fragment}}); absl::StrReplaceAll(name_template, {{"%s", resource_name_fragment}});

@ -1,20 +1,18 @@
/* //
* // Copyright 2015 gRPC authors.
* Copyright 2015 gRPC authors. //
* // Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
* you may not use this file except in compliance with the License. // You may obtain a copy of the License at
* You may obtain a copy of the License at //
* // http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0 //
* // Unless required by applicable law or agreed to in writing, software
* Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
* distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
* See the License for the specific language governing permissions and // limitations under the License.
* limitations under the License. //
*
*/
#include <grpc/support/port_platform.h> #include <grpc/support/port_platform.h>
@ -37,48 +35,110 @@ namespace grpc_core {
namespace { namespace {
bool ShouldEscape(unsigned char c) { // Returns true for any sub-delim character, as defined in:
// Unreserved characters RFC 3986 section 2.3 Unreserved Characters. // https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || bool IsSubDelimChar(char c) {
(c >= '0' && c <= '9')) { switch (c) {
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
return true;
}
return false; return false;
} }
// Returns true for any unreserved character, as defined in:
// https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
bool IsUnreservedChar(char c) {
if (absl::ascii_isalnum(c)) return true;
switch (c) { switch (c) {
case '-': case '-':
case '_':
case '.': case '.':
case '_':
case '~': case '~':
case '/': return true;
}
return false;
}
// Returns true for any character in scheme, as defined in:
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
bool IsSchemeChar(char c) {
if (absl::ascii_isalnum(c)) return true;
switch (c) {
case '+':
case '-':
case '.':
return true;
}
return false;
}
// Returns true for any character in authority, as defined in:
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
bool IsAuthorityChar(char c) {
if (IsUnreservedChar(c)) return true;
if (IsSubDelimChar(c)) return true;
switch (c) {
case ':': case ':':
case '[':
case ']':
case '@':
return true;
}
return false; return false;
} }
// Returns true for any character in pchar, as defined in:
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
bool IsPChar(char c) {
if (IsUnreservedChar(c)) return true;
if (IsSubDelimChar(c)) return true;
switch (c) {
case ':':
case '@':
return true; return true;
} }
return false;
}
// Returns true for any character allowed in a URI path, as defined in:
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
bool IsPathChar(char c) { return IsPChar(c) || c == '/'; }
// Checks if this string is made up of pchars, '/', '?', and '%' exclusively. // Returns true for any character allowed in a URI query or fragment,
// as defined in:
// See https://tools.ietf.org/html/rfc3986#section-3.4 // See https://tools.ietf.org/html/rfc3986#section-3.4
bool IsPCharString(absl::string_view str) { bool IsQueryOrFragmentChar(char c) {
return (str.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ" return IsPChar(c) || c == '/' || c == '?';
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"?/:@\\-._~!$&'()*+,;=%") ==
absl::string_view::npos);
} }
absl::Status MakeInvalidURIStatus(absl::string_view part_name, // Same as IsQueryOrFragmentChar(), but excludes '&' and '='.
absl::string_view uri, bool IsQueryKeyOrValueChar(char c) {
absl::string_view extra) { return c != '&' && c != '=' && IsQueryOrFragmentChar(c);
return absl::InvalidArgumentError(absl::StrFormat(
"Could not parse '%s' from uri '%s'. %s", part_name, uri, extra));
} }
} // namespace
std::string URI::PercentEncode(absl::string_view str) { // Returns a copy of str, percent-encoding any character for which
// is_allowed_char() returns false.
std::string PercentEncode(absl::string_view str,
std::function<bool(char)> is_allowed_char) {
std::string out; std::string out;
for (const char c : str) { for (char c : str) {
if (ShouldEscape(c)) { if (!is_allowed_char(c)) {
std::string hex = absl::BytesToHexString(absl::string_view(&c, 1)); std::string hex = absl::BytesToHexString(absl::string_view(&c, 1));
GPR_ASSERT(hex.size() == 2); GPR_ASSERT(hex.size() == 2);
// BytesToHexString() returns lower case, but
// https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1 says
// to prefer upper-case.
absl::AsciiStrToUpper(&hex);
out.push_back('%'); out.push_back('%');
out.append(hex); out.append(hex);
} else { } else {
@ -88,6 +148,28 @@ std::string URI::PercentEncode(absl::string_view str) {
return out; return out;
} }
// Checks if this string is made up of query/fragment chars and '%' exclusively.
// See https://tools.ietf.org/html/rfc3986#section-3.4
bool IsQueryOrFragmentString(absl::string_view str) {
for (char c : str) {
if (!IsQueryOrFragmentChar(c) && c != '%') return false;
}
return true;
}
absl::Status MakeInvalidURIStatus(absl::string_view part_name,
absl::string_view uri,
absl::string_view extra) {
return absl::InvalidArgumentError(absl::StrFormat(
"Could not parse '%s' from uri '%s'. %s", part_name, uri, extra));
}
} // namespace
std::string URI::PercentEncodePath(absl::string_view str) {
return PercentEncode(str, IsPathChar);
}
// Similar to `grpc_permissive_percent_decode_slice`, this %-decodes all valid // Similar to `grpc_permissive_percent_decode_slice`, this %-decodes all valid
// triplets, and passes through the rest verbatim. // triplets, and passes through the rest verbatim.
std::string URI::PercentDecode(absl::string_view str) { std::string URI::PercentDecode(absl::string_view str) {
@ -99,18 +181,14 @@ std::string URI::PercentDecode(absl::string_view str) {
out.reserve(str.size()); out.reserve(str.size());
for (size_t i = 0; i < str.length(); i++) { for (size_t i = 0; i < str.length(); i++) {
unescaped = ""; unescaped = "";
if (str[i] != '%') { if (str[i] == '%' && i + 3 <= str.length() &&
out += str[i]; absl::CUnescape(absl::StrCat("\\x", str.substr(i + 1, 2)),
continue; &unescaped) &&
} unescaped.length() == 1) {
if (i + 3 >= str.length() ||
!absl::CUnescape(absl::StrCat("\\x", str.substr(i + 1, 2)),
&unescaped) ||
unescaped.length() > 1) {
out += str[i];
} else {
out += unescaped[0]; out += unescaped[0];
i += 2; i += 2;
} else {
out += str[i];
} }
} }
return out; return out;
@ -120,11 +198,11 @@ absl::StatusOr<URI> URI::Parse(absl::string_view uri_text) {
absl::StatusOr<std::string> decoded; absl::StatusOr<std::string> decoded;
absl::string_view remaining = uri_text; absl::string_view remaining = uri_text;
// parse scheme // parse scheme
size_t idx = remaining.find(':'); size_t offset = remaining.find(':');
if (idx == remaining.npos || idx == 0) { if (offset == remaining.npos || offset == 0) {
return MakeInvalidURIStatus("scheme", uri_text, "Scheme not found."); return MakeInvalidURIStatus("scheme", uri_text, "Scheme not found.");
} }
std::string scheme(remaining.substr(0, idx)); std::string scheme(remaining.substr(0, offset));
if (scheme.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ" if (scheme.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz"
"0123456789+-.") != std::string::npos) { "0123456789+-.") != std::string::npos) {
@ -136,30 +214,38 @@ absl::StatusOr<URI> URI::Parse(absl::string_view uri_text) {
"scheme", uri_text, "scheme", uri_text,
"Scheme must begin with an alpha character [A-Za-z]."); "Scheme must begin with an alpha character [A-Za-z].");
} }
remaining.remove_prefix(scheme.length() + 1); remaining.remove_prefix(offset + 1);
// parse authority // parse authority
std::string authority; std::string authority;
if (absl::StartsWith(remaining, "//")) { if (absl::ConsumePrefix(&remaining, "//")) {
remaining.remove_prefix(2); offset = remaining.find_first_of("/?#");
authority = authority = PercentDecode(remaining.substr(0, offset));
PercentDecode(remaining.substr(0, remaining.find_first_of("/?#"))); if (offset == remaining.npos) {
remaining.remove_prefix(authority.length()); remaining = "";
} else {
remaining.remove_prefix(offset);
}
} }
// parse path // parse path
std::string path; std::string path;
if (!remaining.empty()) { if (!remaining.empty()) {
path = PercentDecode(remaining.substr(0, remaining.find_first_of("?#"))); offset = remaining.find_first_of("?#");
remaining.remove_prefix(path.length()); path = PercentDecode(remaining.substr(0, offset));
if (offset == remaining.npos) {
remaining = "";
} else {
remaining.remove_prefix(offset);
}
} }
// parse query // parse query
std::vector<QueryParam> query_param_pairs; std::vector<QueryParam> query_param_pairs;
if (!remaining.empty() && remaining[0] == '?') { if (absl::ConsumePrefix(&remaining, "?")) {
remaining.remove_prefix(1); offset = remaining.find('#');
absl::string_view tmp_query = remaining.substr(0, remaining.find('#')); absl::string_view tmp_query = remaining.substr(0, offset);
if (tmp_query.empty()) { if (tmp_query.empty()) {
return MakeInvalidURIStatus("query", uri_text, "Invalid query string."); return MakeInvalidURIStatus("query", uri_text, "Invalid query string.");
} }
if (!IsPCharString(tmp_query)) { if (!IsQueryOrFragmentString(tmp_query)) {
return MakeInvalidURIStatus("query string", uri_text, return MakeInvalidURIStatus("query string", uri_text,
"Query string contains invalid characters."); "Query string contains invalid characters.");
} }
@ -170,12 +256,15 @@ absl::StatusOr<URI> URI::Parse(absl::string_view uri_text) {
query_param_pairs.push_back({PercentDecode(possible_kv.first), query_param_pairs.push_back({PercentDecode(possible_kv.first),
PercentDecode(possible_kv.second)}); PercentDecode(possible_kv.second)});
} }
remaining.remove_prefix(tmp_query.length()); if (offset == remaining.npos) {
remaining = "";
} else {
remaining.remove_prefix(offset);
}
} }
std::string fragment; std::string fragment;
if (!remaining.empty() && remaining[0] == '#') { if (absl::ConsumePrefix(&remaining, "#")) {
remaining.remove_prefix(1); if (!IsQueryOrFragmentString(remaining)) {
if (!IsPCharString(remaining)) {
return MakeInvalidURIStatus("fragment", uri_text, return MakeInvalidURIStatus("fragment", uri_text,
"Fragment contains invalid characters."); "Fragment contains invalid characters.");
} }
@ -185,6 +274,18 @@ absl::StatusOr<URI> URI::Parse(absl::string_view uri_text) {
std::move(query_param_pairs), std::move(fragment)); std::move(query_param_pairs), std::move(fragment));
} }
absl::StatusOr<URI> URI::Create(std::string scheme, std::string authority,
std::string path,
std::vector<QueryParam> query_parameter_pairs,
std::string fragment) {
if (!authority.empty() && !path.empty() && path[0] != '/') {
return absl::InvalidArgumentError(
"if authority is present, path must start with a '/'");
}
return URI(std::move(scheme), std::move(authority), std::move(path),
std::move(query_parameter_pairs), std::move(fragment));
}
URI::URI(std::string scheme, std::string authority, std::string path, URI::URI(std::string scheme, std::string authority, std::string path,
std::vector<QueryParam> query_parameter_pairs, std::string fragment) std::vector<QueryParam> query_parameter_pairs, std::string fragment)
: scheme_(std::move(scheme)), : scheme_(std::move(scheme)),
@ -222,4 +323,39 @@ URI& URI::operator=(const URI& other) {
} }
return *this; return *this;
} }
namespace {
// A pair formatter for use with absl::StrJoin() for formatting query params.
struct QueryParameterFormatter {
void operator()(std::string* out, const URI::QueryParam& query_param) const {
out->append(
absl::StrCat(PercentEncode(query_param.key, IsQueryKeyOrValueChar), "=",
PercentEncode(query_param.value, IsQueryKeyOrValueChar)));
}
};
} // namespace
std::string URI::ToString() const {
std::vector<std::string> parts = {PercentEncode(scheme_, IsSchemeChar), ":"};
if (!authority_.empty()) {
parts.emplace_back("//");
parts.emplace_back(PercentEncode(authority_, IsAuthorityChar));
}
if (!path_.empty()) {
parts.emplace_back(PercentEncode(path_, IsPathChar));
}
if (!query_parameter_pairs_.empty()) {
parts.push_back("?");
parts.push_back(
absl::StrJoin(query_parameter_pairs_, "&", QueryParameterFormatter()));
}
if (!fragment_.empty()) {
parts.push_back("#");
parts.push_back(PercentEncode(fragment_, IsQueryOrFragmentChar));
}
return absl::StrJoin(parts, "");
}
} // namespace grpc_core } // namespace grpc_core

@ -1,20 +1,18 @@
/* //
* // Copyright 2015 gRPC authors.
* Copyright 2015 gRPC authors. //
* // Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
* you may not use this file except in compliance with the License. // You may obtain a copy of the License at
* You may obtain a copy of the License at //
* // http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0 //
* // Unless required by applicable law or agreed to in writing, software
* Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
* distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
* See the License for the specific language governing permissions and // limitations under the License.
* limitations under the License. //
*
*/
#ifndef GRPC_CORE_LIB_URI_URI_PARSER_H #ifndef GRPC_CORE_LIB_URI_URI_PARSER_H
#define GRPC_CORE_LIB_URI_URI_PARSER_H #define GRPC_CORE_LIB_URI_URI_PARSER_H
@ -40,15 +38,23 @@ class URI {
bool operator==(const QueryParam& other) const { bool operator==(const QueryParam& other) const {
return key == other.key && value == other.value; return key == other.key && value == other.value;
} }
bool operator<(const QueryParam& other) const {
int c = key.compare(other.key);
if (c != 0) return c < 0;
return value < other.value;
}
}; };
// Creates an instance of GrpcURI by parsing an rfc3986 URI string. Returns // Creates a URI by parsing an rfc3986 URI string. Returns an
// an IllegalArgumentError on failure. // InvalidArgumentError on failure.
static absl::StatusOr<URI> Parse(absl::string_view uri_text); static absl::StatusOr<URI> Parse(absl::string_view uri_text);
// Explicit construction by individual URI components // Creates a URI from components. Returns an InvalidArgumentError on failure.
URI(std::string scheme, std::string authority, std::string path, static absl::StatusOr<URI> Create(
std::string scheme, std::string authority, std::string path,
std::vector<QueryParam> query_parameter_pairs, std::string fragment); std::vector<QueryParam> query_parameter_pairs, std::string fragment);
URI() = default; URI() = default;
// Copy construction and assignment // Copy construction and assignment
URI(const URI& other); URI(const URI& other);
URI& operator=(const URI& other); URI& operator=(const URI& other);
@ -56,7 +62,8 @@ class URI {
URI(URI&&) = default; URI(URI&&) = default;
URI& operator=(URI&&) = default; URI& operator=(URI&&) = default;
static std::string PercentEncode(absl::string_view str); static std::string PercentEncodePath(absl::string_view str);
static std::string PercentDecode(absl::string_view str); static std::string PercentDecode(absl::string_view str);
const std::string& scheme() const { return scheme_; } const std::string& scheme() const { return scheme_; }
@ -77,7 +84,12 @@ class URI {
} }
const std::string& fragment() const { return fragment_; } const std::string& fragment() const { return fragment_; }
std::string ToString() const;
private: private:
URI(std::string scheme, std::string authority, std::string path,
std::vector<QueryParam> query_parameter_pairs, std::string fragment);
std::string scheme_; std::string scheme_;
std::string authority_; std::string authority_;
std::string path_; std::string path_;
@ -87,4 +99,4 @@ class URI {
}; };
} // namespace grpc_core } // namespace grpc_core
#endif /* GRPC_CORE_LIB_URI_URI_PARSER_H */ #endif // GRPC_CORE_LIB_URI_URI_PARSER_H

@ -1,20 +1,18 @@
/* //
* // Copyright 2015 gRPC authors.
* Copyright 2015 gRPC authors. //
* // Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
* you may not use this file except in compliance with the License. // You may obtain a copy of the License at
* You may obtain a copy of the License at //
* // http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0 //
* // Unless required by applicable law or agreed to in writing, software
* Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
* distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
* See the License for the specific language governing permissions and // limitations under the License.
* limitations under the License. //
*
*/
#include "src/core/lib/uri/uri_parser.h" #include "src/core/lib/uri/uri_parser.h"
@ -34,14 +32,18 @@ using ::testing::Contains;
using ::testing::ElementsAre; using ::testing::ElementsAre;
using ::testing::Pair; using ::testing::Pair;
namespace grpc_core {
class URIParserTest : public testing::Test {
protected:
static void TestSucceeds( static void TestSucceeds(
absl::string_view uri_text, absl::string_view scheme, absl::string_view uri_text, absl::string_view scheme,
absl::string_view authority, absl::string_view path, absl::string_view authority, absl::string_view path,
const std::map<absl::string_view, absl::string_view>& query_param_map, const std::map<absl::string_view, absl::string_view>& query_param_map,
const std::vector<grpc_core::URI::QueryParam>& query_param_pairs, const std::vector<URI::QueryParam>& query_param_pairs,
absl::string_view fragment) { absl::string_view fragment) {
absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(uri_text); absl::StatusOr<URI> uri = URI::Parse(uri_text);
ASSERT_TRUE(uri.ok()); ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(scheme, uri->scheme()); EXPECT_EQ(scheme, uri->scheme());
EXPECT_EQ(authority, uri->authority()); EXPECT_EQ(authority, uri->authority());
EXPECT_EQ(path, uri->path()); EXPECT_EQ(path, uri->path());
@ -51,11 +53,12 @@ static void TestSucceeds(
} }
static void TestFails(absl::string_view uri_text) { static void TestFails(absl::string_view uri_text) {
absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(uri_text); absl::StatusOr<URI> uri = URI::Parse(uri_text);
ASSERT_FALSE(uri.ok()); ASSERT_FALSE(uri.ok());
} }
};
TEST(URIParserTest, BasicExamplesAreParsedCorrectly) { TEST_F(URIParserTest, BasicExamplesAreParsedCorrectly) {
TestSucceeds("http://www.google.com", "http", "www.google.com", "", {}, {}, TestSucceeds("http://www.google.com", "http", "www.google.com", "", {}, {},
""); "");
TestSucceeds("dns:///foo", "dns", "", "/foo", {}, {}, ""); TestSucceeds("dns:///foo", "dns", "", "/foo", {}, {}, "");
@ -75,7 +78,7 @@ TEST(URIParserTest, BasicExamplesAreParsedCorrectly) {
"buckle/my/shoe"); "buckle/my/shoe");
} }
TEST(URIParserTest, UncommonValidExamplesAreParsedCorrectly) { TEST_F(URIParserTest, UncommonValidExamplesAreParsedCorrectly) {
TestSucceeds("scheme:path//is/ok", "scheme", "", "path//is/ok", {}, {}, ""); TestSucceeds("scheme:path//is/ok", "scheme", "", "path//is/ok", {}, {}, "");
TestSucceeds("http:?legit", "http", "", "", {{"legit", ""}}, {{"legit", ""}}, TestSucceeds("http:?legit", "http", "", "", {{"legit", ""}}, {{"legit", ""}},
""); "");
@ -84,15 +87,18 @@ TEST(URIParserTest, UncommonValidExamplesAreParsedCorrectly) {
TestSucceeds("http:?legit#twice", "http", "", "", {{"legit", ""}}, TestSucceeds("http:?legit#twice", "http", "", "", {{"legit", ""}},
{{"legit", ""}}, "twice"); {{"legit", ""}}, "twice");
TestSucceeds("fake:///", "fake", "", "/", {}, {}, ""); TestSucceeds("fake:///", "fake", "", "/", {}, {}, "");
TestSucceeds("http://local%25host:8080/whatz%25it?1%25=2%25#fragment", "http",
"local%host:8080", "/whatz%it", {{"1%", "2%"}}, {{"1%", "2%"}},
"fragment");
} }
TEST(URIParserTest, VariousKeyValueAndNonKVQueryParamsAreParsedCorrectly) { TEST_F(URIParserTest, VariousKeyValueAndNonKVQueryParamsAreParsedCorrectly) {
TestSucceeds("http://foo/path?a&b=B&c=&#frag", "http", "foo", "/path", TestSucceeds("http://foo/path?a&b=B&c=&#frag", "http", "foo", "/path",
{{"c", ""}, {"a", ""}, {"b", "B"}}, {{"c", ""}, {"a", ""}, {"b", "B"}},
{{"a", ""}, {"b", "B"}, {"c", ""}}, "frag"); {{"a", ""}, {"b", "B"}, {"c", ""}}, "frag");
} }
TEST(URIParserTest, ParserTreatsFirstEqualSignAsKVDelimiterInQueryString) { TEST_F(URIParserTest, ParserTreatsFirstEqualSignAsKVDelimiterInQueryString) {
TestSucceeds( TestSucceeds(
"http://localhost:8080/?too=many=equals&are=present=here#fragged", "http", "http://localhost:8080/?too=many=equals&are=present=here#fragged", "http",
"localhost:8080", "/", {{"are", "present=here"}, {"too", "many=equals"}}, "localhost:8080", "/", {{"are", "present=here"}, {"too", "many=equals"}},
@ -102,52 +108,48 @@ TEST(URIParserTest, ParserTreatsFirstEqualSignAsKVDelimiterInQueryString) {
{{"foo", "bar=baz"}, {"foobar", "=="}}, ""); {{"foo", "bar=baz"}, {"foobar", "=="}}, "");
} }
TEST(URIParserTest, TEST_F(URIParserTest,
RepeatedQueryParamsAreSupportedInOrderedPairsButDeduplicatedInTheMap) { RepeatedQueryParamsAreSupportedInOrderedPairsButDeduplicatedInTheMap) {
absl::StatusOr<grpc_core::URI> uri = absl::StatusOr<URI> uri = URI::Parse("http://foo/path?a=2&a=1&a=3");
grpc_core::URI::Parse("http://foo/path?a=2&a=1&a=3"); ASSERT_TRUE(uri.ok()) << uri.status().ToString();
ASSERT_TRUE(uri.ok());
// The map stores the last found value. // The map stores the last found value.
ASSERT_THAT(uri->query_parameter_map(), ElementsAre(Pair("a", "3"))); ASSERT_THAT(uri->query_parameter_map(), ElementsAre(Pair("a", "3")));
// Order matters for query parameter pairs // Order matters for query parameter pairs
ASSERT_THAT(uri->query_parameter_pairs(), ASSERT_THAT(uri->query_parameter_pairs(),
ElementsAre(grpc_core::URI::QueryParam{"a", "2"}, ElementsAre(URI::QueryParam{"a", "2"}, URI::QueryParam{"a", "1"},
grpc_core::URI::QueryParam{"a", "1"}, URI::QueryParam{"a", "3"}));
grpc_core::URI::QueryParam{"a", "3"}));
} }
TEST(URIParserTest, QueryParamMapRemainsValiditAfterMovingTheURI) { TEST_F(URIParserTest, QueryParamMapRemainsValiditAfterMovingTheURI) {
grpc_core::URI uri_copy; URI uri_copy;
{ {
absl::StatusOr<grpc_core::URI> uri = absl::StatusOr<URI> uri = URI::Parse("http://foo/path?a=2&b=1&c=3");
grpc_core::URI::Parse("http://foo/path?a=2&b=1&c=3"); ASSERT_TRUE(uri.ok()) << uri.status().ToString();
ASSERT_TRUE(uri.ok());
uri_copy = std::move(*uri); uri_copy = std::move(*uri);
} }
// ASSERT_EQ(uri_copy.query_parameter_map().find("a")->second, "2"); // ASSERT_EQ(uri_copy.query_parameter_map().find("a")->second, "2");
ASSERT_THAT(uri_copy.query_parameter_map(), Contains(Pair("a", "2"))); ASSERT_THAT(uri_copy.query_parameter_map(), Contains(Pair("a", "2")));
} }
TEST(URIParserTest, QueryParamMapRemainsValidAfterCopyingTheURI) { TEST_F(URIParserTest, QueryParamMapRemainsValidAfterCopyingTheURI) {
// Since the query parameter map points to objects stored in the param pair // Since the query parameter map points to objects stored in the param pair
// vector, this test checks that the param map pointers remain valid after // vector, this test checks that the param map pointers remain valid after
// a copy. Ideally {a,m}san will catch this if there's a problem. // a copy. Ideally {a,m}san will catch this if there's a problem.
// testing copy operator=: // testing copy operator=:
grpc_core::URI uri_copy; URI uri_copy;
{ {
absl::StatusOr<grpc_core::URI> del_uri = absl::StatusOr<URI> del_uri = URI::Parse("http://foo/path?a=2&b=1&c=3");
grpc_core::URI::Parse("http://foo/path?a=2&b=1&c=3"); ASSERT_TRUE(del_uri.ok()) << del_uri.status().ToString();
ASSERT_TRUE(del_uri.ok());
uri_copy = *del_uri; uri_copy = *del_uri;
} }
ASSERT_THAT(uri_copy.query_parameter_map(), Contains(Pair("a", "2"))); ASSERT_THAT(uri_copy.query_parameter_map(), Contains(Pair("a", "2")));
grpc_core::URI* del_uri2 = new grpc_core::URI(uri_copy); URI* del_uri2 = new URI(uri_copy);
grpc_core::URI uri_copy2(*del_uri2); URI uri_copy2(*del_uri2);
delete del_uri2; delete del_uri2;
ASSERT_THAT(uri_copy2.query_parameter_map(), Contains(Pair("a", "2"))); ASSERT_THAT(uri_copy2.query_parameter_map(), Contains(Pair("a", "2")));
} }
TEST(URIParserTest, AWSExternalAccountRegressionTest) { TEST_F(URIParserTest, AWSExternalAccountRegressionTest) {
TestSucceeds( TestSucceeds(
"https://foo.com:5555/v1/" "https://foo.com:5555/v1/"
"token-exchange?subject_token=eyJhbGciO&subject_token_type=urn:ietf:" "token-exchange?subject_token=eyJhbGciO&subject_token_type=urn:ietf:"
@ -160,20 +162,21 @@ TEST(URIParserTest, AWSExternalAccountRegressionTest) {
""); "");
} }
TEST(URIParserTest, NonKeyValueQueryStringsWork) { TEST_F(URIParserTest, NonKeyValueQueryStringsWork) {
TestSucceeds("http://www.google.com?yay-i'm-using-queries", "http", TestSucceeds("http://www.google.com?yay-i'm-using-queries", "http",
"www.google.com", "", {{"yay-i'm-using-queries", ""}}, "www.google.com", "", {{"yay-i'm-using-queries", ""}},
{{"yay-i'm-using-queries", ""}}, ""); {{"yay-i'm-using-queries", ""}}, "");
} }
TEST(URIParserTest, IPV6StringsAreParsedCorrectly) { TEST_F(URIParserTest, IPV6StringsAreParsedCorrectly) {
TestSucceeds("ipv6:[2001:db8::1%252]:12345", "ipv6", "", TestSucceeds("ipv6:[2001:db8::1%252]:12345", "ipv6", "",
"[2001:db8::1%2]:12345", {}, {}, ""); "[2001:db8::1%2]:12345", {}, {}, "");
TestSucceeds("ipv6:[fe80::90%eth1.sky1]:6010", "ipv6", "", TestSucceeds("ipv6:[fe80::90%eth1.sky1]:6010", "ipv6", "",
"[fe80::90%eth1.sky1]:6010", {}, {}, ""); "[fe80::90%eth1.sky1]:6010", {}, {}, "");
} }
TEST(URIParserTest, PreviouslyReservedCharactersInUnrelatedURIPartsAreIgnored) { TEST_F(URIParserTest,
PreviouslyReservedCharactersInUnrelatedURIPartsAreIgnored) {
// The '?' and '/' characters are not reserved delimiter characters in the // The '?' and '/' characters are not reserved delimiter characters in the
// fragment. See http://go/rfc/3986#section-3.5 // fragment. See http://go/rfc/3986#section-3.5
TestSucceeds("http://foo?bar#lol?", "http", "foo", "", {{"bar", ""}}, TestSucceeds("http://foo?bar#lol?", "http", "foo", "", {{"bar", ""}},
@ -182,30 +185,30 @@ TEST(URIParserTest, PreviouslyReservedCharactersInUnrelatedURIPartsAreIgnored) {
{{"bar", ""}}, "lol?/"); {{"bar", ""}}, "lol?/");
} }
TEST(URIParserTest, EncodedCharactersInQueryStringAreParsedCorrectly) { TEST_F(URIParserTest, EncodedCharactersInQueryStringAreParsedCorrectly) {
TestSucceeds("https://www.google.com/?a=1%26b%3D2&c=3", "https", TestSucceeds("https://www.google.com/?a=1%26b%3D2&c=3", "https",
"www.google.com", "/", {{"c", "3"}, {"a", "1&b=2"}}, "www.google.com", "/", {{"c", "3"}, {"a", "1&b=2"}},
{{"a", "1&b=2"}, {"c", "3"}}, ""); {{"a", "1&b=2"}, {"c", "3"}}, "");
} }
TEST(URIParserTest, InvalidPercentEncodingsArePassedThrough) { TEST_F(URIParserTest, InvalidPercentEncodingsArePassedThrough) {
TestSucceeds("x:y?%xx", "x", "", "y", {{"%xx", ""}}, {{"%xx", ""}}, ""); TestSucceeds("x:y?%xx", "x", "", "y", {{"%xx", ""}}, {{"%xx", ""}}, "");
TestSucceeds("http:?dangling-pct-%0", "http", "", "", TestSucceeds("http:?dangling-pct-%0", "http", "", "",
{{"dangling-pct-%0", ""}}, {{"dangling-pct-%0", ""}}, ""); {{"dangling-pct-%0", ""}}, {{"dangling-pct-%0", ""}}, "");
} }
TEST(URIParserTest, NullCharactersInURIStringAreSupported) { TEST_F(URIParserTest, NullCharactersInURIStringAreSupported) {
// Artificial examples to show that embedded nulls are supported. // Artificial examples to show that embedded nulls are supported.
TestSucceeds(std::string("unix-abstract:\0should-be-ok", 27), "unix-abstract", TestSucceeds(std::string("unix-abstract:\0should-be-ok", 27), "unix-abstract",
"", std::string("\0should-be-ok", 13), {}, {}, ""); "", std::string("\0should-be-ok", 13), {}, {}, "");
} }
TEST(URIParserTest, EncodedNullsInURIStringAreSupported) { TEST_F(URIParserTest, EncodedNullsInURIStringAreSupported) {
TestSucceeds("unix-abstract:%00x", "unix-abstract", "", std::string("\0x", 2), TestSucceeds("unix-abstract:%00x", "unix-abstract", "", std::string("\0x", 2),
{}, {}, ""); {}, {}, "");
} }
TEST(URIParserTest, InvalidURIsResultInFailureStatuses) { TEST_F(URIParserTest, InvalidURIsResultInFailureStatuses) {
TestFails("xyz"); TestFails("xyz");
TestFails("http://foo?[bar]"); TestFails("http://foo?[bar]");
TestFails("http://foo?x[bar]"); TestFails("http://foo?x[bar]");
@ -215,13 +218,253 @@ TEST(URIParserTest, InvalidURIsResultInFailureStatuses) {
TestFails("0invalid_scheme:must_start/with?alpha"); TestFails("0invalid_scheme:must_start/with?alpha");
} }
TEST(URIParserTest, PercentEncode) { TEST(URITest, PercentEncodePath) {
// Ensure "?" and "=" are percent encoded; and ":" is escaped. EXPECT_EQ(URI::PercentEncodePath(
std::string input = "127.0.0.1:22222?psm_project_id=1234"; // These chars are allowed.
EXPECT_EQ("127.0.0.1:22222%3fpsm_project_id%3d1234", "abcdefghijklmnopqrstuvwxyz"
grpc_core::URI::PercentEncode(input)); "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"/:@-._~!$&'()*+,;="
// These chars will be escaped.
"\\?%#[]^"),
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"/:@-._~!$&'()*+,;="
"%5C%3F%25%23%5B%5D%5E");
}
TEST(URITest, Basic) {
auto uri =
URI::Create("http", "server.example.com", "/path/to/file.html", {}, "");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "server.example.com");
EXPECT_EQ(uri->path(), "/path/to/file.html");
EXPECT_THAT(uri->query_parameter_pairs(), testing::ElementsAre());
EXPECT_THAT(uri->query_parameter_map(), testing::ElementsAre());
EXPECT_EQ(uri->fragment(), "");
EXPECT_EQ("http://server.example.com/path/to/file.html", uri->ToString());
} }
TEST(URITest, NoAuthority) {
auto uri = URI::Create("http", "", "/path/to/file.html", {}, "");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "");
EXPECT_EQ(uri->path(), "/path/to/file.html");
EXPECT_THAT(uri->query_parameter_pairs(), testing::ElementsAre());
EXPECT_THAT(uri->query_parameter_map(), testing::ElementsAre());
EXPECT_EQ(uri->fragment(), "");
EXPECT_EQ("http:/path/to/file.html", uri->ToString());
}
TEST(URITest, NoAuthorityRelativePath) {
auto uri = URI::Create("http", "", "path/to/file.html", {}, "");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "");
EXPECT_EQ(uri->path(), "path/to/file.html");
EXPECT_THAT(uri->query_parameter_pairs(), testing::ElementsAre());
EXPECT_THAT(uri->query_parameter_map(), testing::ElementsAre());
EXPECT_EQ(uri->fragment(), "");
EXPECT_EQ("http:path/to/file.html", uri->ToString());
}
TEST(URITest, AuthorityRelativePath) {
auto uri =
URI::Create("http", "server.example.com", "path/to/file.html", {}, "");
ASSERT_FALSE(uri.ok());
EXPECT_EQ(uri.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(uri.status().message(),
"if authority is present, path must start with a '/'");
}
TEST(URITest, QueryParams) {
auto uri = URI::Create("http", "server.example.com", "/path/to/file.html",
{{"key", "value"}, {"key2", "value2"}}, "");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "server.example.com");
EXPECT_EQ(uri->path(), "/path/to/file.html");
EXPECT_THAT(
uri->query_parameter_pairs(),
testing::ElementsAre(
testing::AllOf(testing::Field(&URI::QueryParam::key, "key"),
testing::Field(&URI::QueryParam::value, "value")),
testing::AllOf(testing::Field(&URI::QueryParam::key, "key2"),
testing::Field(&URI::QueryParam::value, "value2"))));
EXPECT_THAT(uri->query_parameter_map(),
testing::ElementsAre(testing::Pair("key", "value"),
testing::Pair("key2", "value2")));
EXPECT_EQ(uri->fragment(), "");
EXPECT_EQ("http://server.example.com/path/to/file.html?key=value&key2=value2",
uri->ToString());
}
TEST(URITest, DuplicateQueryParams) {
auto uri = URI::Create(
"http", "server.example.com", "/path/to/file.html",
{{"key", "value"}, {"key2", "value2"}, {"key", "other_value"}}, "");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "server.example.com");
EXPECT_EQ(uri->path(), "/path/to/file.html");
EXPECT_THAT(
uri->query_parameter_pairs(),
testing::ElementsAre(
testing::AllOf(testing::Field(&URI::QueryParam::key, "key"),
testing::Field(&URI::QueryParam::value, "value")),
testing::AllOf(testing::Field(&URI::QueryParam::key, "key2"),
testing::Field(&URI::QueryParam::value, "value2")),
testing::AllOf(
testing::Field(&URI::QueryParam::key, "key"),
testing::Field(&URI::QueryParam::value, "other_value"))));
EXPECT_THAT(uri->query_parameter_map(),
testing::ElementsAre(testing::Pair("key", "other_value"),
testing::Pair("key2", "value2")));
EXPECT_EQ(uri->fragment(), "");
EXPECT_EQ(
"http://server.example.com/path/to/file.html"
"?key=value&key2=value2&key=other_value",
uri->ToString());
}
TEST(URITest, Fragment) {
auto uri = URI::Create("http", "server.example.com", "/path/to/file.html", {},
"fragment");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "server.example.com");
EXPECT_EQ(uri->path(), "/path/to/file.html");
EXPECT_THAT(uri->query_parameter_pairs(), testing::ElementsAre());
EXPECT_THAT(uri->query_parameter_map(), testing::ElementsAre());
EXPECT_EQ(uri->fragment(), "fragment");
EXPECT_EQ("http://server.example.com/path/to/file.html#fragment",
uri->ToString());
}
TEST(URITest, QueryParamsAndFragment) {
auto uri = URI::Create("http", "server.example.com", "/path/to/file.html",
{{"key", "value"}, {"key2", "value2"}}, "fragment");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(), "http");
EXPECT_EQ(uri->authority(), "server.example.com");
EXPECT_EQ(uri->path(), "/path/to/file.html");
EXPECT_THAT(
uri->query_parameter_pairs(),
testing::ElementsAre(
testing::AllOf(testing::Field(&URI::QueryParam::key, "key"),
testing::Field(&URI::QueryParam::value, "value")),
testing::AllOf(testing::Field(&URI::QueryParam::key, "key2"),
testing::Field(&URI::QueryParam::value, "value2"))));
EXPECT_THAT(uri->query_parameter_map(),
testing::ElementsAre(testing::Pair("key", "value"),
testing::Pair("key2", "value2")));
EXPECT_EQ(uri->fragment(), "fragment");
EXPECT_EQ(
"http://server.example.com/path/to/"
"file.html?key=value&key2=value2#fragment",
uri->ToString());
}
TEST(URITest, ToStringPercentEncoding) {
auto uri = URI::Create(
// Scheme allowed chars.
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-."
// Scheme escaped chars.
"%:/?#[]@!$&'()*,;=",
// Authority allowed chars.
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-.+~!$&'()*+,;=:[]@"
// Authority escaped chars.
"%/?#",
// Path allowed chars.
"/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$&'()*+,;=:@"
// Path escaped chars.
"%?#[]",
{{// Query allowed chars.
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
// Query escaped chars.
"%=&#[]",
// Query allowed chars.
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
// Query escaped chars.
"%=&#[]"}},
// Fragment allowed chars.
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?=&"
// Fragment escaped chars.
"%#[]");
ASSERT_TRUE(uri.ok()) << uri.status().ToString();
EXPECT_EQ(uri->scheme(),
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-."
"%:/?#[]@!$&'()*,;=");
EXPECT_EQ(uri->authority(),
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-.+~!$&'()*+,;=:[]@"
"%/?#");
EXPECT_EQ(uri->path(),
"/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$&'()*+,;=:@"
"%?#[]");
EXPECT_THAT(
uri->query_parameter_pairs(),
testing::ElementsAre(testing::AllOf(
testing::Field(
&URI::QueryParam::key,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
"%=&#[]"),
testing::Field(
&URI::QueryParam::value,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
"%=&#[]"))));
EXPECT_THAT(
uri->query_parameter_map(),
testing::ElementsAre(testing::Pair(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
"%=&#[]",
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
"%=&#[]")));
EXPECT_EQ(uri->fragment(),
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?=&"
"%#[]");
EXPECT_EQ(
// Scheme
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-."
"%25%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2C%3B%3D"
// Authority
"://abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-.+~!$&'()*+,;=:[]@"
"%25%2F%3F%23"
// Path
"/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$&'()*+,;=:@"
"%25%3F%23%5B%5D"
// Query
"?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
"%25%3D%26%23%5B%5D"
"=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?"
"%25%3D%26%23%5B%5D"
// Fragment
"#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"-._~!$'()*+,;:@/?=&"
"%25%23%5B%5D",
uri->ToString());
}
} // namespace grpc_core
int main(int argc, char** argv) { int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
grpc::testing::TestEnvironment env(argc, argv); grpc::testing::TestEnvironment env(argc, argv);

Loading…
Cancel
Save