Line data Source code
1 : #include "source/common/config/xds_resource.h"
2 :
3 : #include <algorithm>
4 :
5 : #include "source/common/common/fmt.h"
6 : #include "source/common/http/utility.h"
7 :
8 : #include "absl/strings/str_cat.h"
9 : #include "absl/strings/str_split.h"
10 :
11 : // TODO(htuch): This file has a bunch of ad hoc URI encoding/decoding based on Envoy's HTTP util
12 : // functions. Once https://github.com/envoyproxy/envoy/issues/6588 lands, we can replace with GURL.
13 :
14 : namespace Envoy {
15 : namespace Config {
16 :
17 : using PercentEncoding = Http::Utility::PercentEncoding;
18 :
19 : namespace {
20 :
21 : // We need to percent-encode authority, id, path and query params. Resource types should not have
22 : // reserved characters.
23 :
24 0 : std::string encodeAuthority(const std::string& authority) {
25 0 : return PercentEncoding::encode(authority, "%/?#");
26 0 : }
27 :
28 0 : std::string encodeIdPath(const std::string& id) {
29 0 : const std::string path = PercentEncoding::encode(id, "%:?#[]");
30 0 : return path.empty() ? "" : absl::StrCat("/", path);
31 0 : }
32 :
33 : std::string encodeContextParams(const xds::core::v3::ContextParams& context_params,
34 0 : bool sort_context_params) {
35 0 : std::vector<std::string> query_param_components;
36 0 : for (const auto& context_param : context_params.params()) {
37 0 : query_param_components.emplace_back(
38 0 : absl::StrCat(PercentEncoding::encode(context_param.first, "%#[]&="), "=",
39 0 : PercentEncoding::encode(context_param.second, "%#[]&=")));
40 0 : }
41 0 : if (sort_context_params) {
42 0 : std::sort(query_param_components.begin(), query_param_components.end());
43 0 : }
44 0 : return query_param_components.empty() ? "" : "?" + absl::StrJoin(query_param_components, "&");
45 0 : }
46 :
47 : std::string encodeDirectives(
48 0 : const Protobuf::RepeatedPtrField<xds::core::v3::ResourceLocator::Directive>& directives) {
49 0 : std::vector<std::string> fragment_components;
50 0 : const std::string DirectiveEscapeChars = "%#[],";
51 0 : for (const auto& directive : directives) {
52 0 : switch (directive.directive_case()) {
53 0 : case xds::core::v3::ResourceLocator::Directive::DirectiveCase::kAlt:
54 0 : fragment_components.emplace_back(absl::StrCat(
55 0 : "alt=", PercentEncoding::encode(XdsResourceIdentifier::encodeUrl(directive.alt()),
56 0 : DirectiveEscapeChars)));
57 0 : break;
58 0 : case xds::core::v3::ResourceLocator::Directive::DirectiveCase::kEntry:
59 0 : fragment_components.emplace_back(
60 0 : absl::StrCat("entry=", PercentEncoding::encode(directive.entry(), DirectiveEscapeChars)));
61 0 : break;
62 0 : case xds::core::v3::ResourceLocator::Directive::DirectiveCase::DIRECTIVE_NOT_SET:
63 0 : PANIC_DUE_TO_PROTO_UNSET;
64 0 : }
65 0 : }
66 0 : return fragment_components.empty() ? "" : "#" + absl::StrJoin(fragment_components, ",");
67 0 : }
68 :
69 : } // namespace
70 :
71 : std::string XdsResourceIdentifier::encodeUrn(const xds::core::v3::ResourceName& resource_name,
72 0 : const EncodeOptions& options) {
73 0 : const std::string authority = encodeAuthority(resource_name.authority());
74 0 : const std::string id_path = encodeIdPath(resource_name.id());
75 0 : const std::string query_params =
76 0 : encodeContextParams(resource_name.context(), options.sort_context_params_);
77 0 : return absl::StrCat("xdstp://", authority, "/", resource_name.resource_type(), id_path,
78 0 : query_params);
79 0 : }
80 :
81 : std::string XdsResourceIdentifier::encodeUrl(const xds::core::v3::ResourceLocator& resource_locator,
82 0 : const EncodeOptions& options) {
83 0 : const std::string id_path = encodeIdPath(resource_locator.id());
84 0 : const std::string fragment = encodeDirectives(resource_locator.directives());
85 0 : std::string scheme = "xdstp:";
86 0 : switch (resource_locator.scheme()) {
87 0 : PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
88 0 : case xds::core::v3::ResourceLocator::HTTP:
89 0 : scheme = "http:";
90 0 : FALLTHRU;
91 0 : case xds::core::v3::ResourceLocator::XDSTP: {
92 0 : const std::string authority = encodeAuthority(resource_locator.authority());
93 0 : const std::string query_params =
94 0 : encodeContextParams(resource_locator.exact_context(), options.sort_context_params_);
95 0 : return absl::StrCat(scheme, "//", authority, "/", resource_locator.resource_type(), id_path,
96 0 : query_params, fragment);
97 0 : }
98 0 : case xds::core::v3::ResourceLocator::FILE: {
99 0 : return absl::StrCat("file://", id_path, fragment);
100 0 : }
101 0 : }
102 0 : return "";
103 0 : }
104 :
105 : namespace {
106 :
107 0 : absl::Status decodePath(absl::string_view path, std::string* resource_type, std::string& id) {
108 : // This is guaranteed by Http::Utility::extractHostPathFromUrn.
109 0 : ASSERT(absl::StartsWith(path, "/"));
110 0 : const std::vector<absl::string_view> path_components = absl::StrSplit(path.substr(1), '/');
111 0 : auto id_it = path_components.cbegin();
112 0 : if (resource_type != nullptr) {
113 0 : *resource_type = std::string(path_components[0]);
114 0 : if (resource_type->empty()) {
115 0 : return absl::InvalidArgumentError(fmt::format("Resource type missing from {}", path));
116 0 : }
117 0 : id_it = std::next(id_it);
118 0 : }
119 0 : id = PercentEncoding::decode(absl::StrJoin(id_it, path_components.cend(), "/"));
120 0 : return absl::OkStatus();
121 0 : }
122 :
123 : void decodeQueryParams(absl::string_view query_params,
124 0 : xds::core::v3::ContextParams& context_params) {
125 0 : auto query_params_components = Http::Utility::QueryParamsMulti::parseQueryString(query_params);
126 0 : for (const auto& it : query_params_components.data()) {
127 0 : (*context_params.mutable_params())[PercentEncoding::decode(it.first)] =
128 0 : PercentEncoding::decode(it.second[0]);
129 0 : }
130 0 : }
131 :
132 : absl::Status
133 : decodeFragment(absl::string_view fragment,
134 0 : Protobuf::RepeatedPtrField<xds::core::v3::ResourceLocator::Directive>& directives) {
135 0 : const std::vector<absl::string_view> fragment_components = absl::StrSplit(fragment, ',');
136 0 : for (const absl::string_view& fragment_component : fragment_components) {
137 0 : if (absl::StartsWith(fragment_component, "alt=")) {
138 0 : directives.Add()->mutable_alt()->MergeFrom(
139 0 : XdsResourceIdentifier::decodeUrl(PercentEncoding::decode(fragment_component.substr(4))));
140 0 : } else if (absl::StartsWith(fragment_component, "entry=")) {
141 0 : directives.Add()->set_entry(PercentEncoding::decode(fragment_component.substr(6)));
142 0 : } else {
143 0 : return absl::InvalidArgumentError(
144 0 : fmt::format("Unknown fragment component {}", fragment_component));
145 0 : }
146 0 : }
147 0 : return absl::OkStatus();
148 0 : }
149 :
150 : } // namespace
151 :
152 : absl::StatusOr<xds::core::v3::ResourceName>
153 0 : XdsResourceIdentifier::decodeUrn(absl::string_view resource_urn) {
154 0 : if (!hasXdsTpScheme(resource_urn)) {
155 0 : return absl::InvalidArgumentError(
156 0 : fmt::format("{} does not have an xdstp: scheme", resource_urn));
157 0 : }
158 0 : absl::string_view host, path;
159 0 : Http::Utility::extractHostPathFromUri(resource_urn, host, path);
160 0 : xds::core::v3::ResourceName decoded_resource_name;
161 0 : decoded_resource_name.set_authority(PercentEncoding::decode(host));
162 0 : const size_t query_params_start = path.find('?');
163 0 : if (query_params_start != absl::string_view::npos) {
164 0 : decodeQueryParams(path.substr(query_params_start), *decoded_resource_name.mutable_context());
165 0 : path = path.substr(0, query_params_start);
166 0 : }
167 0 : auto status = decodePath(path, decoded_resource_name.mutable_resource_type(),
168 0 : *decoded_resource_name.mutable_id());
169 0 : if (!status.ok()) {
170 0 : return status;
171 0 : }
172 0 : return decoded_resource_name;
173 0 : }
174 :
175 0 : xds::core::v3::ResourceLocator XdsResourceIdentifier::decodeUrl(absl::string_view resource_url) {
176 0 : absl::string_view host, path;
177 0 : Http::Utility::extractHostPathFromUri(resource_url, host, path);
178 0 : xds::core::v3::ResourceLocator decoded_resource_locator;
179 0 : const size_t fragment_start = path.find('#');
180 0 : if (fragment_start != absl::string_view::npos) {
181 0 : THROW_IF_NOT_OK(decodeFragment(path.substr(fragment_start + 1),
182 0 : *decoded_resource_locator.mutable_directives()));
183 0 : path = path.substr(0, fragment_start);
184 0 : }
185 0 : if (hasXdsTpScheme(resource_url)) {
186 0 : decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::XDSTP);
187 0 : } else if (absl::StartsWith(resource_url, "http:")) {
188 0 : decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::HTTP);
189 0 : } else if (absl::StartsWith(resource_url, "file:")) {
190 0 : decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::FILE);
191 : // File URLs only have a path and fragment.
192 0 : THROW_IF_NOT_OK(decodePath(path, nullptr, *decoded_resource_locator.mutable_id()));
193 0 : return decoded_resource_locator;
194 0 : } else {
195 0 : throwEnvoyExceptionOrPanic(
196 0 : fmt::format("{} does not have a xdstp:, http: or file: scheme", resource_url));
197 0 : }
198 0 : decoded_resource_locator.set_authority(PercentEncoding::decode(host));
199 0 : const size_t query_params_start = path.find('?');
200 0 : if (query_params_start != absl::string_view::npos) {
201 0 : decodeQueryParams(path.substr(query_params_start),
202 0 : *decoded_resource_locator.mutable_exact_context());
203 0 : path = path.substr(0, query_params_start);
204 0 : }
205 0 : THROW_IF_NOT_OK(decodePath(path, decoded_resource_locator.mutable_resource_type(),
206 0 : *decoded_resource_locator.mutable_id()));
207 0 : return decoded_resource_locator;
208 0 : }
209 :
210 418 : bool XdsResourceIdentifier::hasXdsTpScheme(absl::string_view resource_name) {
211 418 : return absl::StartsWith(resource_name, "xdstp:");
212 418 : }
213 :
214 : } // namespace Config
215 : } // namespace Envoy
|