header_utils
Loading...
Searching...
No Matches
uri_impl.h
1#include "uri.h"
5
6namespace ghassanpl
7{
8 namespace detail
9 {
10 using namespace string_ops;
11 using namespace parsing;
12
13 template <typename T>
14 void eat(uri_view& uri, T&& c, uri_error_code err)
15 {
16 if (!consume(uri, std::forward<T>(c)))
17 throw err;
18 }
19
20 static constexpr auto isscheme(char c) { return ascii::isalnum(c) || c == '+' || c == '-' || c == '.'; };
21 static constexpr auto isunreserved(char c) { return ascii::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~'; };
22 static constexpr auto isgendelims(char c) { return isany(c, ":/?#[]@"); };
23 static constexpr auto issubdelims(char c) { return isany(c, "!$&'()*+,;="); };
24 static constexpr auto isreserved(char c) { return isany(c, ":/?#[]@!$&'()*+,;="); };
25
26 template <typename T>
27 std::string condlower(T&& s, enum_flags<uri_decompose_flags> const flags)
28 {
29 return flags.contain(uri_decompose_flags::lowercase_when_appropriate) ? ascii::tolower(std::move(s)) : std::move(s);
30 }
31
32 static std::string parse_scheme(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
33 {
34 const auto start = uri;
35 eat(uri, ascii::isalpha, uri_error_code::scheme_malformed);
36 trim_while(uri, isscheme);
37
38 return condlower(make_string(start.begin(), uri.begin()), flags);
39 }
40
41 static std::string parse_authority(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
42 {
43 return std::string(consume_until_any(uri, '/', '?', '#'));
44 }
45
46 static char parse_pct(std::string_view& str)
47 {
48 uint8_t value = 0;
49 if (auto h1 = consume(str, ascii::isxdigit))
50 value += ascii::xdigit_to_number(h1) * 16;
51 else
52 throw uri_error_code::invalid_percent_encoding;
53 if (auto h2 = consume(str, ascii::isxdigit))
54 value += ascii::xdigit_to_number(h2);
55 else
56 throw uri_error_code::invalid_percent_encoding;
57 return (char)value;
58 }
59
60 template <typename PRED>
61 static std::string consume_with_pct(std::string_view& str, PRED&& pred, std::string_view prefix = {})
62 {
63 std::string result{ prefix };
64 do
65 {
66 result += consume_while(str, pred);
67 if (consume(str, '%'))
68 result += parse_pct(str);
69 else
70 break;
71 } while (true);
72 return result;
73 }
74
75 static std::string try_parse_ipv4(std::string_view& str, enum_flags<uri_decompose_flags> const flags)
76 {
77 auto start = str;
78 auto [_, n1] = consume_c_unsigned(str);
79 if (n1 < 0 || n1 > 255) return {};
80 if (!consume(str, '.')) return {};
81 auto [__, n2] = consume_c_unsigned(str);
82 if (n2 < 0 || n2 > 255) return {};
83 if (!consume(str, '.')) return {};
84 auto [___, n3] = consume_c_unsigned(str);
85 if (n3 < 0 || n3 > 255) return {};
86 if (!consume(str, '.')) return {};
87 auto [____, n4] = consume_c_unsigned(str);
88 if (n4 < 0 || n4 > 255) return {};
89
90 return make_string(start.begin(), str.begin());
91 }
92
93 static std::string parse_host(std::string_view& authority, enum_flags<uri_decompose_flags> const flags)
94 {
95 if (consume(authority, '['))
96 {
101
102 auto result = consume_until(authority, ']');
103 if (result.empty() || !consume(authority, ']'))
104 throw uri_error_code::host_malformed;
105 return std::string{ result };
106 }
107 else
108 {
109 auto start = authority;
110 auto ipv4 = try_parse_ipv4(authority, flags);
111 if (!ipv4.empty())
112 return ipv4;
113
115 authority = start;
116
118 return condlower(consume_with_pct(authority, [](char c) { return isunreserved(c) || c == '-' || c == '.'; }), flags);
119 }
120 }
121
122 static std::tuple<std::string, std::string, std::string> parse_authority_elements(std::string_view authority, enum_flags<uri_decompose_flags> const flags)
123 {
124 std::tuple<std::string, std::string, std::string> result;
125
126 if (authority.contains('@'))
127 {
128 std::get<0>(result) = consume_with_pct(authority, [](char c) { return isunreserved(c) || issubdelims(c) || c == ':'; });
129 eat(authority, '@', uri_error_code::authority_malformed);
130 std::get<1>(result) = parse_host(authority, flags);
131 }
132 else
133 {
134 std::get<1>(result) = parse_host(authority, flags);
135 }
136
137 if (consume(authority, ':'))
138 {
139 std::get<2>(result) = std::string(consume_while(authority, ascii::isdigit));
140 }
141
142 return result;
143 }
144
145 static constexpr auto ispchar(char c) { return isunreserved(c) || issubdelims(c) || c == ':' || c == '@'; }
146 static constexpr auto ispcharnc(char c) { return isunreserved(c) || issubdelims(c) || c == '@'; }
147
148 static std::string parse_segment(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
149 {
150 return consume_with_pct(uri, ispchar);
151 }
152
153 static std::string parse_segment_nonzero(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
154 {
155 auto result = consume_with_pct(uri, ispchar);
156 if (result.empty())
157 throw uri_error_code::path_element_malformed;
158 return result;
159 }
160
161 static std::string parse_segment_nonzero_noncolon(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
162 {
163 auto result = consume_with_pct(uri, ispcharnc);
164 if (result.empty())
165 throw uri_error_code::path_element_malformed;
166 return result;
167 }
168
169 static std::tuple<std::string, std::vector<std::string>> parse_path(bool with_authority, uri_view& uri, enum_flags<uri_decompose_flags> const flags)
170 {
172 auto path = consume_until_any(uri, '?', '#');
173
174 if (with_authority && !path.empty() && path[0] != '/')
175 throw uri_error_code::path_malformed;
176
177 std::vector<std::string> elements;
178
179 if (!path.empty())
180 {
181 uri_view pv = path;
182
183 trim(pv, '/');
184
185 if (!pv.empty())
186 {
187 do
188 {
189 if (flags.contain(uri_decompose_flags::split_path_elements))
190 elements.push_back(parse_segment(pv, flags));
191 else
192 std::ignore = parse_segment(pv, flags);
193 } while (consume(pv, '/'));
194
195 if (!pv.empty())
196 throw uri_error_code::path_malformed;
197 }
198 }
199
200 return { std::string{ path }, std::move(elements) };
201 }
202
203 static constexpr auto isqorf(char c) { return ispchar(c) || c == '/' || c == '?'; }
204
205 template <char QUERY_DELIMITER = '&', char KEY_DELIMITER = '='>
206 void split_query_elements(std::string_view path, std::vector<std::pair<std::string, std::string>>& elements)
207 {
208 split(path, QUERY_DELIMITER, [&elements](std::string_view el, bool last) {
209 auto k = consume_until(el, KEY_DELIMITER); std::ignore = consume(el);
210 elements.push_back({ std::string{k}, std::string{el} });
211 });
212 }
213
214 static std::tuple<std::string, std::vector<std::pair<std::string, std::string>>> parse_query(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
215 {
216 std::tuple<std::string, std::vector<std::pair<std::string, std::string>>> result;
217
218 auto path = consume_with_pct(uri, isqorf, "?");
219 if (!uri.empty() && uri[0] != '#')
220 throw uri_error_code::query_malformed;
221
222 std::get<0>(result) = path;
223 if (flags.contain(uri_decompose_flags::split_query_elements))
224 split_query_elements(std::string_view{ path }.substr(1), std::get<1>(result));
225
226 return result;
227 }
228
229 static std::string parse_fragment(uri_view& uri, enum_flags<uri_decompose_flags> const flags)
230 {
231 auto fragment = consume_with_pct(uri, isqorf, "#");
232 if (!uri.empty())
233 throw uri_error_code::query_malformed;
234 return fragment;
235 }
236
237 static std::string deduce_port_from_scheme(std::string_view scheme)
238 {
239 auto known = query_uri_scheme(scheme);
240 if (known)
241 return std::string{ known->default_port() };
242 return {};
243 }
244 }
245
246 uri_expected<std::string_view> extract_scheme(uri_view uri) noexcept
247 {
248 using namespace detail;
249
250 const auto start = uri;
251 if (!consume(uri, ascii::isalpha))
252 return unexpected(uri_error_code::scheme_malformed);
253 trim_while(uri, isscheme);
254 return make_sv(start.begin(), uri.begin());
255 }
256
257 uri_expected<decomposed_uri> decompose_uri(uri_view uri, enum_flags<uri_decompose_flags> const flags)
258 {
259 try
260 {
261 decomposed_uri result{};
262 result.decompose_flags = flags;
263
264 result.scheme = detail::parse_scheme(uri, flags);
265
266 if (!string_ops::consume(uri, ':'))
267 return unexpected(uri_error_code::scheme_malformed);
268
269 if (string_ops::consume(uri, "//"))
270 {
271 result.authority = detail::parse_authority(uri, flags);
272
273 auto [user_info, host, port] = detail::parse_authority_elements(result.authority, flags);
275 port = detail::deduce_port_from_scheme(result.scheme);
276 result.user_info = std::move(user_info);
277 result.host = std::move(host);
278 result.port = std::move(port);
279 }
280
281 auto [path, elements] = detail::parse_path(!result.authority.empty(), uri, flags);
282 result.path = std::move(path);
283 result.path_elements = std::move(elements);
284 if (flags.contain(uri_decompose_flags::normalize_path))
285 result.path_elements = result.normalized_path();
286
287 if (string_ops::consume(uri, '?'))
288 {
289 auto [query, elements] = detail::parse_query(uri, flags);
290 result.query = std::move(query);
291 result.query_elements = std::move(elements);
292 }
293
294 if (string_ops::consume(uri, '#'))
295 result.fragment = detail::parse_fragment(uri, flags);
296
297 if (flags.contains_all_of(uri_decompose_flags::lowercase_when_appropriate, uri_decompose_flags::normalize_path))
298 result.canonical_form = true;
299
300 if (flags.contain(uri_decompose_flags::query_known_scheme))
301 {
302 result.known_scheme = query_uri_scheme(result.scheme);
303 if (result.known_scheme)
304 {
305 return result.known_scheme->validate(result)
306 .transform([&]{ return std::move(result); });
307 }
308 }
309
310 return result;
311 }
312 catch (uri_error_code code)
313 {
314 return unexpected(code);
315 }
316 }
317
318 uri_expected<uri> compose_uri(decomposed_uri const& decomposed, enum_flags<uri_decompose_flags> const flags)
319 {
320 throw "unimplemented";
321 }
322
323 bool decomposed_uri::operator==(decomposed_uri const& other) const noexcept
324 {
325 return
326 string_ops::ascii::strings_equal_ignore_case(scheme, other.scheme) &&
327 user_info == other.user_info &&
328 string_ops::ascii::strings_equal_ignore_case(host, other.host) &&
329 string_ops::trimmed(port, '0') == string_ops::trimmed(other.port, '0') &&
330 normalized_path() == other.normalized_path() &&
331 query == other.query &&
332 fragment == other.fragment;
333 }
334
335 std::vector<std::string> decomposed_uri::normalized_path() const noexcept
336 {
337 std::vector<std::string> result;
338 for (auto& el : path_elements)
339 {
340 if (el == ".")
341 continue;
342 else if (el == ".." && !result.empty())
343 result.pop_back();
344 else
345 result.push_back(el);
346 }
347 return result;
348 }
349
350 namespace known_schemes
351 {
352
353 struct url_schemes : known_uri_scheme
354 {
356 virtual uri_error validate_host(std::string_view element) const noexcept override {
357 if (element.empty())
358 return unexpected(uri_error_code::host_required_in_scheme);
359 return {};
360 }
361 };
362
363 struct http_schemes : url_schemes
364 {
365 virtual uri_error validate_user_info(std::string_view element) const noexcept override { return {}; }
366 virtual uri_error validate_path(std::string_view element) const noexcept override {
367 if (!element.empty() && !element.starts_with("/"))
368 return unexpected(uri_error_code::path_malformed);
369 return {};
370 }
371
372 virtual std::string normalize_port(std::string_view element) const noexcept override { if (string_ops::trimmed(element, '0') == default_port()) return {}; return std::string{ element }; }
373 virtual std::string normalize_path(std::string_view element) const noexcept override { if (element.empty()) return "/"; return std::string{ element }; }
374 virtual std::string normalize_host(std::string_view element) const noexcept override { return string_ops::ascii::tolower(element); }
375 };
376
377 struct http_scheme : http_schemes
378 {
379 virtual std::string_view scheme() const noexcept override { return "http"; }
380 virtual std::string_view default_port() const noexcept override { return "80"; }
381 };
382
383 struct https_scheme : http_schemes
384 {
385 virtual std::string_view scheme() const noexcept override { return "https"; }
386 virtual std::string_view default_port() const noexcept override { return "443"; }
387 };
388
389 inline const http_scheme http;
390 inline const https_scheme https;
391
393 }
394
395 known_uri_scheme const* query_uri_scheme(std::string_view scheme)
396 {
398 static std::map<std::string, known_uri_scheme const*, std::less<>> const schemes = {
399 {"file", &known_schemes::file},
400 {"http", &known_schemes::http},
401 {"https", &known_schemes::https}
402 };
403 if (auto s = schemes.find(scheme); s != schemes.end())
404 return s->second;
405 return nullptr;
406 }
407
408 std::vector<std::pair<std::string, std::string>> known_uri_scheme::split_query_elements(std::string_view query) const noexcept
409 {
410 std::vector<std::pair<std::string, std::string>> result;
411 detail::split_query_elements(query, result);
412 return result;
413 }
414}
constexpr int xdigit_to_number(char32_t cp) noexcept
Convert an ASCII xdigit to its numerical value (only gives meaningful results with valid xdigit argum...
Definition string_ops.h:482
auto * element(T &cont, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an pointer to a random element in cont.
Definition random.h:345
std::string_view consume_until(std::string_view &str, FUNC &&pred)
Consumes characters from the beginning of str until one matches pred(str[0]), exclusive.
Definition string_ops.h:838
constexpr bool isany(char32_t cp, T &&chars) noexcept
Checks if cp is any of the characters in chars.
Definition string_ops.h:688
char consume(std::string_view &str)
Consumes and returns the first character in the str, or \0 if no more characters.
Definition string_ops.h:702
std::string_view consume_until_any(std::string_view &str, ARGS &&... args)
Consumes characters from the beginning of str until one is equal to any in the parameter pack,...
Definition string_ops.h:884
constexpr auto split(std::string_view source, char delim, FUNC &&func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)))
Performs a basic "split" operation, calling func for each part of source delimited by delim.
std::string_view consume_while(std::string_view &str, FUNC &&pred)
Consumes characters from the beginning of str while they match pred(str[0]).
Definition string_ops.h:805
std::string_view prefix(std::string_view str, size_t count) noexcept
Returns a substring containing the count leftmost characters of str. Always valid,...
Definition string_ops.h:605
uri_expected< uri > compose_uri(decomposed_uri const &decomposed, enum_flags< uri_compose_flags > flags=enum_flags< uri_compose_flags >::all())
[Unimplemented]
uri_error_code
Error codes relating to URI decomposing, composing, and other operations.
Definition uri.h:36
std::string uri
URIs are stored in a UTF-8 encoding where both non-ASCII code unit bytes as well as URI-reserved char...
Definition uri.h:28
known_uri_scheme const * query_uri_scheme(std::string_view scheme)
Looks up the scheme in the internal URI scheme database.
Definition uri_impl.h:395
uri_expected< decomposed_uri > decompose_uri(uri_view uri, enum_flags< uri_decompose_flags > flags=enum_flags< uri_decompose_flags >::all())
This function will decompose URI into its composite elements, which includes percent-decoding all the...
Definition uri_impl.h:257
@ use_well_known_port_numbers
if a port is not specified in the uri, the result will guess the port based on the scheme
Definition uri.h:94
Primary namespace for everything in this library.
Definition align+rec2.h:10
Contains known URI scheme types and objects.
Holds the constituents of a URI.
Definition uri.h:104
std::vector< std::string > normalized_path() const noexcept
Returns the path normalized by applying any "." or ".." elements.
Definition uri_impl.h:335
A (constexpr) value struct that represents a set of bits mapped to an enum (implemented as a bitset).
Definition enum_flags.h:60
constexpr bool contains_all_of(ARGS... args) const noexcept
Returns whether or not all of the given flags are set.
Definition enum_flags.h:193
constexpr bool contain(enum_type flag) const noexcept
Same as is_set.
Definition enum_flags.h:127
An base class for classes that can validate (and potentially parse more info from) a known URI scheme...
Definition uri.h:190