1 /*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include <grpc/support/port_platform.h>
20
21 #include "src/core/lib/uri/uri_parser.h"
22
23 #include <string.h>
24
25 #include <map>
26 #include <string>
27
28 #include "absl/strings/escaping.h"
29 #include "absl/strings/str_format.h"
30 #include "absl/strings/str_split.h"
31
32 #include <grpc/support/log.h>
33
34 #include "src/core/lib/gpr/string.h"
35
36 namespace grpc_core {
37 namespace {
38
39 // Similar to `grpc_permissive_percent_decode_slice`, this %-decodes all valid
40 // triplets, and passes through the rest verbatim.
PercentDecode(absl::string_view str)41 std::string PercentDecode(absl::string_view str) {
42 if (str.empty() || !absl::StrContains(str, "%")) {
43 return std::string(str);
44 }
45 std::string out;
46 std::string unescaped;
47 out.reserve(str.size());
48 for (size_t i = 0; i < str.length(); i++) {
49 unescaped = "";
50 if (str[i] != '%') {
51 out += str[i];
52 continue;
53 }
54 if (i + 3 >= str.length() ||
55 !absl::CUnescape(absl::StrCat("\\x", str.substr(i + 1, 2)),
56 &unescaped) ||
57 unescaped.length() > 1) {
58 out += str[i];
59 } else {
60 out += unescaped[0];
61 i += 2;
62 }
63 }
64 return out;
65 }
66
67 // Checks if this string is made up of pchars, '/', '?', and '%' exclusively.
68 // See https://tools.ietf.org/html/rfc3986#section-3.4
IsPCharString(absl::string_view str)69 bool IsPCharString(absl::string_view str) {
70 return (str.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
71 "abcdefghijklmnopqrstuvwxyz"
72 "0123456789"
73 "?/:@\\-._~!$&'()*+,;=%") ==
74 absl::string_view::npos);
75 }
76
MakeInvalidURIStatus(absl::string_view part_name,absl::string_view uri,absl::string_view extra)77 absl::Status MakeInvalidURIStatus(absl::string_view part_name,
78 absl::string_view uri,
79 absl::string_view extra) {
80 return absl::InvalidArgumentError(absl::StrFormat(
81 "Could not parse '%s' from uri '%s'. %s", part_name, uri, extra));
82 }
83 } // namespace
84
Parse(absl::string_view uri_text)85 absl::StatusOr<URI> URI::Parse(absl::string_view uri_text) {
86 absl::StatusOr<std::string> decoded;
87 absl::string_view remaining = uri_text;
88 // parse scheme
89 size_t idx = remaining.find(':');
90 if (idx == remaining.npos || idx == 0) {
91 return MakeInvalidURIStatus("scheme", uri_text, "Scheme not found.");
92 }
93 std::string scheme(remaining.substr(0, idx));
94 if (scheme.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
95 "abcdefghijklmnopqrstuvwxyz"
96 "0123456789+-.") != std::string::npos) {
97 return MakeInvalidURIStatus("scheme", uri_text,
98 "Scheme contains invalid characters.");
99 }
100 if (!isalpha(scheme[0])) {
101 return MakeInvalidURIStatus(
102 "scheme", uri_text,
103 "Scheme must begin with an alpha character [A-Za-z].");
104 }
105 remaining.remove_prefix(scheme.length() + 1);
106 // parse authority
107 std::string authority;
108 if (absl::StartsWith(remaining, "//")) {
109 remaining.remove_prefix(2);
110 authority =
111 PercentDecode(remaining.substr(0, remaining.find_first_of("/?#")));
112 remaining.remove_prefix(authority.length());
113 }
114 // parse path
115 std::string path;
116 if (!remaining.empty()) {
117 path = PercentDecode(remaining.substr(0, remaining.find_first_of("?#")));
118 remaining.remove_prefix(path.length());
119 }
120 // parse query
121 std::vector<QueryParam> query_param_pairs;
122 if (!remaining.empty() && remaining[0] == '?') {
123 remaining.remove_prefix(1);
124 absl::string_view tmp_query = remaining.substr(0, remaining.find('#'));
125 if (tmp_query.empty()) {
126 return MakeInvalidURIStatus("query", uri_text, "Invalid query string.");
127 }
128 if (!IsPCharString(tmp_query)) {
129 return MakeInvalidURIStatus("query string", uri_text,
130 "Query string contains invalid characters.");
131 }
132 for (absl::string_view query_param : absl::StrSplit(tmp_query, '&')) {
133 const std::pair<absl::string_view, absl::string_view> possible_kv =
134 absl::StrSplit(query_param, absl::MaxSplits('=', 1));
135 if (possible_kv.first.empty()) continue;
136 query_param_pairs.push_back({PercentDecode(possible_kv.first),
137 PercentDecode(possible_kv.second)});
138 }
139 remaining.remove_prefix(tmp_query.length());
140 }
141 std::string fragment;
142 if (!remaining.empty() && remaining[0] == '#') {
143 remaining.remove_prefix(1);
144 if (!IsPCharString(remaining)) {
145 return MakeInvalidURIStatus("fragment", uri_text,
146 "Fragment contains invalid characters.");
147 }
148 fragment = PercentDecode(remaining);
149 }
150 return URI(std::move(scheme), std::move(authority), std::move(path),
151 std::move(query_param_pairs), std::move(fragment));
152 }
153
URI(std::string scheme,std::string authority,std::string path,std::vector<QueryParam> query_parameter_pairs,std::string fragment)154 URI::URI(std::string scheme, std::string authority, std::string path,
155 std::vector<QueryParam> query_parameter_pairs, std::string fragment)
156 : scheme_(std::move(scheme)),
157 authority_(std::move(authority)),
158 path_(std::move(path)),
159 query_parameter_pairs_(std::move(query_parameter_pairs)),
160 fragment_(std::move(fragment)) {
161 for (const auto& kv : query_parameter_pairs_) {
162 query_parameter_map_[kv.key] = kv.value;
163 }
164 }
165
URI(const URI & other)166 URI::URI(const URI& other)
167 : scheme_(other.scheme_),
168 authority_(other.authority_),
169 path_(other.path_),
170 query_parameter_pairs_(other.query_parameter_pairs_),
171 fragment_(other.fragment_) {
172 for (const auto& kv : query_parameter_pairs_) {
173 query_parameter_map_[kv.key] = kv.value;
174 }
175 }
176
operator =(const URI & other)177 URI& URI::operator=(const URI& other) {
178 if (this == &other) {
179 return *this;
180 }
181 scheme_ = other.scheme_;
182 authority_ = other.authority_;
183 path_ = other.path_;
184 query_parameter_pairs_ = other.query_parameter_pairs_;
185 fragment_ = other.fragment_;
186 for (const auto& kv : query_parameter_pairs_) {
187 query_parameter_map_[kv.key] = kv.value;
188 }
189 return *this;
190 }
191 } // namespace grpc_core
192