uri_parser.cc
Go to the documentation of this file.
1 //
2 // Copyright 2015 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
18 
20 
21 #include <ctype.h>
22 #include <stddef.h>
23 
24 #include <algorithm>
25 #include <functional>
26 #include <map>
27 #include <string>
28 #include <utility>
29 
30 #include "absl/status/status.h"
31 #include "absl/strings/ascii.h"
32 #include "absl/strings/escaping.h"
33 #include "absl/strings/match.h"
34 #include "absl/strings/str_cat.h"
35 #include "absl/strings/str_format.h"
36 #include "absl/strings/str_join.h"
37 #include "absl/strings/str_split.h"
38 #include "absl/strings/strip.h"
39 
40 #include <grpc/support/log.h>
41 
42 namespace grpc_core {
43 
44 namespace {
45 
46 // Returns true for any sub-delim character, as defined in:
47 // https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
48 bool IsSubDelimChar(char c) {
49  switch (c) {
50  case '!':
51  case '$':
52  case '&':
53  case '\'':
54  case '(':
55  case ')':
56  case '*':
57  case '+':
58  case ',':
59  case ';':
60  case '=':
61  return true;
62  }
63  return false;
64 }
65 
66 // Returns true for any unreserved character, as defined in:
67 // https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
68 bool IsUnreservedChar(char c) {
69  if (absl::ascii_isalnum(c)) return true;
70  switch (c) {
71  case '-':
72  case '.':
73  case '_':
74  case '~':
75  return true;
76  }
77  return false;
78 }
79 
80 // Returns true for any character in scheme, as defined in:
81 // https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
82 bool IsSchemeChar(char c) {
83  if (absl::ascii_isalnum(c)) return true;
84  switch (c) {
85  case '+':
86  case '-':
87  case '.':
88  return true;
89  }
90  return false;
91 }
92 
93 // Returns true for any character in authority, as defined in:
94 // https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
95 bool IsAuthorityChar(char c) {
96  if (IsUnreservedChar(c)) return true;
97  if (IsSubDelimChar(c)) return true;
98  switch (c) {
99  case ':':
100  case '[':
101  case ']':
102  case '@':
103  return true;
104  }
105  return false;
106 }
107 
108 // Returns true for any character in pchar, as defined in:
109 // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
110 bool IsPChar(char c) {
111  if (IsUnreservedChar(c)) return true;
112  if (IsSubDelimChar(c)) return true;
113  switch (c) {
114  case ':':
115  case '@':
116  return true;
117  }
118  return false;
119 }
120 
121 // Returns true for any character allowed in a URI path, as defined in:
122 // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
123 bool IsPathChar(char c) { return IsPChar(c) || c == '/'; }
124 
125 // Returns true for any character allowed in a URI query or fragment,
126 // as defined in:
127 // See https://tools.ietf.org/html/rfc3986#section-3.4
128 bool IsQueryOrFragmentChar(char c) {
129  return IsPChar(c) || c == '/' || c == '?';
130 }
131 
132 // Same as IsQueryOrFragmentChar(), but excludes '&' and '='.
133 bool IsQueryKeyOrValueChar(char c) {
134  return c != '&' && c != '=' && IsQueryOrFragmentChar(c);
135 }
136 
137 // Returns a copy of str, percent-encoding any character for which
138 // is_allowed_char() returns false.
139 std::string PercentEncode(absl::string_view str,
140  std::function<bool(char)> is_allowed_char) {
142  for (char c : str) {
143  if (!is_allowed_char(c)) {
145  GPR_ASSERT(hex.size() == 2);
146  // BytesToHexString() returns lower case, but
147  // https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1 says
148  // to prefer upper-case.
149  absl::AsciiStrToUpper(&hex);
150  out.push_back('%');
151  out.append(hex);
152  } else {
153  out.push_back(c);
154  }
155  }
156  return out;
157 }
158 
159 // Checks if this string is made up of query/fragment chars and '%' exclusively.
160 // See https://tools.ietf.org/html/rfc3986#section-3.4
161 bool IsQueryOrFragmentString(absl::string_view str) {
162  for (char c : str) {
163  if (!IsQueryOrFragmentChar(c) && c != '%') return false;
164  }
165  return true;
166 }
167 
168 absl::Status MakeInvalidURIStatus(absl::string_view part_name,
169  absl::string_view uri,
170  absl::string_view extra) {
172  "Could not parse '%s' from uri '%s'. %s", part_name, uri, extra));
173 }
174 
175 } // namespace
176 
178  return PercentEncode(str, IsAuthorityChar);
179 }
180 
182  return PercentEncode(str, IsPathChar);
183 }
184 
185 // Similar to `grpc_permissive_percent_decode_slice`, this %-decodes all valid
186 // triplets, and passes through the rest verbatim.
188  if (str.empty() || !absl::StrContains(str, "%")) {
189  return std::string(str);
190  }
192  std::string unescaped;
193  out.reserve(str.size());
194  for (size_t i = 0; i < str.length(); i++) {
195  unescaped = "";
196  if (str[i] == '%' && i + 3 <= str.length() &&
197  absl::CUnescape(absl::StrCat("\\x", str.substr(i + 1, 2)),
198  &unescaped) &&
199  unescaped.length() == 1) {
200  out += unescaped[0];
201  i += 2;
202  } else {
203  out += str[i];
204  }
205  }
206  return out;
207 }
208 
211  absl::string_view remaining = uri_text;
212  // parse scheme
213  size_t offset = remaining.find(':');
214  if (offset == remaining.npos || offset == 0) {
215  return MakeInvalidURIStatus("scheme", uri_text, "Scheme not found.");
216  }
217  std::string scheme(remaining.substr(0, offset));
218  if (scheme.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
219  "abcdefghijklmnopqrstuvwxyz"
220  "0123456789+-.") != std::string::npos) {
221  return MakeInvalidURIStatus("scheme", uri_text,
222  "Scheme contains invalid characters.");
223  }
224  if (!isalpha(scheme[0])) {
225  return MakeInvalidURIStatus(
226  "scheme", uri_text,
227  "Scheme must begin with an alpha character [A-Za-z].");
228  }
229  remaining.remove_prefix(offset + 1);
230  // parse authority
232  if (absl::ConsumePrefix(&remaining, "//")) {
233  offset = remaining.find_first_of("/?#");
234  authority = PercentDecode(remaining.substr(0, offset));
235  if (offset == remaining.npos) {
236  remaining = "";
237  } else {
238  remaining.remove_prefix(offset);
239  }
240  }
241  // parse path
243  if (!remaining.empty()) {
244  offset = remaining.find_first_of("?#");
245  path = PercentDecode(remaining.substr(0, offset));
246  if (offset == remaining.npos) {
247  remaining = "";
248  } else {
249  remaining.remove_prefix(offset);
250  }
251  }
252  // parse query
253  std::vector<QueryParam> query_param_pairs;
254  if (absl::ConsumePrefix(&remaining, "?")) {
255  offset = remaining.find('#');
256  absl::string_view tmp_query = remaining.substr(0, offset);
257  if (tmp_query.empty()) {
258  return MakeInvalidURIStatus("query", uri_text, "Invalid query string.");
259  }
260  if (!IsQueryOrFragmentString(tmp_query)) {
261  return MakeInvalidURIStatus("query string", uri_text,
262  "Query string contains invalid characters.");
263  }
264  for (absl::string_view query_param : absl::StrSplit(tmp_query, '&')) {
265  const std::pair<absl::string_view, absl::string_view> possible_kv =
266  absl::StrSplit(query_param, absl::MaxSplits('=', 1));
267  if (possible_kv.first.empty()) continue;
268  query_param_pairs.push_back({PercentDecode(possible_kv.first),
269  PercentDecode(possible_kv.second)});
270  }
271  if (offset == remaining.npos) {
272  remaining = "";
273  } else {
274  remaining.remove_prefix(offset);
275  }
276  }
278  if (absl::ConsumePrefix(&remaining, "#")) {
279  if (!IsQueryOrFragmentString(remaining)) {
280  return MakeInvalidURIStatus("fragment", uri_text,
281  "Fragment contains invalid characters.");
282  }
283  fragment = PercentDecode(remaining);
284  }
286  std::move(query_param_pairs), std::move(fragment));
287 }
288 
291  std::vector<QueryParam> query_parameter_pairs,
292  std::string fragment) {
293  if (!authority.empty() && !path.empty() && path[0] != '/') {
295  "if authority is present, path must start with a '/'");
296  }
299 }
300 
302  std::vector<QueryParam> query_parameter_pairs, std::string fragment)
303  : scheme_(std::move(scheme)),
304  authority_(std::move(authority)),
305  path_(std::move(path)),
306  query_parameter_pairs_(std::move(query_parameter_pairs)),
307  fragment_(std::move(fragment)) {
308  for (const auto& kv : query_parameter_pairs_) {
309  query_parameter_map_[kv.key] = kv.value;
310  }
311 }
312 
313 URI::URI(const URI& other)
314  : scheme_(other.scheme_),
315  authority_(other.authority_),
316  path_(other.path_),
317  query_parameter_pairs_(other.query_parameter_pairs_),
318  fragment_(other.fragment_) {
319  for (const auto& kv : query_parameter_pairs_) {
320  query_parameter_map_[kv.key] = kv.value;
321  }
322 }
323 
324 URI& URI::operator=(const URI& other) {
325  if (this == &other) {
326  return *this;
327  }
328  scheme_ = other.scheme_;
329  authority_ = other.authority_;
330  path_ = other.path_;
332  fragment_ = other.fragment_;
333  for (const auto& kv : query_parameter_pairs_) {
334  query_parameter_map_[kv.key] = kv.value;
335  }
336  return *this;
337 }
338 
339 namespace {
340 
341 // A pair formatter for use with absl::StrJoin() for formatting query params.
342 struct QueryParameterFormatter {
343  void operator()(std::string* out, const URI::QueryParam& query_param) const {
344  out->append(
345  absl::StrCat(PercentEncode(query_param.key, IsQueryKeyOrValueChar), "=",
346  PercentEncode(query_param.value, IsQueryKeyOrValueChar)));
347  }
348 };
349 
350 } // namespace
351 
353  std::vector<std::string> parts = {PercentEncode(scheme_, IsSchemeChar), ":"};
354  if (!authority_.empty()) {
355  parts.emplace_back("//");
356  parts.emplace_back(PercentEncode(authority_, IsAuthorityChar));
357  }
358  if (!path_.empty()) {
359  parts.emplace_back(PercentEncode(path_, IsPathChar));
360  }
361  if (!query_parameter_pairs_.empty()) {
362  parts.push_back("?");
363  parts.push_back(
364  absl::StrJoin(query_parameter_pairs_, "&", QueryParameterFormatter()));
365  }
366  if (!fragment_.empty()) {
367  parts.push_back("#");
368  parts.push_back(PercentEncode(fragment_, IsQueryOrFragmentChar));
369  }
370  return absl::StrJoin(parts, "");
371 }
372 
373 } // namespace grpc_core
absl::StrSplit
strings_internal::Splitter< typename strings_internal::SelectDelimiter< Delimiter >::type, AllowEmpty, absl::string_view > StrSplit(strings_internal::ConvertibleToStringView text, Delimiter d)
Definition: abseil-cpp/absl/strings/str_split.h:499
xds_interop_client.str
str
Definition: xds_interop_client.py:487
absl::InvalidArgumentError
Status InvalidArgumentError(absl::string_view message)
Definition: third_party/abseil-cpp/absl/status/status.cc:351
grpc_core::URI::PercentDecode
static std::string PercentDecode(absl::string_view str)
Definition: uri_parser.cc:187
grpc_core::URI::URI
URI()=default
absl::MaxSplits
strings_internal::MaxSplitsImpl< typename strings_internal::SelectDelimiter< Delimiter >::type > MaxSplits(Delimiter delimiter, int limit)
Definition: abseil-cpp/absl/strings/str_split.h:294
gen_build_yaml.out
dictionary out
Definition: src/benchmark/gen_build_yaml.py:24
log.h
grpc_core::URI::query_parameter_pairs_
std::vector< QueryParam > query_parameter_pairs_
Definition: uri_parser.h:96
absl::StrCat
std::string StrCat(const AlphaNum &a, const AlphaNum &b)
Definition: abseil-cpp/absl/strings/str_cat.cc:98
absl::StrFormat
ABSL_MUST_USE_RESULT std::string StrFormat(const FormatSpec< Args... > &format, const Args &... args)
Definition: abseil-cpp/absl/strings/str_format.h:338
fix_build_deps.c
list c
Definition: fix_build_deps.py:490
grpc_core::URI::QueryParam::key
std::string key
Definition: uri_parser.h:34
absl::string_view::find
size_type find(string_view s, size_type pos=0) const noexcept
Definition: abseil-cpp/absl/strings/string_view.cc:81
grpc_core
Definition: call_metric_recorder.h:31
grpc_core::URI::authority_
std::string authority_
Definition: uri_parser.h:93
grpc_core::URI::fragment
const std::string & fragment() const
Definition: uri_parser.h:84
absl::string_view
Definition: abseil-cpp/absl/strings/string_view.h:167
grpc_core::URI::scheme_
std::string scheme_
Definition: uri_parser.h:92
testing::internal::string
::std::string string
Definition: bloaty/third_party/protobuf/third_party/googletest/googletest/include/gtest/internal/gtest-port.h:881
grpc_core::URI
Definition: uri_parser.h:31
grpc_core::URI::operator=
URI & operator=(const URI &other)
Definition: uri_parser.cc:324
check_documentation.path
path
Definition: check_documentation.py:57
grpc_core::URI::path_
std::string path_
Definition: uri_parser.h:94
grpc_core::URI::Parse
static absl::StatusOr< URI > Parse(absl::string_view uri_text)
Definition: uri_parser.cc:209
grpc_core::URI::QueryParam::value
std::string value
Definition: uri_parser.h:35
absl::string_view::find_first_of
size_type find_first_of(string_view s, size_type pos=0) const noexcept
Definition: abseil-cpp/absl/strings/string_view.cc:124
grpc_core::URI::path
const std::string & path() const
Definition: uri_parser.h:70
grpc_core::URI::QueryParam
Definition: uri_parser.h:33
absl::CUnescape
bool CUnescape(absl::string_view source, std::string *dest, std::string *error)
Definition: abseil-cpp/absl/strings/escaping.cc:849
absl::ascii_isalnum
bool ascii_isalnum(unsigned char c)
Definition: abseil-cpp/absl/strings/ascii.h:87
absl::BytesToHexString
std::string BytesToHexString(absl::string_view from)
Definition: abseil-cpp/absl/strings/escaping.cc:940
absl::move
constexpr absl::remove_reference_t< T > && move(T &&t) noexcept
Definition: abseil-cpp/absl/utility/utility.h:221
GPR_ASSERT
#define GPR_ASSERT(x)
Definition: include/grpc/impl/codegen/log.h:94
absl::StrJoin
std::string StrJoin(Iterator start, Iterator end, absl::string_view sep, Formatter &&fmt)
Definition: abseil-cpp/absl/strings/str_join.h:239
grpc_core::URI::authority
const std::string & authority() const
Definition: uri_parser.h:69
absl::AsciiStrToUpper
void AsciiStrToUpper(std::string *s)
Definition: abseil-cpp/absl/strings/ascii.cc:164
grpc_core::URI::query_parameter_map_
std::map< absl::string_view, absl::string_view > query_parameter_map_
Definition: uri_parser.h:95
absl::StrContains
ABSL_NAMESPACE_BEGIN bool StrContains(absl::string_view haystack, absl::string_view needle) noexcept
Definition: third_party/abseil-cpp/absl/strings/match.h:46
grpc_core::URI::fragment_
std::string fragment_
Definition: uri_parser.h:97
grpc_core::URI::query_parameter_pairs
const std::vector< QueryParam > & query_parameter_pairs() const
Definition: uri_parser.h:81
absl::chars_format::hex
@ hex
grpc_core::URI::scheme
const std::string & scheme() const
Definition: uri_parser.h:68
grpc_core::URI::PercentEncodePath
static std::string PercentEncodePath(absl::string_view str)
Definition: uri_parser.cc:181
path_
grpc_slice path_
Definition: client_channel.cc:389
absl::string_view::remove_prefix
ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void remove_prefix(size_type n)
Definition: abseil-cpp/absl/strings/string_view.h:344
absl::Status
Definition: third_party/abseil-cpp/absl/status/status.h:424
grpc_core::URI::Create
static absl::StatusOr< URI > Create(std::string scheme, std::string authority, std::string path, std::vector< QueryParam > query_parameter_pairs, std::string fragment)
Definition: uri_parser.cc:289
std
Definition: grpcpp/impl/codegen/async_unary_call.h:407
absl::string_view::empty
constexpr bool empty() const noexcept
Definition: abseil-cpp/absl/strings/string_view.h:292
grpc_core::URI::PercentEncodeAuthority
static std::string PercentEncodeAuthority(absl::string_view str)
Definition: uri_parser.cc:177
absl::StatusOr
Definition: abseil-cpp/absl/status/statusor.h:187
uri_parser.h
absl::string_view::npos
static constexpr size_type npos
Definition: abseil-cpp/absl/strings/string_view.h:182
function
std::function< bool(GrpcTool *, int, const char **, const CliCredentials &, GrpcToolOutputCallback)> function
Definition: grpc_tool.cc:250
absl::string_view::substr
constexpr string_view substr(size_type pos=0, size_type n=npos) const
Definition: abseil-cpp/absl/strings/string_view.h:399
grpc_core::URI::ToString
std::string ToString() const
Definition: uri_parser.cc:352
i
uint64_t i
Definition: abseil-cpp/absl/container/btree_benchmark.cc:230
offset
voidpf uLong offset
Definition: bloaty/third_party/zlib/contrib/minizip/ioapi.h:142
port_platform.h
absl::ConsumePrefix
ABSL_NAMESPACE_BEGIN bool ConsumePrefix(absl::string_view *str, absl::string_view expected)
Definition: abseil-cpp/absl/strings/strip.h:46


grpc
Author(s):
autogenerated on Thu Mar 13 2025 03:01:47