forked from envoyproxy/envoy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
udpa: UDPA URI encoding/decoding utils.
These map between the structured udpa::core::v1::ResourceName message and flat udpa:// URI representations of resource names. Risk level: Low Testing: Unit tests added. Part of envoyproxy#11264. Signed-off-by: Harvey Tuch <[email protected]>
- Loading branch information
Showing
6 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#include "common/config/udpa_resource.h" | ||
|
||
#include <algorithm> | ||
|
||
#include "common/common/fmt.h" | ||
#include "common/http/utility.h" | ||
|
||
#include "absl/strings/str_cat.h" | ||
#include "absl/strings/str_split.h" | ||
|
||
// TODO(htuch): This file has a bunch of ad hoc URI encoding/decoding based on Envoy's HTTP util | ||
// functions. Once https://github.com/envoyproxy/envoy/issues/6588 lands, we can replace with GURL. | ||
|
||
namespace Envoy { | ||
namespace Config { | ||
|
||
using PercentEncoding = Http::Utility::PercentEncoding; | ||
|
||
std::string UdpaResourceName::encodeUri(const udpa::core::v1::ResourceName& resource_name, | ||
const EncodeOptions& options) { | ||
// We need to percent-encode authority, id, path and query params. Qualified types should not have | ||
// reserved characters. | ||
const std::string authority = PercentEncoding::encode(resource_name.authority(), "%/#?&="); | ||
std::vector<std::string> path_components; | ||
for (const auto& id_component : resource_name.id()) { | ||
path_components.emplace_back(PercentEncoding::encode(id_component, "%/#?&=")); | ||
} | ||
const std::string path = absl::StrJoin(path_components, "/"); | ||
std::vector<std::string> query_param_components; | ||
for (const auto& context_param : resource_name.context().params()) { | ||
query_param_components.emplace_back( | ||
absl::StrCat(PercentEncoding::encode(context_param.first, "%#&="), "=", | ||
PercentEncoding::encode(context_param.second, "%#&="))); | ||
} | ||
if (options.sort_context_params_) { | ||
std::sort(query_param_components.begin(), query_param_components.end()); | ||
} | ||
const std::string query_params = | ||
query_param_components.empty() ? "" : "?" + absl::StrJoin(query_param_components, "&"); | ||
return absl::StrCat("udpa://", authority, "/", resource_name.qualified_type(), | ||
path.empty() && query_params.empty() ? "" : "/", path, query_params); | ||
} | ||
|
||
udpa::core::v1::ResourceName UdpaResourceName::decodeUri(absl::string_view resource_uri) { | ||
if (!absl::StartsWith(resource_uri, "udpa://")) { | ||
throw UdpaResourceName::DecodeException( | ||
fmt::format("{} does not have udpa:// scheme", resource_uri)); | ||
} | ||
absl::string_view host, path; | ||
Http::Utility::extractHostPathFromUri(resource_uri, host, path); | ||
udpa::core::v1::ResourceName decoded_resource_name; | ||
decoded_resource_name.set_authority(PercentEncoding::decode(host)); | ||
const size_t query_params_start = path.find('?'); | ||
Http::Utility::QueryParams query_params; | ||
if (query_params_start != absl::string_view::npos) { | ||
query_params = Http::Utility::parseQueryString(path.substr(query_params_start)); | ||
for (const auto& it : query_params) { | ||
(*decoded_resource_name.mutable_context() | ||
->mutable_params())[PercentEncoding::decode(it.first)] = | ||
PercentEncoding::decode(it.second); | ||
} | ||
path = path.substr(0, query_params_start); | ||
} | ||
// This is guaranteed by Http::Utility::extractHostPathFromUri. | ||
ASSERT(absl::StartsWith(path, "/")); | ||
const std::vector<absl::string_view> path_components = absl::StrSplit(path.substr(1), '/'); | ||
decoded_resource_name.set_qualified_type(std::string(path_components[0])); | ||
if (decoded_resource_name.qualified_type().empty()) { | ||
throw UdpaResourceName::DecodeException( | ||
fmt::format("Qualified type missing from {}", resource_uri)); | ||
} | ||
for (auto it = std::next(path_components.cbegin()); it != path_components.cend(); it++) { | ||
decoded_resource_name.add_id(PercentEncoding::decode(*it)); | ||
} | ||
return decoded_resource_name; | ||
} | ||
|
||
} // namespace Config | ||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#include "envoy/common/exception.h" | ||
|
||
#include "absl/strings/string_view.h" | ||
#include "udpa/core/v1/resource_name.pb.h" | ||
|
||
namespace Envoy { | ||
namespace Config { | ||
|
||
// Utilities for URI encoding/decoding of udpa::core::v1::ResourceName. | ||
class UdpaResourceName { | ||
public: | ||
// Options for encoded URIs. | ||
struct EncodeOptions { | ||
// Should the context params be sorted by key? This provides deterministic encoding. | ||
bool sort_context_params_{}; | ||
}; | ||
|
||
/** | ||
* Encode a udpa::core::v1::ResourceName message as a udpa:// URI string. | ||
* | ||
* @param resource_name resource name message. | ||
* @param options encoding options. | ||
* @return std::string udpa:// URI for resource_name. | ||
*/ | ||
static std::string encodeUri(const udpa::core::v1::ResourceName& resource_name, | ||
const EncodeOptions& options); | ||
static std::string encodeUri(const udpa::core::v1::ResourceName& resource_name) { | ||
return encodeUri(resource_name, {}); | ||
} | ||
|
||
// Thrown when an exception occurs during URI decoding. | ||
class DecodeException : public EnvoyException { | ||
public: | ||
DecodeException(const std::string& what) : EnvoyException(what) {} | ||
}; | ||
|
||
/** | ||
* Decode a udpa:// URI string to a udpa::core::v1::ResourceName. | ||
* | ||
* @param resource_uri udpa:// resource URI. | ||
* @return udpa::core::v1::ResourceName resource name message for resource_uri. | ||
* @throws DecodeException when parsing fails. | ||
*/ | ||
static udpa::core::v1::ResourceName decodeUri(absl::string_view resource_uri); | ||
}; | ||
|
||
} // namespace Config | ||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
#include "common/config/udpa_resource.h" | ||
|
||
#include "test/test_common/utility.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
namespace Envoy { | ||
namespace Config { | ||
namespace { | ||
|
||
const std::string EscapedUri = | ||
"udpa://f123%25%2F%23%3F%26%3Do/envoy.config.listener.v3.Listener/b%25%2F%23%3F%26%3Dar//" | ||
"baz?%25%23%26%3Dab=cde%25%23%26%3Df"; | ||
const std::string EscapedUriWithManyQueryParams = | ||
"udpa://f123%25%2F%23%3F%26%3Do/envoy.config.listener.v3.Listener/b%25%2F%23%3F%26%3Dar//" | ||
"baz?%25%23%26%3D=bar&%25%23%26%3Dab=cde%25%23%26%3Df&foo=%25%23%26%3D"; | ||
|
||
// for all x. encodeUri(decodeUri(x)) = x where x comes from sample of valid udpa:// URIs. | ||
// TODO(htuch): write a fuzzer that validates this property as well. | ||
TEST(UdpaResourceNameTest, DecodeEncode) { | ||
const std::vector<std::string> uris = { | ||
"udpa://foo/envoy.config.listener.v3.Listener", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar/baz", | ||
"udpa://foo/envoy.config.listener.v3.Listener/?ab=cde", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar?ab=cd", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar/baz?ab=cde", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar/baz?ab=", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar/baz?=cd", | ||
"udpa://foo/envoy.config.listener.v3.Listener/bar/baz?ab=cde&ba=edc&z=f", | ||
EscapedUri, | ||
EscapedUriWithManyQueryParams, | ||
}; | ||
UdpaResourceName::EncodeOptions encode_options; | ||
encode_options.sort_context_params_ = true; | ||
for (const std::string& uri : uris) { | ||
EXPECT_EQ(uri, UdpaResourceName::encodeUri(UdpaResourceName::decodeUri(uri), encode_options)); | ||
} | ||
} | ||
|
||
// Validate that URI decoding behaves as expected component-wise. | ||
TEST(UdpaResourceNameTest, DecodeSuccess) { | ||
const auto resource_name = UdpaResourceName::decodeUri(EscapedUriWithManyQueryParams); | ||
EXPECT_EQ("f123%/#?&=o", resource_name.authority()); | ||
EXPECT_EQ("envoy.config.listener.v3.Listener", resource_name.qualified_type()); | ||
EXPECT_EQ(3, resource_name.id().size()); | ||
EXPECT_EQ("b%/#?&=ar", resource_name.id()[0]); | ||
EXPECT_EQ("", resource_name.id()[1]); | ||
EXPECT_EQ("baz", resource_name.id()[2]); | ||
EXPECT_EQ(3, resource_name.context().params().size()); | ||
EXPECT_EQ("bar", resource_name.context().params().at("%#&=")); | ||
EXPECT_EQ("cde%#&=f", resource_name.context().params().at("%#&=ab")); | ||
EXPECT_EQ("%#&=", resource_name.context().params().at("foo")); | ||
} | ||
|
||
// Negative tests for URI decoding. | ||
TEST(UdpaResourceNameTest, DecodeFail) { | ||
{ | ||
EXPECT_THROW_WITH_MESSAGE(UdpaResourceName::decodeUri("foo://"), | ||
UdpaResourceName::DecodeException, | ||
"foo:// does not have udpa:// scheme"); | ||
} | ||
{ | ||
EXPECT_THROW_WITH_MESSAGE(UdpaResourceName::decodeUri("udpa://foo"), | ||
UdpaResourceName::DecodeException, | ||
"Qualified type missing from udpa://foo"); | ||
} | ||
} | ||
|
||
} // namespace | ||
} // namespace Config | ||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1102,6 +1102,7 @@ typedef | |
typeid | ||
typesafe | ||
ucontext | ||
udpa | ||
uint | ||
un- | ||
unacked | ||
|