Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

udpa: UDPA URL encoding/decoding utils. #11805

Merged
merged 6 commits into from
Jul 1, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@ GOOGLEAPIS_SHA = "a45019af4d3290f02eaeb1ce10990166978c807cb33a9692141a076ba46d14
PROMETHEUS_GIT_SHA = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" # Nov 17, 2017
PROMETHEUS_SHA = "783bdaf8ee0464b35ec0c8704871e1e72afa0005c3f3587f65d9d6694bf3911b"

UDPA_GIT_SHA = "ca580c4fcf87b178547c2e9e41a2481b0008efe9" # June 24, 2020
UDPA_SHA256 = "a1dc305cd56f1dd393fec8ec6b19f4f7d76af9740c7746e9377c8dd480f77e70"
UDPA_GIT_SHA = "efcf912fb35470672231c7b7bef620f3d17f655a" # June 29, 2020
UDPA_SHA256 = "0f8179fbe3d27b89a4c34b2fbd55832f3b27b6810ea9b03b36d18da2629cc871"

ZIPKINAPI_RELEASE = "0.2.2" # Aug 23, 2019
ZIPKINAPI_SHA256 = "688c4fe170821dd589f36ec45aaadc03a618a40283bc1f97da8fa11686fc816b"
4 changes: 2 additions & 2 deletions generated_api_shadow/bazel/repository_locations.bzl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

