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
734
std::string encodeAuthority(const std::string& authority) {
26
734
  return PercentEncoding::encode(authority, "%/?#");
27
734
}
28

            
29
735
std::string encodeIdPath(const std::string& id) {
30
735
  const std::string path = PercentEncoding::encode(id, "%?#[]");
31
735
  return path.empty() ? "" : absl::StrCat("/", path);
32
735
}
33

            
34
std::string encodeContextParams(const xds::core::v3::ContextParams& context_params,
35
734
                                bool sort_context_params) {
36
734
  std::vector<std::string> query_param_components;
37
856
  for (const auto& context_param : context_params.params()) {
38
487
    query_param_components.emplace_back(
39
487
        absl::StrCat(PercentEncoding::encode(context_param.first, "%#[]&="), "=",
40
487
                     PercentEncoding::encode(context_param.second, "%#[]&=")));
41
487
  }
42
734
  if (sort_context_params) {
43
650
    std::sort(query_param_components.begin(), query_param_components.end());
44
650
  }
45
734
  return query_param_components.empty() ? "" : "?" + absl::StrJoin(query_param_components, "&");
46
734
}
47

            
48
std::string encodeDirectives(
49
102
    const Protobuf::RepeatedPtrField<xds::core::v3::ResourceLocator::Directive>& directives) {
50
102
  std::vector<std::string> fragment_components;
51
102
  const std::string DirectiveEscapeChars = "%#[],";
52
102
  for (const auto& directive : directives) {
53
4
    switch (directive.directive_case()) {
54
1
    case xds::core::v3::ResourceLocator::Directive::DirectiveCase::kAlt:
55
1
      fragment_components.emplace_back(absl::StrCat(
56
1
          "alt=", PercentEncoding::encode(XdsResourceIdentifier::encodeUrl(directive.alt()),
57
1
                                          DirectiveEscapeChars)));
58
1
      break;
59
3
    case xds::core::v3::ResourceLocator::Directive::DirectiveCase::kEntry:
60
3
      fragment_components.emplace_back(
61
3
          absl::StrCat("entry=", PercentEncoding::encode(directive.entry(), DirectiveEscapeChars)));
62
3
      break;
63
    case xds::core::v3::ResourceLocator::Directive::DirectiveCase::DIRECTIVE_NOT_SET:
64
      PANIC_DUE_TO_PROTO_UNSET;
65
4
    }
66
4
  }
67
102
  return fragment_components.empty() ? "" : "#" + absl::StrJoin(fragment_components, ",");
68
102
}
69

            
70
} // namespace
71

            
72
std::string XdsResourceIdentifier::encodeUrn(const xds::core::v3::ResourceName& resource_name,
73
633
                                             const EncodeOptions& options) {
74
633
  const std::string authority = encodeAuthority(resource_name.authority());
75
633
  const std::string id_path = encodeIdPath(resource_name.id());
76
633
  const std::string query_params =
77
633
      encodeContextParams(resource_name.context(), options.sort_context_params_);
78
633
  return absl::StrCat("xdstp://", authority, "/", resource_name.resource_type(), id_path,
79
633
                      query_params);
80
633
}
81

            
82
std::string XdsResourceIdentifier::encodeUrl(const xds::core::v3::ResourceLocator& resource_locator,
83
102
                                             const EncodeOptions& options) {
84
102
  const std::string id_path = encodeIdPath(resource_locator.id());
85
102
  const std::string fragment = encodeDirectives(resource_locator.directives());
86
102
  std::string scheme = "xdstp:";
87
102
  switch (resource_locator.scheme()) {
88
    PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
89
1
  case xds::core::v3::ResourceLocator::HTTP:
90
1
    scheme = "http:";
91
1
    FALLTHRU;
92
101
  case xds::core::v3::ResourceLocator::XDSTP: {
93
101
    const std::string authority = encodeAuthority(resource_locator.authority());
94
101
    const std::string query_params =
95
101
        encodeContextParams(resource_locator.exact_context(), options.sort_context_params_);
96
101
    return absl::StrCat(scheme, "//", authority, "/", resource_locator.resource_type(), id_path,
97
101
                        query_params, fragment);
98
1
  }
99
1
  case xds::core::v3::ResourceLocator::FILE: {
100
1
    return absl::StrCat("file://", id_path, fragment);
101
1
  }
102
102
  }
103
  return "";
104
102
}
105

            
106
namespace {
107

            
108
688
absl::Status decodePath(absl::string_view path, std::string* resource_type, std::string& id) {
109
  // This is guaranteed by Http::Utility::extractHostPathFromUrn.
110
688
  ASSERT(absl::StartsWith(path, "/"));
111
688
  const std::vector<absl::string_view> path_components = absl::StrSplit(path.substr(1), '/');
112
688
  auto id_it = path_components.cbegin();
113
688
  if (resource_type != nullptr) {
114
684
    *resource_type = std::string(path_components[0]);
115
684
    if (resource_type->empty()) {
116
2
      return absl::InvalidArgumentError(fmt::format("Resource type missing from {}", path));
117
2
    }
118
682
    id_it = std::next(id_it);
119
682
  }
120
686
  id = PercentEncoding::decode(absl::StrJoin(id_it, path_components.cend(), "/"));
121
686
  return absl::OkStatus();
122
688
}
123

            
124
void decodeQueryParams(absl::string_view query_params,
125
134
                       xds::core::v3::ContextParams& context_params) {
126
134
  auto query_params_components = Http::Utility::QueryParamsMulti::parseQueryString(query_params);
127
256
  for (const auto& it : query_params_components.data()) {
128
256
    (*context_params.mutable_params())[PercentEncoding::decode(it.first)] =
129
256
        PercentEncoding::decode(it.second[0]);
130
256
  }
131
134
}
132

            
133
absl::Status
134
decodeFragment(absl::string_view fragment,
135
7
               Protobuf::RepeatedPtrField<xds::core::v3::ResourceLocator::Directive>& directives) {
136
7
  const std::vector<absl::string_view> fragment_components = absl::StrSplit(fragment, ',');
137
9
  for (const absl::string_view& fragment_component : fragment_components) {
138
9
    if (absl::StartsWith(fragment_component, "alt=")) {
139
3
      auto url_or_error =
140
3
          XdsResourceIdentifier::decodeUrl(PercentEncoding::decode(fragment_component.substr(4)));
141
3
      RETURN_IF_NOT_OK(url_or_error.status());
142
3
      directives.Add()->mutable_alt()->MergeFrom(url_or_error.value());
143
6
    } else if (absl::StartsWith(fragment_component, "entry=")) {
144
5
      directives.Add()->set_entry(PercentEncoding::decode(fragment_component.substr(6)));
145
5
    } else {
146
1
      return absl::InvalidArgumentError(
147
1
          fmt::format("Unknown fragment component {}", fragment_component));
148
1
    }
149
9
  }
150
6
  return absl::OkStatus();
151
7
}
152

            
153
} // namespace
154

            
155
absl::StatusOr<xds::core::v3::ResourceName>
156
571
XdsResourceIdentifier::decodeUrn(absl::string_view resource_urn) {
157
571
  if (!hasXdsTpScheme(resource_urn)) {
158
1
    return absl::InvalidArgumentError(
159
1
        fmt::format("{} does not have an xdstp: scheme", resource_urn));
160
1
  }
161
570
  absl::string_view host, path;
162
570
  Http::Utility::extractHostPathFromUri(resource_urn, host, path);
163
570
  xds::core::v3::ResourceName decoded_resource_name;
164
570
  decoded_resource_name.set_authority(PercentEncoding::decode(host));
165
570
  const size_t query_params_start = path.find('?');
166
570
  if (query_params_start != absl::string_view::npos) {
167
122
    decodeQueryParams(path.substr(query_params_start), *decoded_resource_name.mutable_context());
168
122
    path = path.substr(0, query_params_start);
169
122
  }
170
570
  auto status = decodePath(path, decoded_resource_name.mutable_resource_type(),
171
570
                           *decoded_resource_name.mutable_id());
172
570
  if (!status.ok()) {
173
1
    return status;
174
1
  }
175
569
  return decoded_resource_name;
176
570
}
177

            
178
absl::StatusOr<xds::core::v3::ResourceLocator>
179
121
XdsResourceIdentifier::decodeUrl(absl::string_view resource_url) {
180
121
  absl::string_view host, path;
181
121
  Http::Utility::extractHostPathFromUri(resource_url, host, path);
182
121
  xds::core::v3::ResourceLocator decoded_resource_locator;
183
121
  const size_t fragment_start = path.find('#');
184
121
  if (fragment_start != absl::string_view::npos) {
185
7
    RETURN_IF_NOT_OK(decodeFragment(path.substr(fragment_start + 1),
186
7
                                    *decoded_resource_locator.mutable_directives()));
187
6
    path = path.substr(0, fragment_start);
188
6
  }
189
120
  if (hasXdsTpScheme(resource_url)) {
190
113
    decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::XDSTP);
191
115
  } else if (absl::StartsWith(resource_url, "http:")) {
192
1
    decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::HTTP);
