10 using namespace string_ops;
11 using namespace parsing;
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,
":/?#[]@!$&'()*+,;="); };
27 std::string condlower(T&& s, enum_flags<uri_decompose_flags>
const flags)
29 return flags.contain(uri_decompose_flags::lowercase_when_appropriate) ? ascii::tolower(std::move(s)) : std::move(s);
32 static std::string parse_scheme(uri_view&
uri, enum_flags<uri_decompose_flags>
const flags)
34 const auto start =
uri;
35 eat(
uri, ascii::isalpha, uri_error_code::scheme_malformed);
36 trim_while(
uri, isscheme);
38 return condlower(make_string(start.begin(),
uri.begin()), flags);
41 static std::string parse_authority(uri_view&
uri, enum_flags<uri_decompose_flags>
const flags)
46 static char parse_pct(std::string_view& str)
49 if (
auto h1 =
consume(str, ascii::isxdigit))
52 throw uri_error_code::invalid_percent_encoding;
53 if (
auto h2 =
consume(str, ascii::isxdigit))
56 throw uri_error_code::invalid_percent_encoding;
60 template <
typename PRED>
61 static std::string consume_with_pct(std::string_view& str, PRED&& pred, std::string_view
prefix = {})
63 std::string result{
prefix };
68 result += parse_pct(str);
75 static std::string try_parse_ipv4(std::string_view& str, enum_flags<uri_decompose_flags>
const flags)
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 {};
90 return make_string(start.begin(), str.begin());
93 static std::string parse_host(std::string_view& authority, enum_flags<uri_decompose_flags>
const flags)
103 if (result.empty() || !
consume(authority,
']'))
104 throw uri_error_code::host_malformed;
105 return std::string{ result };
109 auto start = authority;
110 auto ipv4 = try_parse_ipv4(authority, flags);
118 return condlower(consume_with_pct(authority, [](
char c) {
return isunreserved(c) || c ==
'-' || c ==
'.'; }), flags);
122 static std::tuple<std::string, std::string, std::string> parse_authority_elements(std::string_view authority, enum_flags<uri_decompose_flags>
const flags)
124 std::tuple<std::string, std::string, std::string> result;
126 if (authority.contains(
'@'))
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);
134 std::get<1>(result) = parse_host(authority, flags);
139 std::get<2>(result) = std::string(
consume_while(authority, ascii::isdigit));
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 ==
'@'; }
148 static std::string parse_segment(uri_view&
uri, enum_flags<uri_decompose_flags>
const flags)
150 return consume_with_pct(
uri, ispchar);
153 static std::string parse_segment_nonzero(uri_view&
uri, enum_flags<uri_decompose_flags>
const flags)
155 auto result = consume_with_pct(
uri, ispchar);
157 throw uri_error_code::path_element_malformed;
161 static std::string parse_segment_nonzero_noncolon(uri_view&
uri, enum_flags<uri_decompose_flags>
const flags)
163 auto result = consume_with_pct(
uri, ispcharnc);
165 throw uri_error_code::path_element_malformed;
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)
174 if (with_authority && !path.empty() && path[0] !=
'/')
175 throw uri_error_code::path_malformed;
177 std::vector<std::string> elements;
189 if (flags.contain(uri_decompose_flags::split_path_elements))
190 elements.push_back(parse_segment(pv, flags));
192 std::ignore = parse_segment(pv, flags);
196 throw uri_error_code::path_malformed;
200 return { std::string{ path }, std::move(elements) };
203 static constexpr auto isqorf(
char c) {
return ispchar(c) || c ==
'/' || c ==
'?'; }
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)
208 split(path, QUERY_DELIMITER, [&elements](std::string_view el,
bool last) {
210 elements.push_back({ std::string{k}, std::string{el} });
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)
216 std::tuple<std::string, std::vector<std::pair<std::string, std::string>>> result;
218 auto path = consume_with_pct(
uri, isqorf,
"?");
219 if (!
uri.empty() &&
uri[0] !=
'#')
220 throw uri_error_code::query_malformed;
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));
229 static std::string parse_fragment(uri_view&
uri, enum_flags<uri_decompose_flags>
const flags)
231 auto fragment = consume_with_pct(
uri, isqorf,
"#");
233 throw uri_error_code::query_malformed;
237 static std::string deduce_port_from_scheme(std::string_view scheme)
241 return std::string{ known->default_port() };
246 uri_expected<std::string_view> extract_scheme(uri_view
uri)
noexcept
248 using namespace detail;
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());
262 result.decompose_flags = flags;
264 result.scheme = detail::parse_scheme(
uri, flags);
267 return unexpected(uri_error_code::scheme_malformed);
271 result.authority = detail::parse_authority(
uri, flags);
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);
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))
289 auto [query, elements] = detail::parse_query(
uri, flags);
290 result.query = std::move(query);
291 result.query_elements = std::move(elements);
295 result.fragment = detail::parse_fragment(
uri, flags);
297 if (flags.
contains_all_of(uri_decompose_flags::lowercase_when_appropriate, uri_decompose_flags::normalize_path))
298 result.canonical_form =
true;
300 if (flags.
contain(uri_decompose_flags::query_known_scheme))
303 if (result.known_scheme)
305 return result.known_scheme->validate(result)
306 .transform([&]{
return std::move(result); });
314 return unexpected(code);
318 uri_expected<uri>
compose_uri(decomposed_uri
const& decomposed, enum_flags<uri_decompose_flags>
const flags)
320 throw "unimplemented";
323 bool decomposed_uri::operator==(
decomposed_uri const& other)
const noexcept
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;
337 std::vector<std::string> result;
338 for (
auto& el : path_elements)
342 else if (el ==
".." && !result.empty())
345 result.push_back(el);
356 virtual uri_error validate_host(std::string_view element)
const noexcept override {
358 return unexpected(uri_error_code::host_required_in_scheme);
363 struct http_schemes : url_schemes
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 {
368 return unexpected(uri_error_code::path_malformed);
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); }
377 struct http_scheme : http_schemes
379 virtual std::string_view scheme() const noexcept
override {
return "http"; }
380 virtual std::string_view default_port() const noexcept
override {
return "80"; }
383 struct https_scheme : http_schemes
385 virtual std::string_view scheme() const noexcept
override {
return "https"; }
386 virtual std::string_view default_port() const noexcept
override {
return "443"; }
389 inline const http_scheme http;
390 inline const https_scheme https;
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}
403 if (
auto s = schemes.find(scheme); s != schemes.end())
408 std::vector<std::pair<std::string, std::string>> known_uri_scheme::split_query_elements(std::string_view query)
const noexcept
410 std::vector<std::pair<std::string, std::string>> result;
411 detail::split_query_elements(query, result);
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...
auto * element(T &cont, RANDOM &rng=::ghassanpl::random::default_random_engine)
Returns an pointer to a random element in cont.
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.
constexpr bool isany(char32_t cp, T &&chars) noexcept
Checks if cp is any of the characters in chars.
char consume(std::string_view &str)
Consumes and returns the first character in the str, or \0 if no more characters.
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,...
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]).
std::string_view prefix(std::string_view str, size_t count) noexcept
Returns a substring containing the count leftmost characters of str. Always valid,...
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.
std::string uri
URIs are stored in a UTF-8 encoding where both non-ASCII code unit bytes as well as URI-reserved char...
known_uri_scheme const * query_uri_scheme(std::string_view scheme)
Looks up the scheme in the internal URI scheme database.
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...
@ use_well_known_port_numbers
if a port is not specified in the uri, the result will guess the port based on the scheme
Primary namespace for everything in this library.
Contains known URI scheme types and objects.
Holds the constituents of a URI.
std::vector< std::string > normalized_path() const noexcept
Returns the path normalized by applying any "." or ".." elements.
A (constexpr) value struct that represents a set of bits mapped to an enum (implemented as a bitset).
constexpr bool contains_all_of(ARGS... args) const noexcept
Returns whether or not all of the given flags are set.
constexpr bool contain(enum_type flag) const noexcept
Same as is_set.
An base class for classes that can validate (and potentially parse more info from) a known URI scheme...