202 changes: 169 additions & 33 deletions source/common/config/udpa_resource.cc
Original file line number Diff line number Diff line change
@@ -16,63 +16,199 @@ 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(), "%/?#");
namespace {

// We need to percent-encode authority, id, path and query params. Resource types should not have
// reserved characters.

std::string encodeAuthority(const std::string& authority) {
return PercentEncoding::encode(authority, "%/?#");
}

std::string encodeIdPath(const Protobuf::RepeatedPtrField<std::string>& id) {
std::vector<std::string> path_components;
for (const auto& id_component : resource_name.id()) {
for (const auto& id_component : id) {
path_components.emplace_back(PercentEncoding::encode(id_component, "%:/?#[]"));
}
const std::string path = absl::StrJoin(path_components, "/");
return path.empty() ? "" : absl::StrCat("/", path);
}

std::string encodeContextParams(const udpa::core::v1::ContextParams& context_params,
bool sort_context_params) {
std::vector<std::string> query_param_components;
for (const auto& context_param : resource_name.context().params()) {
for (const auto& context_param : context_params.params()) {
query_param_components.emplace_back(
absl::StrCat(PercentEncoding::encode(context_param.first, "%#[]&="), "=",
PercentEncoding::encode(context_param.second, "%#[]&=")));
}
if (options.sort_context_params_) {
if (sort_context_params) {
std::sort(query_param_components.begin(), query_param_components.end());
}
return query_param_components.empty() ? "" : "?" + absl::StrJoin(query_param_components, "&");
}

std::string encodeDirectives(
const Protobuf::RepeatedPtrField<udpa::core::v1::ResourceLocator::Directive>& directives) {
std::vector<std::string> fragment_components;
const std::string DirectiveEscapeChars = "%#[],";
for (const auto& directive : directives) {
switch (directive.directive_case()) {
case udpa::core::v1::ResourceLocator::Directive::DirectiveCase::kAlt:
fragment_components.emplace_back(absl::StrCat(
"alt=", PercentEncoding::encode(UdpaResourceIdentifier::encodeUrl(directive.alt()),
DirectiveEscapeChars)));
break;
case udpa::core::v1::ResourceLocator::Directive::DirectiveCase::kEntry:
fragment_components.emplace_back(
absl::StrCat("entry=", PercentEncoding::encode(directive.entry(), DirectiveEscapeChars)));
break;
default:
NOT_REACHED_GCOVR_EXCL_LINE;
}
}
return fragment_components.empty() ? "" : "#" + absl::StrJoin(fragment_components, ",");
}

} // namespace

std::string UdpaResourceIdentifier::encodeUrn(const udpa::core::v1::ResourceName& resource_name,
const EncodeOptions& options) {
const std::string authority = encodeAuthority(resource_name.authority());
const std::string id_path = encodeIdPath(resource_name.id());
const std::string query_params =
query_param_components.empty() ? "" : "?" + absl::StrJoin(query_param_components, "&");
return absl::StrCat("udpa://", authority, "/", resource_name.resource_type(),
path.empty() ? "" : "/", path, query_params);
encodeContextParams(resource_name.context(), options.sort_context_params_);
return absl::StrCat("udpa://", authority, "/", resource_name.resource_type(), id_path,
query_params);
}

std::string
UdpaResourceIdentifier::encodeUrl(const udpa::core::v1::ResourceLocator& resource_locator,
const EncodeOptions& options) {
const std::string id_path = encodeIdPath(resource_locator.id());
const std::string fragment = encodeDirectives(resource_locator.directives());
std::string scheme = "udpa:";
switch (resource_locator.scheme()) {
case udpa::core::v1::ResourceLocator::HTTP:
scheme = "http:";
FALLTHRU;
case udpa::core::v1::ResourceLocator::UDPA: {
const std::string authority = encodeAuthority(resource_locator.authority());
const std::string query_params =
encodeContextParams(resource_locator.exact_context(), options.sort_context_params_);
return absl::StrCat(scheme, "//", authority, "/", resource_locator.resource_type(), id_path,
query_params, fragment);
}
case udpa::core::v1::ResourceLocator::FILE: {
return absl::StrCat("file://", id_path, fragment);
}
default:
NOT_REACHED_GCOVR_EXCL_LINE;
}
}

namespace {

void decodePath(absl::string_view path, std::string* resource_type,
Protobuf::RepeatedPtrField<std::string>& id) {
// This is guaranteed by Http::Utility::extractHostPathFromUrn.
ASSERT(absl::StartsWith(path, "/"));
const std::vector<absl::string_view> path_components = absl::StrSplit(path.substr(1), '/');
auto id_it = path_components.cbegin();
if (resource_type != nullptr) {
*resource_type = std::string(path_components[0]);
if (resource_type->empty()) {
throw UdpaResourceIdentifier::DecodeException(
fmt::format("Resource type missing from {}", path));
}
id_it = std::next(id_it);
}
for (; id_it != path_components.cend(); id_it++) {
*id.Add() = PercentEncoding::decode(*id_it);
}
}

void decodeQueryParams(absl::string_view query_params,
udpa::core::v1::ContextParams& context_params) {
Http::Utility::QueryParams query_params_components =
Http::Utility::parseQueryString(query_params);
for (const auto& it : query_params_components) {
(*context_params.mutable_params())[PercentEncoding::decode(it.first)] =
PercentEncoding::decode(it.second);
}
}

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 an udpa scheme", resource_uri));
void decodeFragment(
absl::string_view fragment,
Protobuf::RepeatedPtrField<udpa::core::v1::ResourceLocator::Directive>& directives) {
const std::vector<absl::string_view> fragment_components = absl::StrSplit(fragment, ',');
for (const absl::string_view& fragment_component : fragment_components) {
if (absl::StartsWith(fragment_component, "alt=")) {
directives.Add()->mutable_alt()->MergeFrom(
UdpaResourceIdentifier::decodeUrl(PercentEncoding::decode(fragment_component.substr(4))));
} else if (absl::StartsWith(fragment_component, "entry=")) {
directives.Add()->set_entry(PercentEncoding::decode(fragment_component.substr(6)));
} else {
throw UdpaResourceIdentifier::DecodeException(
fmt::format("Unknown fragment component {}", fragment_component));
;
}
}
}

} // namespace

udpa::core::v1::ResourceName UdpaResourceIdentifier::decodeUrn(absl::string_view resource_urn) {
if (!absl::StartsWith(resource_urn, "udpa:")) {
throw UdpaResourceIdentifier::DecodeException(
fmt::format("{} does not have an udpa: scheme", resource_urn));
}
absl::string_view host, path;
Http::Utility::extractHostPathFromUri(resource_uri, host, path);
Http::Utility::extractHostPathFromUri(resource_urn, 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);
}
decodeQueryParams(path.substr(query_params_start), *decoded_resource_name.mutable_context());
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_resource_type(std::string(path_components[0]));
if (decoded_resource_name.resource_type().empty()) {
throw UdpaResourceName::DecodeException(
fmt::format("Qualified type missing from {}", resource_uri));
decodePath(path, decoded_resource_name.mutable_resource_type(),
*decoded_resource_name.mutable_id());
return decoded_resource_name;
}

udpa::core::v1::ResourceLocator UdpaResourceIdentifier::decodeUrl(absl::string_view resource_url) {
absl::string_view host, path;
Http::Utility::extractHostPathFromUri(resource_url, host, path);
udpa::core::v1::ResourceLocator decoded_resource_locator;
const size_t fragment_start = path.find('#');
if (fragment_start != absl::string_view::npos) {
decodeFragment(path.substr(fragment_start + 1), *decoded_resource_locator.mutable_directives());
path = path.substr(0, fragment_start);
}
for (auto it = std::next(path_components.cbegin()); it != path_components.cend(); it++) {
decoded_resource_name.add_id(PercentEncoding::decode(*it));
if (absl::StartsWith(resource_url, "udpa:")) {
decoded_resource_locator.set_scheme(udpa::core::v1::ResourceLocator::UDPA);
} else if (absl::StartsWith(resource_url, "http:")) {
decoded_resource_locator.set_scheme(udpa::core::v1::ResourceLocator::HTTP);
} else if (absl::StartsWith(resource_url, "file:")) {
decoded_resource_locator.set_scheme(udpa::core::v1::ResourceLocator::FILE);
// File URLs only have a path and fragment.
decodePath(path, nullptr, *decoded_resource_locator.mutable_id());
return decoded_resource_locator;
} else {
throw UdpaResourceIdentifier::DecodeException(
fmt::format("{} does not have a udpa:, http: or file: scheme", resource_url));
}
return decoded_resource_name;
decoded_resource_locator.set_authority(PercentEncoding::decode(host));
const size_t query_params_start = path.find('?');
if (query_params_start != absl::string_view::npos) {
decodeQueryParams(path.substr(query_params_start),
*decoded_resource_locator.mutable_exact_context());
path = path.substr(0, query_params_start);
}
decodePath(path, decoded_resource_locator.mutable_resource_type(),
*decoded_resource_locator.mutable_id());
return decoded_resource_locator;
}

} // namespace Config
45 changes: 34 additions & 11 deletions source/common/config/udpa_resource.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#include "envoy/common/exception.h"

#include "absl/strings/string_view.h"
#include "udpa/core/v1/resource_locator.pb.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 {
// Utilities for URI encoding/decoding of udpa::core::v1::Resource{Name,Locator}.
class UdpaResourceIdentifier {
public:
// Options for encoded URIs.
struct EncodeOptions {
@@ -16,16 +17,29 @@ class UdpaResourceName {
};

/**
* Encode a udpa::core::v1::ResourceName message as a udpa:// URI string.
* Encode a udpa::core::v1::ResourceName message as a udpa:// URN string.
*
* @param resource_name resource name message.
* @param options encoding options.
* @return std::string udpa:// URI for resource_name.
* @return std::string udpa:// URN for resource_name.
*/
static std::string encodeUri(const udpa::core::v1::ResourceName& resource_name,
static std::string encodeUrn(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, {});
static std::string encodeUrn(const udpa::core::v1::ResourceName& resource_name) {
return encodeUrn(resource_name, {});
}

/**
* Encode a udpa::core::v1::ResourceLocator message as a udpa:// URL string.
*
* @param resource_name resource name message.
* @param options encoding options.
* @return std::string udpa:// URL for resource_name.
*/
static std::string encodeUrl(const udpa::core::v1::ResourceLocator& resource_locator,
const EncodeOptions& options);
static std::string encodeUrl(const udpa::core::v1::ResourceLocator& resource_locator) {
return encodeUrl(resource_locator, {});
}

// Thrown when an exception occurs during URI decoding.
@@ -35,13 +49,22 @@ class UdpaResourceName {
};

/**
* Decode a udpa:// URI string to a udpa::core::v1::ResourceName.
* Decode a udpa:// URN string to a udpa::core::v1::ResourceName.
*
* @param resource_urn udpa:// resource URN.
* @return udpa::core::v1::ResourceName resource name message for resource_urn.
* @throws DecodeException when parsing fails.
*/
static udpa::core::v1::ResourceName decodeUrn(absl::string_view resource_urn);

/**
* Decode a udpa:// URL string to a udpa::core::v1::ResourceLocator.
*
* @param resource_uri udpa:// resource URI.
* @return udpa::core::v1::ResourceName resource name message for resource_uri.
* @param resource_url udpa:// resource URL.
* @return udpa::core::v1::ResourceLocator resource name message for resource_url.
* @throws DecodeException when parsing fails.
*/
static udpa::core::v1::ResourceName decodeUri(absl::string_view resource_uri);
static udpa::core::v1::ResourceLocator decodeUrl(absl::string_view resource_url);
};

} // namespace Config
Loading