/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 | | |
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 | 26 | void throwDecodeExceptionOrPanic(std::string message) { |
108 | 26 | throwExceptionOrPanic(XdsResourceIdentifier::DecodeException, message); |
109 | 26 | } |
110 | | |
111 | 0 | void decodePath(absl::string_view path, std::string* resource_type, std::string& id) { |
112 | | // This is guaranteed by Http::Utility::extractHostPathFromUrn. |
113 | 0 | ASSERT(absl::StartsWith(path, "/")); |
114 | 0 | const std::vector<absl::string_view> path_components = absl::StrSplit(path.substr(1), '/'); |
115 | 0 | auto id_it = path_components.cbegin(); |
116 | 0 | if (resource_type != nullptr) { |
117 | 0 | *resource_type = std::string(path_components[0]); |
118 | 0 | if (resource_type->empty()) { |
119 | 0 | throwDecodeExceptionOrPanic(fmt::format("Resource type missing from {}", path)); |
120 | 0 | } |
121 | 0 | id_it = std::next(id_it); |
122 | 0 | } |
123 | 0 | id = PercentEncoding::decode(absl::StrJoin(id_it, path_components.cend(), "/")); |
124 | 0 | } |
125 | | |
126 | | void decodeQueryParams(absl::string_view query_params, |
127 | 0 | xds::core::v3::ContextParams& context_params) { |
128 | 0 | auto query_params_components = Http::Utility::QueryParamsMulti::parseQueryString(query_params); |
129 | 0 | for (const auto& it : query_params_components.data()) { |
130 | 0 | (*context_params.mutable_params())[PercentEncoding::decode(it.first)] = |
131 | 0 | PercentEncoding::decode(it.second[0]); |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | | void decodeFragment( |
136 | | absl::string_view fragment, |
137 | 26 | Protobuf::RepeatedPtrField<xds::core::v3::ResourceLocator::Directive>& directives) { |
138 | 26 | const std::vector<absl::string_view> fragment_components = absl::StrSplit(fragment, ','); |
139 | 26 | for (const absl::string_view& fragment_component : fragment_components) { |
140 | 26 | if (absl::StartsWith(fragment_component, "alt=")) { |
141 | 0 | directives.Add()->mutable_alt()->MergeFrom( |
142 | 0 | XdsResourceIdentifier::decodeUrl(PercentEncoding::decode(fragment_component.substr(4)))); |
143 | 26 | } else if (absl::StartsWith(fragment_component, "entry=")) { |
144 | 0 | directives.Add()->set_entry(PercentEncoding::decode(fragment_component.substr(6))); |
145 | 26 | } else { |
146 | 26 | throwDecodeExceptionOrPanic(fmt::format("Unknown fragment component {}", fragment_component)); |
147 | 26 | ; |
148 | 26 | } |
149 | 26 | } |
150 | 26 | } |
151 | | |
152 | | } // namespace |
153 | | |
154 | 0 | xds::core::v3::ResourceName XdsResourceIdentifier::decodeUrn(absl::string_view resource_urn) { |
155 | 0 | if (!hasXdsTpScheme(resource_urn)) { |
156 | 0 | throwDecodeExceptionOrPanic(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 | decodePath(path, decoded_resource_name.mutable_resource_type(), |
168 | 0 | *decoded_resource_name.mutable_id()); |
169 | 0 | return decoded_resource_name; |
170 | 0 | } |
171 | | |
172 | 53 | xds::core::v3::ResourceLocator XdsResourceIdentifier::decodeUrl(absl::string_view resource_url) { |
173 | 53 | absl::string_view host, path; |
174 | 53 | Http::Utility::extractHostPathFromUri(resource_url, host, path); |
175 | 53 | xds::core::v3::ResourceLocator decoded_resource_locator; |
176 | 53 | const size_t fragment_start = path.find('#'); |
177 | 53 | if (fragment_start != absl::string_view::npos) { |
178 | 26 | decodeFragment(path.substr(fragment_start + 1), *decoded_resource_locator.mutable_directives()); |
179 | 26 | path = path.substr(0, fragment_start); |
180 | 26 | } |
181 | 53 | if (hasXdsTpScheme(resource_url)) { |
182 | 0 | decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::XDSTP); |
183 | 53 | } else if (absl::StartsWith(resource_url, "http:")) { |
184 | 0 | decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::HTTP); |
185 | 53 | } else if (absl::StartsWith(resource_url, "file:")) { |
186 | 0 | decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::FILE); |
187 | | // File URLs only have a path and fragment. |
188 | 0 | decodePath(path, nullptr, *decoded_resource_locator.mutable_id()); |
189 | 0 | return decoded_resource_locator; |
190 | 53 | } else { |
191 | 53 | throwExceptionOrPanic( |
192 | 53 | XdsResourceIdentifier::DecodeException, |
193 | 53 | fmt::format("{} does not have a xdstp:, http: or file: scheme", resource_url)); |
194 | 53 | } |
195 | 0 | decoded_resource_locator.set_authority(PercentEncoding::decode(host)); |
196 | 0 | const size_t query_params_start = path.find('?'); |
197 | 0 | if (query_params_start != absl::string_view::npos) { |
198 | 0 | decodeQueryParams(path.substr(query_params_start), |
199 | 0 | *decoded_resource_locator.mutable_exact_context()); |
200 | 0 | path = path.substr(0, query_params_start); |
201 | 0 | } |
202 | 0 | decodePath(path, decoded_resource_locator.mutable_resource_type(), |
203 | 0 | *decoded_resource_locator.mutable_id()); |
204 | 0 | return decoded_resource_locator; |
205 | 53 | } |
206 | | |
207 | 691 | bool XdsResourceIdentifier::hasXdsTpScheme(absl::string_view resource_name) { |
208 | 691 | return absl::StartsWith(resource_name, "xdstp:"); |
209 | 691 | } |
210 | | |
211 | | } // namespace Config |
212 | | } // namespace Envoy |