Line data Source code
1 : #include "url.hpp"
2 : #include "../../util/ascii.hpp"
3 :
4 : namespace thinger::http::util::url{
5 :
6 : namespace {
7 : constexpr char hex_chars[] = "0123456789ABCDEF";
8 :
9 132780 : inline int hex_digit(char c) {
10 132780 : if (c >= '0' && c <= '9') return c - '0';
11 85402 : if (c >= 'a' && c <= 'f') return c - 'a' + 10;
12 73259 : if (c >= 'A' && c <= 'F') return c - 'A' + 10;
13 34801 : return -1;
14 : }
15 :
16 1567158 : inline bool is_unreserved(char c) {
17 1567158 : return std::isalnum(static_cast<unsigned char>(c))
18 1567158 : || c == '-' || c == '_' || c == '.' || c == '~';
19 : }
20 : }
21 :
22 : // RFC 3986 Section 2.3: unreserved characters are not percent-encoded
23 120579 : std::string url_encode(const std::string &value) {
24 120579 : std::string result;
25 120579 : result.reserve(value.size() * 1.2);
26 :
27 604638 : for (unsigned char c : value) {
28 484059 : if (is_unreserved(c)) {
29 483585 : result += static_cast<char>(c);
30 : } else {
31 474 : result += '%';
32 474 : result += hex_chars[c >> 4];
33 474 : result += hex_chars[c & 0x0F];
34 : }
35 : }
36 :
37 120579 : return result;
38 0 : }
39 :
40 : // RFC 3986 path encoding: also preserves '/' and '~'
41 120348 : std::string uri_path_encode(const std::string &value) {
42 120348 : std::string result;
43 120348 : result.reserve(value.size() * 1.2);
44 :
45 1203447 : for (unsigned char c : value) {
46 1083099 : if (is_unreserved(c) || c == '/' ) {
47 1083084 : result += static_cast<char>(c);
48 : } else {
49 15 : result += '%';
50 15 : result += hex_chars[c >> 4];
51 15 : result += hex_chars[c & 0x0F];
52 : }
53 : }
54 :
55 120348 : return result;
56 0 : }
57 :
58 : namespace {
59 365372 : bool percent_decode(std::string_view in, std::string &out, bool plus_is_space) {
60 365372 : out.clear();
61 365372 : out.reserve(in.size());
62 :
63 2821092 : for (size_t i = 0; i < in.size(); ++i) {
64 2504119 : if (in[i] == '%') {
65 88975 : if (i + 2 >= in.size()) return false;
66 66390 : int hi = hex_digit(in[i + 1]);
67 66390 : int lo = hex_digit(in[i + 2]);
68 66390 : if (hi < 0 || lo < 0) return false;
69 40576 : out += static_cast<char>((hi << 4) | lo);
70 40576 : i += 2;
71 2415144 : } else if (in[i] == '+' && plus_is_space) {
72 20797 : out += ' ';
73 : } else {
74 2394347 : out += in[i];
75 : }
76 : }
77 316973 : return true;
78 : }
79 : }
80 :
81 187435 : bool url_decode(const std::string &in, std::string &out) {
82 187435 : return percent_decode(in, out, true);
83 : }
84 :
85 177937 : bool detail::path_decode(std::string_view in, std::string &out) {
86 177937 : return percent_decode(in, out, false);
87 : }
88 :
89 187015 : std::string url_decode(const std::string &in) {
90 187015 : std::string out;
91 187015 : if (url_decode(in, out)) {
92 153634 : return out;
93 : }
94 33381 : return {};
95 187015 : }
96 :
97 30 : void parse_url_encoded_data(const std::string &data, std::multimap<std::string, std::string>& store) {
98 30 : auto start = data.cbegin();
99 30 : auto end = data.cend();
100 30 : parse_url_encoded_data(start, end, store);
101 30 : }
102 :
103 50652 : void parse_url_encoded_data(std::string::const_iterator& start, std::string::const_iterator& end, std::multimap<std::string, std::string>& store) {
104 50652 : if (start == end) return;
105 :
106 45966 : auto it = start;
107 115943 : while (it != end) {
108 : // find the end of the current key=value pair
109 69977 : auto pair_end = std::find(it, end, '&');
110 :
111 : // find the '=' separator within the pair
112 69977 : auto eq_pos = std::find(it, pair_end, '=');
113 :
114 69977 : std::string key(it, eq_pos);
115 69977 : std::string value;
116 69977 : if (eq_pos != pair_end) {
117 23067 : value.assign(eq_pos + 1, pair_end);
118 : }
119 :
120 69977 : if (!key.empty()) {
121 60077 : store.emplace(url_decode(key), url_decode(value));
122 : }
123 :
124 69977 : it = (pair_end != end) ? pair_end + 1 : end;
125 69977 : }
126 :
127 45966 : start = it;
128 : }
129 :
130 60195 : std::string get_url_encoded_data(const std::multimap<std::string, std::string>& store) {
131 60195 : std::string result;
132 60195 : bool first = true;
133 120417 : for (const auto& [key, value] : store) {
134 60222 : if (!first) result += '&';
135 60222 : result += url_encode(key);
136 60222 : result += '=';
137 60222 : result += url_encode(value);
138 60222 : first = false;
139 : }
140 60195 : return result;
141 0 : }
142 :
143 : namespace {
144 :
145 : constexpr std::string_view redacted = "[redacted]";
146 :
147 : // "scheme://user:password@host...": the password redacted
148 886 : std::string redact_user_info(std::string_view uri) {
149 886 : auto scheme_end = uri.find("://");
150 2026 : if (scheme_end == std::string_view::npos) return std::string(uri);
151 316 : auto authority_start = scheme_end + 3;
152 316 : auto authority_end = uri.find_first_of("/?#", authority_start);
153 316 : auto authority = uri.substr(authority_start, authority_end == std::string_view::npos ? std::string_view::npos
154 : : authority_end - authority_start);
155 316 : auto at = authority.rfind('@');
156 918 : if (at == std::string_view::npos) return std::string(uri);
157 15 : auto colon = authority.substr(0, at).find(':');
158 21 : if (colon == std::string_view::npos) return std::string(uri);
159 12 : std::string out(uri.substr(0, authority_start + colon + 1));
160 12 : out += redacted;
161 12 : out += uri.substr(authority_start + at);
162 12 : return out;
163 12 : }
164 :
165 : }
166 :
167 2401 : redacted_parameters::redacted_parameters(std::initializer_list<std::string_view> names) {
168 42216 : for (auto name : names) add(name);
169 2401 : }
170 :
171 46722 : const redacted_parameters& redacted_parameters::defaults() {
172 : static const redacted_parameters names{
173 : "access_token", "api_key", "apikey", "authorization", "client_secret", "code",
174 : "id_token", "key", "password", "refresh_token", "secret", "sig", "signature", "token",
175 46722 : "x-amz-security-token", "x-amz-signature", "x-goog-signature"};
176 46722 : return names;
177 : }
178 :
179 : // The names are shared with the copies of this set: both change a copy of them
180 39842 : redacted_parameters& redacted_parameters::add(std::string_view name) {
181 39842 : if (name.empty() || contains(name)) return *this;
182 39830 : auto names = names_ ? std::make_shared<std::vector<std::string>>(*names_)
183 39830 : : std::make_shared<std::vector<std::string>>();
184 39830 : names->emplace_back(name);
185 39830 : names_ = std::move(names);
186 39830 : return *this;
187 39830 : }
188 :
189 15 : redacted_parameters& redacted_parameters::remove(std::string_view name) {
190 15 : if (!contains(name)) return *this;
191 12 : auto names = std::make_shared<std::vector<std::string>>(*names_);
192 171 : std::erase_if(*names, [&](const std::string& candidate) { return thinger::util::ascii::iequals(candidate, name); });
193 12 : names_ = std::move(names);
194 12 : return *this;
195 12 : }
196 :
197 40859 : bool redacted_parameters::contains(std::string_view name) const {
198 40859 : return names_ && std::any_of(names_->begin(), names_->end(), [&](const std::string& candidate) {
199 329461 : return thinger::util::ascii::iequals(candidate, name);
200 81718 : });
201 : }
202 :
203 174 : std::string loggable_uri(std::string_view uri) {
204 174 : return loggable_uri(uri, redacted_parameters::defaults());
205 : }
206 :
207 886 : std::string loggable_uri(std::string_view uri, const redacted_parameters& names) {
208 886 : auto query = uri.find('?');
209 886 : if (query == std::string_view::npos) return redact_user_info(uri);
210 435 : auto fragment = uri.find('#', query);
211 435 : auto query_end = fragment == std::string_view::npos ? uri.size() : fragment;
212 :
213 435 : std::string out = redact_user_info(uri.substr(0, query + 1));
214 435 : auto pos = query + 1;
215 1041 : while (pos <= query_end) {
216 1041 : auto amp = uri.find('&', pos);
217 1041 : auto end = (amp == std::string_view::npos || amp > query_end) ? query_end : amp;
218 1041 : auto pair = uri.substr(pos, end - pos);
219 1041 : auto eq = pair.find('=');
220 2889 : if (eq != std::string_view::npos && names.contains(url_decode(std::string(pair.substr(0, eq))))) {
221 402 : out += pair.substr(0, eq + 1);
222 402 : out += redacted;
223 : } else {
224 639 : out += pair;
225 : }
226 1041 : if (end == query_end) break;
227 606 : out += '&';
228 606 : pos = end + 1;
229 : }
230 435 : out += uri.substr(query_end);
231 435 : return out;
232 435 : }
233 :
234 : }
|