Coverage Report

Created: 2024-09-19 09:45

/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