Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
uri.h
1#ifndef SOURCEMETA_CORE_URI_H_
2#define SOURCEMETA_CORE_URI_H_
3
4#ifndef SOURCEMETA_CORE_URI_EXPORT
5#include <sourcemeta/core/uri_export.h>
6#endif
7
8#include <sourcemeta/core/text.h>
9
10// NOLINTBEGIN(misc-include-cleaner)
11#include <sourcemeta/core/uri_error.h>
12// NOLINTEND(misc-include-cleaner)
13
14#include <concepts> // std::convertible_to, std::same_as
15#include <cstddef> // std::size_t, std::ptrdiff_t
16#include <cstdint> // std::uint32_t
17#include <filesystem> // std::filesystem
18#include <istream> // std::istream
19#include <iterator> // std::forward_iterator_tag
20#include <memory> // std::unique_ptr
21#include <optional> // std::optional
22#include <span> // std::span
23#include <string> // std::string
24#include <string_view> // std::string_view
25#include <type_traits> // std::is_same_v, std::remove_cvref_t
26#include <utility> // std::pair, std::forward
27#include <vector> // std::vector
28
38
39namespace sourcemeta::core {
40
43class SOURCEMETA_CORE_URI_EXPORT URI {
44public:
46 URI() = default;
47
49 URI(const URI &) = default;
50
52 URI(URI &&) noexcept = default;
53
55 auto operator=(const URI &) -> URI & = default;
56
58 auto operator=(URI &&) noexcept -> URI & = default;
59
67 template <typename T>
68 requires std::convertible_to<T, std::string_view> &&
69 (!std::is_same_v<std::decay_t<T>, URI>)
70 explicit URI(T &&input) {
71 this->parse(std::string_view{std::forward<T>(input)});
72 }
73
83 URI(std::istream &input);
84
94 [[nodiscard]] auto is_absolute() const noexcept -> bool;
95
105 [[nodiscard]] auto is_urn() const -> bool;
106
116 [[nodiscard]] auto is_tag() const -> bool;
117
127 [[nodiscard]] auto is_mailto() const -> bool;
128
138 [[nodiscard]] auto is_file() const -> bool;
139
150 [[nodiscard]] auto is_http() const -> bool;
151
162 [[nodiscard]] auto is_https() const -> bool;
163
173 [[nodiscard]] auto is_fragment_only() const -> bool;
174
184 [[nodiscard]] auto is_relative() const -> bool;
185
195 [[nodiscard]] auto is_ipv4() const -> bool;
196
206 [[nodiscard]] auto is_ipv6() const -> bool;
207
224 [[nodiscard]] auto is_loopback() const -> bool;
225
240 [[nodiscard]] auto is_localhost() const -> bool;
241
251 [[nodiscard]] auto empty() const -> bool;
252
263 [[nodiscard]] auto scheme() const -> std::optional<std::string_view>;
264
275 [[nodiscard]] auto host() const -> std::optional<std::string_view>;
276
289 [[nodiscard]] auto port() const -> std::optional<std::uint32_t>;
290
309 [[nodiscard]] auto authority() const -> std::optional<std::string>;
310
322 [[nodiscard]] auto path() const -> std::optional<std::string_view>;
323
336 auto path(const std::string &path) -> URI &;
337
350 auto path(std::string &&path) -> URI &;
351
365 auto append_path(std::string_view path) -> URI &;
366
380 auto append_path(const URI &reference) -> URI &;
381
397 auto append_path(URI &&reference) -> URI &;
398
410 auto extension(std::string &&extension) -> URI &;
411
422 [[nodiscard]] auto fragment() const -> std::optional<std::string_view>;
423
436 auto fragment(const std::string_view fragment) -> URI &;
437
453 class SOURCEMETA_CORE_URI_EXPORT Query {
454 public:
466 explicit Query(const std::string_view raw);
467
479 [[nodiscard]] auto raw() const -> std::string_view;
480
497 [[nodiscard]] auto at(const std::string_view name) const
498 -> std::optional<std::string_view>;
499
503 class SOURCEMETA_CORE_URI_EXPORT const_iterator {
504 public:
505 using iterator_category = std::forward_iterator_tag;
506 using value_type = std::pair<std::string_view, std::string_view>;
507 using difference_type = std::ptrdiff_t;
508 using reference = const value_type &;
509 using pointer = const value_type *;
510
511 const_iterator() = default;
512 const_iterator(const std::string_view raw, const std::size_t pair_start);
513
514 [[nodiscard]] auto operator*() const -> reference;
515 [[nodiscard]] auto operator->() const -> pointer;
516 auto operator++() -> const_iterator &;
517 auto operator++(int) -> const_iterator;
518 [[nodiscard]] auto operator==(const const_iterator &other) const -> bool;
519 [[nodiscard]] auto operator!=(const const_iterator &other) const -> bool;
520
521 private:
522#if defined(_MSC_VER)
523#pragma warning(disable : 4251)
524#endif
525 std::string_view raw_{};
526 std::size_t pair_start_{std::string_view::npos};
527 value_type current_{};
528#if defined(_MSC_VER)
529#pragma warning(default : 4251)
530#endif
531 };
532
534 [[nodiscard]] auto begin() const -> const_iterator;
535
537 [[nodiscard]] auto end() const -> const_iterator;
538
539 private:
540#if defined(_MSC_VER)
541#pragma warning(disable : 4251)
542#endif
543 std::string_view raw_;
544#if defined(_MSC_VER)
545#pragma warning(default : 4251)
546#endif
547 };
548
561 [[nodiscard]] auto query() const -> std::optional<Query>;
562
575 auto query(const std::string_view query) -> URI &;
576
587 [[nodiscard]] auto recompose() const -> std::string;
588
602 [[nodiscard]] auto recompose_relative() const -> std::string;
603
618 [[nodiscard]] auto recompose_without_fragment() const
619 -> std::optional<std::string>;
620
631 auto canonicalize() -> URI &;
632
643 [[nodiscard]] auto to_path() const -> std::filesystem::path;
644
657 auto resolve_from(const URI &base) -> URI &;
658
684 auto relative_to(const URI &base) -> URI &;
685
701 auto rebase(const URI &base, const URI &new_base) -> URI &;
702
718 auto rebase(const URI &base, URI &&new_base) -> URI &;
719
734 [[nodiscard]] auto has_same_authority(const URI &other) const noexcept
735 -> bool;
736
751 [[nodiscard]] auto userinfo() const -> std::optional<std::string_view>;
752
765 auto userinfo(const std::string_view userinfo) -> URI &;
766
769 auto operator==(const URI &other) const noexcept -> bool;
770
772 auto operator<(const URI &other) const noexcept -> bool;
773
784 static auto from_fragment(const std::string_view fragment) -> URI;
785
797 static auto from_path(const std::filesystem::path &path) -> URI;
798
811 static auto from_iri(std::string_view input) -> URI;
812
825 [[nodiscard]] auto is_internationalized() const noexcept -> bool;
826
838 static auto canonicalize(std::string_view input) -> std::string;
839
853 [[nodiscard]] static auto escape(std::string_view input,
854 bool maybe_encoded = false) -> std::string;
855
871 template <typename Output>
872 static auto escape(const std::string_view input, Output &output,
873 const bool maybe_encoded = false) -> void {
874 output.reserve(output.size() + input.size() * 3);
875 for (std::size_t position = 0; position < input.size();) {
876 auto byte{static_cast<unsigned char>(input[position])};
877 std::size_t advance{1};
878 // Treat the input as possibly already encoded by decoding each valid
879 // escape before re-encoding, so a valid escape survives and a needlessly
880 // encoded unreserved octet is decoded
881 if (maybe_encoded && input[position] == '%' &&
882 position + 2 < input.size()) {
883 const auto high{hex_digit_value(input[position + 1])};
884 const auto low{high < 0 ? static_cast<std::int8_t>(-1)
885 : hex_digit_value(input[position + 2])};
886 if (low >= 0) {
887 byte = static_cast<unsigned char>((high << 4) | low);
888 advance = 3;
889 }
890 }
891
892 position += advance;
893 // RFC 3986 Section 2.3: the unreserved set passes through unescaped
894 if (is_alphanum(static_cast<char>(byte)) || byte == '-' || byte == '.' ||
895 byte == '_' || byte == '~') {
896 output.push_back(static_cast<char>(byte));
897 } else {
898 // RFC 3986 Section 2.1: percent-encode with uppercase hexadecimal
899 const auto high{static_cast<unsigned char>((byte >> 4U) & 0x0FU)};
900 const auto low{static_cast<unsigned char>(byte & 0x0FU)};
901 output.push_back('%');
902 output.push_back(
903 static_cast<char>(high < 10 ? '0' + high : 'A' + high - 10));
904 output.push_back(
905 static_cast<char>(low < 10 ? '0' + low : 'A' + low - 10));
906 }
907 }
908 }
909
930 template <typename Output>
931 static auto append_query_parameter(Output &sink, const std::string_view name,
932 const std::string_view value,
933 const char opener = '?') -> void {
934 if (!sink.empty() && sink.back() != opener && sink.back() != '&') {
935 sink.push_back('&');
936 }
937
938 URI::escape(name, sink);
939 sink.push_back('=');
940 URI::escape(value, sink);
941 }
942
953 [[nodiscard]] static auto unescape(std::string_view input) -> std::string;
954
973 template <typename Output>
974 [[nodiscard]] static auto unescape_form(const std::string_view input,
975 Output &output) -> bool {
976 const auto base{output.size()};
977 output.reserve(base + input.size());
978 for (std::size_t position = 0; position < input.size();) {
979 const auto character{input[position]};
980 if (character == '+') {
981 output.push_back(' ');
982 position += 1;
983 } else if (character == '%') {
984 const auto high{position + 2 < input.size()
985 ? hex_digit_value(input[position + 1])
986 : static_cast<std::int8_t>(-1)};
987 const auto low{high < 0 ? static_cast<std::int8_t>(-1)
988 : hex_digit_value(input[position + 2])};
989 if (low < 0) {
990 output.resize(base, '\0');
991 return false;
992 }
993
994 output.push_back(static_cast<char>((high << 4) | low));
995 position += 3;
996 } else {
997 output.push_back(character);
998 position += 1;
999 }
1000 }
1001
1002 return true;
1003 }
1004
1015 [[nodiscard]] static auto normalize_path(std::string_view path)
1016 -> std::string;
1017
1029 [[nodiscard]] static auto is_scheme(std::string_view input) noexcept -> bool;
1030
1041 [[nodiscard]] static auto is_gen_delim(char character) noexcept -> bool;
1042
1054 [[nodiscard]] static auto is_uri(std::string_view input) noexcept -> bool;
1055
1068 [[nodiscard]] static auto is_uri_reference(std::string_view input) noexcept
1069 -> bool;
1070
1081 [[nodiscard]] static auto is_iri(std::string_view input) noexcept -> bool;
1082
1095 [[nodiscard]] static auto is_iri_reference(std::string_view input) noexcept
1096 -> bool;
1097
1109 [[nodiscard]] static auto strip_path_prefix(std::string_view path,
1110 std::string_view prefix)
1111 -> std::optional<std::string>;
1112
1124 [[nodiscard]] static auto rebase_path(std::string_view path,
1125 std::string_view old_prefix,
1126 std::string_view new_prefix)
1127 -> std::optional<std::string>;
1128
1129private:
1130 auto parse(std::string_view input) -> void;
1131
1132// Exporting symbols that depends on the standard C++ library is considered
1133// safe.
1134// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
1135#if defined(_MSC_VER)
1136#pragma warning(disable : 4251)
1137#endif
1138 std::optional<std::string> path_{};
1139 std::optional<std::string> userinfo_{};
1140 std::optional<std::string> host_{};
1141 std::optional<std::uint32_t> port_{};
1142 std::optional<std::string> scheme_{};
1143 std::optional<std::string> fragment_{};
1144 std::optional<std::string> query_{};
1145 bool ip_literal_{false};
1146 // Whether this object was parsed as an IRI (RFC 3987) rather than a URI
1147 // (RFC 3986)
1148 bool iri_{false};
1149#if defined(_MSC_VER)
1150#pragma warning(default : 4251)
1151#endif
1152};
1153
1154} // namespace sourcemeta::core
1155
1156#endif
Definition uri.h:453
auto at(const std::string_view name) const -> std::optional< std::string_view >
auto raw() const -> std::string_view
Query(const std::string_view raw)
constexpr auto is_alphanum(const Character character) noexcept -> bool
Definition text.h:289
auto hex_digit_value(const char character) noexcept -> std::int8_t
Definition text.h:752
static auto unescape_form(const std::string_view input, Output &output) -> bool
Definition uri.h:974
static auto is_scheme(std::string_view input) noexcept -> bool
auto is_http() const -> bool
auto is_ipv4() const -> bool
auto append_path(std::string_view path) -> URI &
auto relative_to(const URI &base) -> URI &
auto to_path() const -> std::filesystem::path
auto is_urn() const -> bool
auto is_relative() const -> bool
auto scheme() const -> std::optional< std::string_view >
static auto from_path(const std::filesystem::path &path) -> URI
auto recompose_without_fragment() const -> std::optional< std::string >
auto is_mailto() const -> bool
auto is_ipv6() const -> bool
auto path() const -> std::optional< std::string_view >
auto is_https() const -> bool
URI(URI &&) noexcept=default
Move constructor.
auto resolve_from(const URI &base) -> URI &
auto recompose() const -> std::string
static auto is_iri(std::string_view input) noexcept -> bool
URI(std::istream &input)
static auto is_iri_reference(std::string_view input) noexcept -> bool
URI(const URI &)=default
Copy constructor.
URI()=default
Default constructor creates an empty URI.
auto is_localhost() const -> bool
auto fragment() const -> std::optional< std::string_view >
static auto unescape(std::string_view input) -> std::string
auto has_same_authority(const URI &other) const noexcept -> bool
auto is_fragment_only() const -> bool
auto canonicalize() -> URI &
auto is_internationalized() const noexcept -> bool
static auto normalize_path(std::string_view path) -> std::string
static auto append_query_parameter(Output &sink, const std::string_view name, const std::string_view value, const char opener='?') -> void
Definition uri.h:931
auto host() const -> std::optional< std::string_view >
auto is_absolute() const noexcept -> bool
auto userinfo() const -> std::optional< std::string_view >
auto is_file() const -> bool
auto empty() const -> bool
static auto is_uri_reference(std::string_view input) noexcept -> bool
auto is_loopback() const -> bool
auto is_tag() const -> bool
auto rebase(const URI &base, const URI &new_base) -> URI &
static auto escape(std::string_view input, bool maybe_encoded=false) -> std::string
auto authority() const -> std::optional< std::string >
static auto is_gen_delim(char character) noexcept -> bool
auto recompose_relative() const -> std::string
auto port() const -> std::optional< std::uint32_t >
static auto from_iri(std::string_view input) -> URI
static auto strip_path_prefix(std::string_view path, std::string_view prefix) -> std::optional< std::string >
auto extension(std::string &&extension) -> URI &
static auto rebase_path(std::string_view path, std::string_view old_prefix, std::string_view new_prefix) -> std::optional< std::string >
auto query() const -> std::optional< Query >
static auto from_fragment(const std::string_view fragment) -> URI
static auto is_uri(std::string_view input) noexcept -> bool