45 [[nodiscard]]
constexpr std::optional<std::string>
const& userinfo()
const noexcept
50 constexpr authority_type& set_userinfo(std::optional<std::string>
const& rhs)
noexcept
56 [[nodiscard]]
constexpr std::string const& host()
const noexcept
64 _host = to_lower(rhs);
68 [[nodiscard]]
constexpr std::optional<std::string>
const& port()
const noexcept
73 constexpr authority_type& set_port(std::optional<std::string>
const& rhs)
87 [[nodiscard]]
constexpr friend size_t to_string_size(
authority_type const& rhs)
noexcept
91 size += rhs._userinfo->size() + 1;
93 size += rhs._host.size();
95 size += rhs._port->size() + 1;
105 r += URI::encode<HI_SUB_DELIM, ':'>(*rhs._userinfo);
109 if (rhs._host.empty() or rhs._host.front() !=
'[') {
110 r += URI::encode<HI_SUB_DELIM>(rhs._host);
112 r += URI::encode<HI_SUB_DELIM, '[', ']', ':'>(rhs._host);
124 using const_iterator = std::string_view::const_iterator;
126 std::optional<std::string> _userinfo = {};
128 std::optional<std::string> _port = {};
130 [[nodiscard]]
constexpr static void validate_host(std::string_view str)
132 if (str.starts_with(
'[') and not str.ends_with(
']')) {
133 throw uri_error(
"The host-component starts with '[' has missing ']' at end");
134 }
else if (str.ends_with(
']') and str.starts_with(
'[')) {
135 throw uri_error(
"The host-component ends with ']' has missing '[' at start");
139 [[nodiscard]]
constexpr static void validate_port(std::string_view str)
142 if (not(c >=
'0' and c <=
'9')) {
143 throw uri_error(
"The port-component contains a non-digit.");
155 [[nodiscard]]
constexpr const_iterator parse_userinfo(const_iterator first, const_iterator last)
noexcept
157 for (
auto it = first; it != last; ++it) {
158 if (
hilet c = *it; c ==
'@') {
174 [[nodiscard]]
constexpr const_iterator parse_host(const_iterator first, const_iterator last)
noexcept
192 for (; it != last; ++it) {
193 if (
hilet c = *it; c ==
':') {
206 [[nodiscard]]
constexpr const_iterator parse_port(const_iterator first, const_iterator last)
noexcept
212 constexpr void parse(std::string_view rhs)
noexcept
214 auto first = rhs.cbegin();
215 auto last = rhs.cend();
216 auto it = parse_userinfo(first, last);
217 it = parse_host(it, last);
219 if (it != last and *it ==
':') {
220 it = parse_port(++it, last);
257 constexpr path_type()
noexcept =
default;
265 auto r = make_vector<std::string>(std::views::transform(std::views::split(str, std::string_view{
"/"}), [](
auto&& x) {
269 if (r.size() == 1 and r.front().empty()) {
273 if (not r.empty() and (r.back() ==
"." or r.back() ==
".." or r.back() ==
"**")) {
283 hi_axiom(holds_invariant());
286 [[nodiscard]]
constexpr bool absolute()
const noexcept
291 [[nodiscard]]
constexpr bool double_absolute()
const noexcept
293 return size() >= 3 and (*
this)[0].
empty() and (*
this)[1].empty();
296 [[nodiscard]]
constexpr bool is_root()
const noexcept
298 return size() == 2 and (*
this)[0].
empty() and (*
this)[1].empty();
301 [[nodiscard]]
constexpr std::optional<std::string> filename()
const noexcept
326 hi_axiom(holds_invariant());
332 if (base_has_authority and base.empty()) {
338 if (not base.empty()) {
342 base.insert(base.cend(), ref.cbegin(), ref.cend());
344 if (base.size() == 1 and base.front().empty()) {
349 hi_axiom(base.holds_invariant());
380 for (
auto it = path.cbegin(); it != path.cend();) {
385 }
else if (*it ==
"..") {
386 if (it == path.cbegin()) {
390 }
else if (it - 1 == path.cbegin() and (it - 1)->empty()) {
396 it = path.erase(it - 1, it + 1);
404 if (path.size() == 1 and path.front().empty()) {
409 hi_axiom(path.holds_invariant());
413 [[nodiscard]]
constexpr bool holds_invariant() const noexcept
419 if (
back() ==
"." or
back() ==
".." or
back() ==
"**") {
426 [[nodiscard]]
constexpr friend size_t to_string_size(path_type
const& rhs)
noexcept
429 for (
hilet& segment : rhs) {
443 r.
reserve(to_string_size(rhs));
445 auto segment_is_empty =
false;
446 for (
auto it = rhs.
cbegin(); it != rhs.
cend(); ++it) {
447 segment_is_empty = it->empty();
449 if (it == rhs.
cbegin() and not has_scheme) {
450 r += URI::encode<HI_SUB_DELIM, '@'>(*it);
452 r += URI::encode<HI_PCHAR>(*it);
457 if (not r.empty() and not segment_is_empty) {
465 constexpr URI() noexcept = default;
466 constexpr
URI(
URI const&) noexcept = default;
467 constexpr
URI(
URI&&) noexcept = default;
468 constexpr
URI& operator=(
URI const&) noexcept = default;
469 constexpr
URI& operator=(
URI&&) noexcept = default;
476 constexpr explicit
URI(
std::string_view str)
483 constexpr explicit URI(
const char *str) :
URI(
std::string_view{str}) {}
485 [[nodiscard]]
constexpr bool empty() const noexcept
487 return not _scheme and not _authority and _path.
empty() and not _query and not _fragment;
490 constexpr operator bool() const noexcept
499 [[nodiscard]]
constexpr std::optional<std::string>
const&
scheme() const noexcept
510 validate_scheme(*rhs);
522 [[nodiscard]]
constexpr std::optional<authority_type>
const&
authority() const noexcept
527 constexpr URI& set_authority(std::optional<authority_type>
const& rhs)
noexcept
533 [[nodiscard]]
constexpr path_type
const& path() const noexcept
538 constexpr URI& set_path(path_type
const& rhs)
540 validate_path(rhs, to_bool(_authority));
545 [[nodiscard]]
constexpr std::optional<std::string> filename() const noexcept
547 return _path.filename();
565 [[nodiscard]]
constexpr std::optional<std::string>
const&
query() const noexcept
570 constexpr URI& set_query(std::optional<std::string>
const& rhs)
noexcept
580 [[nodiscard]]
constexpr std::optional<std::string>
const&
fragment() const noexcept
585 constexpr URI& set_fragment(std::optional<std::string>
const& rhs)
noexcept
591 [[nodiscard]]
constexpr friend std::string to_string(
URI const& rhs)
noexcept
594 r.
reserve(to_string_size(rhs));
601 if (rhs._authority) {
604 r += to_string(*rhs._authority);
607 r += to_string(rhs._path, to_bool(rhs._scheme));
616 r +=
URI::encode<HI_PCHAR,
'/',
'?'>(*rhs._fragment);
624 return lhs << to_string(rhs);
627 [[nodiscard]]
constexpr friend bool operator==(URI
const& lhs, URI
const& rhs)
noexcept =
default;
628 [[nodiscard]]
constexpr friend auto operator<=>(URI
const& lhs, URI
const& rhs)
noexcept =
default;
630 [[nodiscard]]
constexpr friend URI operator/(URI
const& base, URI
const& ref)
noexcept
635 target._scheme =
ref._scheme;
636 target._authority =
ref._authority;
637 target._path = remove_dot_segments(
ref._path);
638 target._query =
ref._query;
640 if (
ref._authority) {
641 target._authority =
ref._authority;
642 target._path = remove_dot_segments(
ref._path);
643 target._query =
ref._query;
646 if (
ref._path.empty()) {
647 target._path = base._path;
649 target._query =
ref._query;
651 target._query = base._query;
654 if (
ref._path.absolute()) {
655 target._path = remove_dot_segments(
ref._path);
657 target._path = remove_dot_segments(
merge(base._path,
ref._path, to_bool(base._authority)));
659 target._query =
ref._query;
661 target._authority = base._authority;
663 target._scheme = base._scheme;
666 target._fragment =
ref._fragment;
671 [[nodiscard]]
constexpr friend bool operator==(URI
const& lhs, std::string_view rhs)
noexcept
673 return lhs == URI(rhs);
676 [[nodiscard]]
constexpr friend auto operator<=>(URI
const& lhs, std::string_view rhs)
noexcept
678 return lhs == URI(rhs);
681 [[nodiscard]]
constexpr friend URI operator/(URI
const& base, std::string_view ref)
noexcept
683 return base / URI(ref);
692 decode(
auto first,
auto last)
requires std::is_same_v < std::decay_t<
decltype(*first)>,
701 uint8_t code_unit = 0;
702 for (
auto it = first; it != last; ++it) {
709 if (c >=
'0' and c <=
'9') {
710 code_unit |= c & 0xf;
711 }
else if ((c >=
'a' and c <=
'f') or (c >=
'A' and c <=
'F')) {
712 code_unit |= (c & 0xf) + 9;
714 throw uri_error(
"Unexpected character in percent-encoding.");
717 r += char_cast<char>(code_unit);
733 throw uri_error(
"Unexpected end of URI component inside percent-encoding.");
746 return decode(std::ranges::begin(range), std::ranges::end(range));
755 template<
char... Extras,
typename It,
typename ItEnd>
763 for (
auto it = first; it != last; ++it) {
767 (c >=
'a' and c <=
'z') or
768 (c >=
'A' and c <=
'Z') or
769 (c >=
'0' and c <=
'9') or
770 c ==
'-' or c ==
'.' or c ==
'_' or c ==
'~'
771 ) or ... or (c == Extras))) {
776 auto uc = char_cast<uint8_t>(c);
777 if (
auto nibble = narrow_cast<char>(uc >> 4); nibble <= 9) {
780 r +=
'A' + nibble - 10;
782 if (
auto nibble = narrow_cast<char>(uc & 0xf); nibble <= 9) {
785 r +=
'A' + nibble - 10;
800 template<
char... Extras,
typename Range>
803 return encode<Extras...>(std::ranges::begin(range), std::ranges::end(range));
807 using const_iterator = std::string_view::const_iterator;
809 std::optional<std::string> _scheme;
810 std::optional<authority_type> _authority;
812 std::optional<std::string> _query;
813 std::optional<std::string> _fragment;
815 [[nodiscard]]
constexpr friend size_t to_string_size(
URI const& rhs)
noexcept
819 size += rhs._scheme->
size() + 1;
821 if (rhs._authority) {
822 size += to_string_size(*rhs._authority) + 2;
824 size += to_string_size(rhs._path);
827 size += rhs._query->size() + 1;
830 size += rhs._fragment->size() + 1;
836 [[nodiscard]]
constexpr static bool is_scheme_start(
char c)
noexcept
838 return (c >=
'a' and c <=
'z') or (c >=
'A' and c <=
'Z');
841 [[nodiscard]]
constexpr static bool is_scheme(
char c)
noexcept
843 return is_scheme_start(c) or (c >=
'0' and c <=
'9') or c == '+' or c == '-' or c == '.';
846 [[nodiscard]] constexpr static
void validate_scheme(
std::string_view str)
849 throw uri_error(
"The scheme-component is not allowed to be empty (it is allowed to not exist).");
851 if (not is_scheme_start(str.front())) {
852 throw uri_error(
"The scheme-component does not start with [a-zA-Z].");
855 if (not is_scheme(c)) {
856 throw uri_error(
"The scheme-component contains a character outside [a-zA-Z0-9.+-].");
861 [[nodiscard]]
constexpr static void validate_path(path_type
const& path,
bool has_authority)
864 if (not(path.empty() or path.absolute())) {
865 throw uri_error(
"A path-component in a URI with an authority-component must be empty or absolute.");
867 }
else if (path.double_absolute()) {
868 throw uri_error(
"A path-component in a URI without an authority-component may not start with a double slash '//'.");
878 [[nodiscard]]
constexpr const_iterator parse_scheme(const_iterator first, const_iterator last)
880 for (
auto it = first; it != last; ++it) {
881 if (
hilet c = *it;
c ==
':') {
885 }
else if (c ==
'/' or c ==
'?' or c ==
'#') {
895 [[nodiscard]]
constexpr const_iterator parse_authority(const_iterator first, const_iterator last)
897 for (
auto it = first; it != last; it++) {
898 if (
hilet c = *it;
c ==
'/' or
c ==
'?' or
c ==
'#') {
899 set_authority(authority_type{std::string_view{first, it}});
904 set_authority(authority_type{std::string_view{first, last}});
908 [[nodiscard]]
constexpr const_iterator parse_path(const_iterator first, const_iterator last)
910 for (
auto it = first; it != last; it++) {
911 if (
hilet c = *it;
c ==
'?' or
c ==
'#') {
912 set_path(path_type{std::string_view{first, it}});
917 set_path(path_type{std::string_view{first, last}});
921 [[nodiscard]]
constexpr const_iterator parse_query(const_iterator first, const_iterator last)
923 for (
auto it = first; it != last; it++) {
924 if (
hilet c = *it;
c ==
'#') {
934 [[nodiscard]]
constexpr const_iterator parse_fragment(const_iterator first, const_iterator last)
940 constexpr void parse(std::string_view str)
942 auto first = str.cbegin();
943 auto last = str.cend();
944 auto it = parse_scheme(first, last);
946 if (
std::distance(it, last) >= 2 and it[0] ==
'/' and it[1] ==
'/') {
948 it = parse_authority(it, last);
951 it = parse_path(it, last);
953 if (it != last and *it ==
'?') {
954 it = parse_query(++it, last);
957 if (it != last and *it ==
'#') {
958 it = parse_fragment(++it, last);