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