193
6
  } else if (absl::StartsWith(resource_url, "file:")) {
194
4
    decoded_resource_locator.set_scheme(xds::core::v3::ResourceLocator::FILE);
195
    // File URLs only have a path and fragment.
196
4
    RETURN_IF_NOT_OK(decodePath(path, nullptr, *decoded_resource_locator.mutable_id()));
197
4
    return decoded_resource_locator;
198
5
  } else {
199
2
    return absl::InvalidArgumentError(
200
2
        fmt::format("{} does not have a xdstp:, http: or file: scheme", resource_url));
201
2
  }
202
114
  decoded_resource_locator.set_authority(PercentEncoding::decode(host));
203
114
  const size_t query_params_start = path.find('?');
204
114
  if (query_params_start != absl::string_view::npos) {
205
12
    decodeQueryParams(path.substr(query_params_start),
206
12
                      *decoded_resource_locator.mutable_exact_context());
207
12
    path = path.substr(0, query_params_start);
208
12
  }
209
114
  RETURN_IF_NOT_OK(decodePath(path, decoded_resource_locator.mutable_resource_type(),
210
114
                              *decoded_resource_locator.mutable_id()));
211
113
  return decoded_resource_locator;
212
114
}
213

            
214
7836
bool XdsResourceIdentifier::hasXdsTpScheme(absl::string_view resource_name) {
215
7836
  return absl::StartsWith(resource_name, "xdstp:");
216
7836
}
217

            
218
} // namespace Config
219
} // namespace Envoy