5#ifndef UPA_URLPATTERN_H
6#define UPA_URLPATTERN_H
19# include <string_view>
20# include <type_traits>
21# include <unordered_map>
30using namespace std::string_view_literals;
34inline bool is_special_scheme(std::string_view scheme) {
35 const auto* scheme_inf = detail::get_scheme_info(scheme);
36 return scheme_inf !=
nullptr && scheme_inf->is_special;
39inline bool is_special_scheme_default_port(std::string_view scheme, std::string_view port) {
41 const auto* scheme_inf = detail::get_scheme_info(scheme);
42 if (scheme_inf !=
nullptr && scheme_inf->is_special && scheme_inf->default_port >= 0) {
44 const auto* first = port.data();
45 const auto* last = port.data() + port.length();
46 std::uint16_t nport = 0;
47 const auto r = std::from_chars(first, last, nport);
48 return r.ec == std::errc() && r.ptr == last && nport == scheme_inf->default_port;
58template <
class T,
class TB,
59 upa::enable_if_str_arg_t<T> = 0,
60 upa::enable_if_optional_str_arg_t<TB> = 0>
61inline upa::url parse_url_against_base(
const T& input,
const TB& base_url_str) {
64 if constexpr (upa::is_nullopt_v<TB>) {
66 }
else if constexpr (upa::is_optional_v<TB>) {
68 url.
parse(input, *base_url_str);
72 url.
parse(input, base_url_str);
79template <
class StrT, upa::enable_if_str_arg_t<StrT> = 0>
80constexpr char32_t get_code_point(StrT&& input) {
81 const auto inp = upa::make_str_arg(std::forward<StrT>(input));
82 const auto* ptr = inp.begin();
83 return upa::url_utf::read_utf_char(ptr, inp.end()).value;
86template <
class StrT, upa::enable_if_str_arg_t<StrT> = 0>
87constexpr char32_t get_code_point(StrT&& input, std::size_t& ind) {
88 const auto inp = upa::make_str_arg(std::forward<StrT>(input));
89 const auto* ptr = inp.begin() + ind;
90 const char32_t cp = upa::url_utf::read_utf_char(ptr, inp.end()).value;
91 ind = ptr - inp.begin();
98template<
class,
class =
void>
99inline constexpr bool has_inputs_v =
false;
102inline constexpr bool has_inputs_v<T, std::void_t<
decltype(T::inputs)>> =
true;
107template<
class T,
class =
void>
108struct has_regex_engine_members : std::false_type {};
111struct has_regex_engine_members<T, std::void_t<
114 decltype(std::declval<typename T::result>().size()),
115 decltype(std::declval<typename T::result>().get(std::declval<std::size_t>(),
116 std::declval<std::string_view>())),
118 decltype(std::declval<T>().init(std::declval<std::string_view>(), std::declval<bool>())),
119 decltype(std::declval<T>().exec(std::declval<std::string_view>(),
120 std::declval<typename T::result&>())),
121 decltype(std::declval<T>().test(std::declval<std::string_view>()))
122 >> : std::conjunction<
124 std::is_same<decltype(std::declval<typename T::result>().size()), std::size_t>,
125 std::is_same<decltype(std::declval<typename T::result>().get(std::declval<std::size_t>(),
126 std::declval<std::string_view>())), std::optional<std::string>>,
128 std::is_same<decltype(std::declval<T>().init(std::declval<std::string_view>(),
129 std::declval<bool>())), bool>,
130 std::is_same<decltype(std::declval<T>().exec(std::declval<std::string_view>(),
131 std::declval<typename T::result&>())), bool>,
132 std::is_same<decltype(std::declval<T>().test(std::declval<std::string_view>())), bool>
141 std::is_default_constructible_v<T>
142 && std::is_copy_constructible_v<T>
143 && std::is_move_constructible_v<T>
144 && std::is_copy_assignable_v<T>
145 && std::is_move_assignable_v<T>
146 && pattern::has_regex_engine_members<T>::value;
164 std::optional<std::string>
port;
167 std::optional<std::string>
hash;
184 [[nodiscard]]
inline std::optional<std::string_view>
get(std::string_view name)
const {
185 if (
auto ptr = get_member(name))
193 template <
typename T, std::enable_if_t<std::is_assignable_v<std::
string, T>,
int> = 0>
194 inline void set(std::string_view name, T&& value) {
195 if (
auto ptr = get_member(name))
196 this->*ptr = std::forward<T>(value);
201 [[nodiscard]] UPA_API
static std::optional<std::string>
urlpattern_init::*
202 get_member(std::string_view name);
213template <
class regex_engine>
214inline urlpattern_init parse_constructor_string(std::string_view input);
264 std::string_view value_;
268using token_list = std::vector<token>;
274enum class tokenize_policy {
280inline token_list tokenize(std::string_view input, tokenize_policy policy);
301 enum class modifier {
312 UPA_CONSTEXPR_20 part(type t, std::string&& value, modifier m)
314 , value_{ std::move(value) }
327using part_list = std::vector<part>;
336 std::string_view delimiter_code_point;
337 std::string_view prefix_code_point;
338 bool ignore_case =
false;
349using encoding_callback = std::string (*)(std::string_view input);
353inline part_list parse_pattern_string(std::string_view input,
const options& opt, encoding_callback encoding_cb);
356inline constexpr std::string_view full_wildcard_regexp_value{
".*"sv };
359UPA_CONSTEXPR_20 std::string generate_segment_wildcard_regexp(
const options& opt);
364using string_list = std::vector<std::string>;
367UPA_CONSTEXPR_20 std::pair<std::string, string_list> generate_regular_expression_and_name_list(
368 const part_list& pt_list,
const options& opt);
370UPA_CONSTEXPR_20
void append_escape_regexp_string(std::string& result, std::string_view input);
375inline std::string generate_pattern_string(
const part_list& pt_list,
const options& opt);
377UPA_CONSTEXPR_20 std::string escape_pattern_string(std::string_view input);
378UPA_CONSTEXPR_20
void append_escape_pattern_string(std::string& result, std::string_view input);
379UPA_CONSTEXPR_20
void append_convert_modifier_to_string(std::string& result, part::modifier modifier);
387inline std::string canonicalize_protocol(std::string_view value);
388inline std::string canonicalize_username(std::string_view value);
389inline std::string canonicalize_password(std::string_view value);
390inline std::string canonicalize_hostname(std::string_view value);
391inline std::string canonicalize_ipv6_hostname(std::string_view value);
392inline std::string canonicalize_port(std::string_view port_value, std::optional<std::string_view> protocol_value);
393inline std::string canonicalize_port(std::string_view port_value) {
394 return canonicalize_port(port_value, std::nullopt);
396inline std::string canonicalize_pathname(std::string_view value);
397inline std::string canonicalize_opaque_pathname(std::string_view value);
398inline std::string canonicalize_search(std::string_view value);
399inline std::string canonicalize_hash(std::string_view value);
404enum class urlpattern_init_type { PATTERN, URL };
406inline urlpattern_init process_urlpattern_init(
const urlpattern_init& init, urlpattern_init_type type,
bool set_empty);
414template <
class regex_engine>
416 component() =
default;
417 component(
const component&) =
delete;
418 component(component&&) noexcept = default;
420 component(std::string_view input, encoding_callback encoding_cb, const options& opt);
422 ~component() = default;
424 component& operator=(const component&) = delete;
425 component& operator=(component&&) noexcept = default;
428 std::
string pattern_string_;
429 regex_engine regular_expression_;
430 string_list group_name_list_;
431 bool has_regexp_groups_ = false;
442inline constexpr options default_options{};
446inline constexpr options hostname_options {
"."sv };
448template <
class regex_engine>
449inline bool protocol_component_matches_special_scheme(
const component<regex_engine>& protocol_component);
451constexpr bool hostname_pattern_is_ipv6_address(std::string_view input)
noexcept;
494 template <class T, class TB =
std::nullopt_t,
upa::enable_if_str_arg_t<T> = 0,
495 upa::enable_if_optional_str_arg_t<TB> = 0>
497 : size_{ 1u + get_optional_count(str1) }
498 , arr_{ make_string_view(str0), get_optional_item(str1) }
503 , arr_{ std::addressof(init) }
514 constexpr bool empty() const noexcept {
return size_ == 0; }
518 template <
class StrT, enable_if_str_arg_t<StrT> = 0>
519 static constexpr auto make_string_view(
const StrT& str) {
520 const auto inp = make_str_arg(str);
521 return util::to_string_view<str_arg_char_t<StrT>>(inp.data(), inp.length());
524 static constexpr size_type get_optional_count(
const T& ostr) {
525 if constexpr (upa::is_nullopt_v<T>)
527 else if constexpr (upa::is_optional_v<T>)
528 return ostr ? 1u : 0u;
533 static constexpr value_type get_optional_item(
const T& ostr) {
534 if constexpr (upa::is_nullopt_v<T>)
536 else if constexpr (upa::is_optional_v<T>)
537 return ostr ? value_type{ make_string_view(*ostr) } : value_type{};
539 return make_string_view(ostr);
552 std::unordered_map<std::string_view, std::optional<std::string>>
groups;
621template <
class regex_engine,
622 typename = std::enable_if_t<is_regex_engine_v<regex_engine>>>
659 template <
class T,
class TB, upa::enable_if_str_arg_t<T> = 0,
660 upa::enable_if_optional_str_arg_t<TB> = 0>
662 :
urlpattern{ make_urlpattern_init(input,
std::forward<TB>(base_url)), opt } {}
676 template <
class T, upa::enable_if_str_arg_t<T> = 0>
678 :
urlpattern{ make_urlpattern_init(input,
std::nullopt), opt } {}
689 [[nodiscard]]
bool test(
const urlpattern_init& input)
const;
699 template <
class T,
class TB = std::nullopt_t, upa::enable_if_str_arg_t<T> = 0,
700 upa::enable_if_optional_str_arg_t<TB> = 0>
701 [[nodiscard]]
bool test(
const T& input,
const TB& base_url_str = upa::nullopt)
const;
707 [[nodiscard]]
bool test(
const upa::url& url)
const;
738 template <
class ResT = urlpattern_result,
739 std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>,
int> = 0>
740 [[nodiscard]] std::optional<ResT> exec(
const urlpattern_init& input)
const;
760 template <
class ResT = urlpattern_result,
class T,
class TB = std::nullopt_t,
761 std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>,
int> = 0,
762 upa::enable_if_str_arg_t<T> = 0, upa::enable_if_optional_str_arg_t<TB> = 0>
763 [[nodiscard]] std::optional<ResT> exec(
const T& input,
764 const TB& base_url_str = upa::nullopt)
const;
781 template <
class ResT = urlpattern_result,
782 std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>,
int> = 0>
783 [[nodiscard]] std::optional<ResT> exec(
const upa::url& url)
const;
786 [[nodiscard]] std::string_view get_protocol() const noexcept;
789 [[nodiscard]]
std::string_view get_username() const noexcept;
792 [[nodiscard]]
std::string_view get_password() const noexcept;
795 [[nodiscard]]
std::string_view get_hostname() const noexcept;
798 [[nodiscard]]
std::string_view get_port() const noexcept;
801 [[nodiscard]]
std::string_view get_pathname() const noexcept;
804 [[nodiscard]]
std::string_view get_search() const noexcept;
807 [[nodiscard]]
std::string_view get_hash() const noexcept;
811 [[nodiscard]]
bool has_regexp_groups() const noexcept;
814 using regex_exec_result = typename regex_engine::result;
817 std::string_view protocol,
std::string_view username,
std::string_view password,
818 std::string_view hostname,
std::string_view port,
std::string_view pathname,
819 std::string_view search,
std::string_view hash) const;
821 template <class ResT>
822 std::optional<ResT> match(
823 std::string_view protocol,
std::string_view username,
std::string_view password,
824 std::string_view hostname,
std::string_view port,
std::string_view pathname,
825 std::string_view search,
std::string_view hash) const;
827 template <class T, class TB,
upa::enable_if_str_arg_t<T> = 0,
828 upa::enable_if_optional_str_arg_t<TB> = 0>
829 static urlpattern_init make_urlpattern_init(const T& input, TB&& base_url);
831 static urlpattern_component_result create_component_match_result(
832 const pattern::component<regex_engine>& comp,
std::string_view input,
833 const regex_exec_result& exec_result);
837 pattern::component<regex_engine> protocol_component_;
838 pattern::component<regex_engine> username_component_;
839 pattern::component<regex_engine> password_component_;
840 pattern::component<regex_engine> hostname_component_;
841 pattern::component<regex_engine> port_component_;
842 pattern::component<regex_engine> pathname_component_;
843 pattern::component<regex_engine> search_component_;
844 pattern::component<regex_engine> hash_component_;
858 :
std::runtime_error(what_arg)
874template <
class regex_engine,
typename E>
875template <
class T,
class TB, upa::enable_if_str_arg_t<T>, upa::enable_if_optional_str_arg_t<TB>>
876inline urlpattern_init urlpattern<regex_engine, E>::make_urlpattern_init(
const T& input, TB&& base_url)
878 urlpattern_init init{ pattern::parse_constructor_string<regex_engine>(upa::make_string(input)) };
879 if constexpr (upa::is_nullopt_v<TB>) {
881 throw urlpattern_error(
"No base URL");
882 }
else if constexpr (upa::is_optional_v<TB>) {
884 init.base_url = upa::make_string(*std::forward<TB>(base_url));
885 else if (!init.protocol)
886 throw urlpattern_error(
"No base URL");
888 init.base_url = upa::make_string(std::forward<TB>(base_url));
893template <
class regex_engine,
typename E>
895 using namespace std::string_view_literals;
899 auto processed_init = process_urlpattern_init(init, pattern::urlpattern_init_type::PATTERN,
false);
904 if (!processed_init.protocol) processed_init.protocol =
"*"sv;
905 if (!processed_init.username) processed_init.username =
"*"sv;
906 if (!processed_init.password) processed_init.password =
"*"sv;
907 if (!processed_init.hostname) processed_init.hostname =
"*"sv;
908 if (!processed_init.port) processed_init.port =
"*"sv;
909 if (!processed_init.pathname) processed_init.pathname =
"*"sv;
910 if (!processed_init.search) processed_init.search =
"*"sv;
911 if (!processed_init.hash) processed_init.hash =
"*"sv;
916 if (pattern::is_special_scheme_default_port(*processed_init.protocol, *processed_init.port))
917 processed_init.port =
""sv;
920 protocol_component_ = pattern::component<regex_engine>(*processed_init.protocol,
921 pattern::canonicalize_protocol, pattern::default_options);
922 username_component_ = pattern::component<regex_engine>(*processed_init.username,
923 pattern::canonicalize_username, pattern::default_options);
924 password_component_ = pattern::component<regex_engine>(*processed_init.password,
925 pattern::canonicalize_password, pattern::default_options);
927 if (pattern::hostname_pattern_is_ipv6_address(*processed_init.hostname))
928 hostname_component_ = pattern::component<regex_engine>(*processed_init.hostname,
929 pattern::canonicalize_ipv6_hostname, pattern::hostname_options);
931 hostname_component_ = pattern::component<regex_engine>(*processed_init.hostname,
932 pattern::canonicalize_hostname, pattern::hostname_options);
934 port_component_ = pattern::component<regex_engine>(*processed_init.port,
935 pattern::canonicalize_port, pattern::default_options);
939 const pattern::options compile_opt{
""sv,
""sv, opt.
ignore_case };
940 if (pattern::protocol_component_matches_special_scheme(protocol_component_)) {
943 const pattern::options path_compile_opt{
"/"sv,
"/"sv, opt.
ignore_case };
944 pathname_component_ = pattern::component<regex_engine>(*processed_init.pathname,
945 pattern::canonicalize_pathname, path_compile_opt);
947 pathname_component_ = pattern::component<regex_engine>(*processed_init.pathname,
948 pattern::canonicalize_opaque_pathname, compile_opt);
950 search_component_ = pattern::component<regex_engine>(*processed_init.search,
951 pattern::canonicalize_search, compile_opt);
952 hash_component_ = pattern::component<regex_engine>(*processed_init.hash,
953 pattern::canonicalize_hash, compile_opt);
957template <
class regex_engine,
typename E>
959 return protocol_component_.pattern_string_;
961template <
class regex_engine,
typename E>
963 return username_component_.pattern_string_;
965template <
class regex_engine,
typename E>
967 return password_component_.pattern_string_;
969template <
class regex_engine,
typename E>
971 return hostname_component_.pattern_string_;
973template <
class regex_engine,
typename E>
975 return port_component_.pattern_string_;
977template <
class regex_engine,
typename E>
979 return pathname_component_.pattern_string_;
981template <
class regex_engine,
typename E>
983 return search_component_.pattern_string_;
985template <
class regex_engine,
typename E>
987 return hash_component_.pattern_string_;
993template <
class regex_engine,
typename E>
997 apply_result = process_urlpattern_init(input, pattern::urlpattern_init_type::URL,
true);
999 catch (std::exception&) {
1002 return match_for_test(
1008template <
class regex_engine,
typename E>
1009template <
class T,
class TB, upa::enable_if_str_arg_t<T>, upa::enable_if_optional_str_arg_t<TB>>
1011 return test(pattern::parse_url_against_base(input, base_url_str));
1014template <
class regex_engine,
typename E>
1019 return match_for_test(
1030template <
class regex_engine,
typename E>
1031inline bool urlpattern<regex_engine, E>::match_for_test(
1032 std::string_view protocol, std::string_view username, std::string_view password,
1033 std::string_view hostname, std::string_view port, std::string_view pathname,
1034 std::string_view search, std::string_view hash)
const
1037 protocol_component_.regular_expression_.test(protocol) &&
1038 username_component_.regular_expression_.test(username) &&
1039 password_component_.regular_expression_.test(password) &&
1040 hostname_component_.regular_expression_.test(hostname) &&
1041 port_component_.regular_expression_.test(port) &&
1042 pathname_component_.regular_expression_.test(pathname) &&
1043 search_component_.regular_expression_.test(search) &&
1044 hash_component_.regular_expression_.test(hash);
1050template <
class regex_engine,
typename E>
1051template <
class ResT, std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>,
int>>
1055 apply_result = process_urlpattern_init(input, pattern::urlpattern_init_type::URL,
true);
1057 catch (std::exception&) {
1058 return std::nullopt;
1061 auto result = match<ResT>(
1065 if constexpr (pattern::has_inputs_v<ResT>) {
1068 result->inputs =
decltype(ResT::inputs){ input };
1073template <
class regex_engine,
typename E>
1074template <
class ResT,
class T,
class TB,
1075 std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>,
int>,
1076 upa::enable_if_str_arg_t<T>, upa::enable_if_optional_str_arg_t<TB>>
1078 const TB& base_url_str)
const
1081 const auto url = pattern::parse_url_against_base(input, base_url_str);
1083 return std::nullopt;
1085 auto result = match<ResT>(
1094 if constexpr (pattern::has_inputs_v<ResT>) {
1097 result->inputs =
decltype(ResT::inputs){ input, base_url_str };
1102template <
class regex_engine,
typename E>
1103template <
class ResT, std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>,
int>>
1106 return std::nullopt;
1108 auto result = match<ResT>(
1117 if constexpr (pattern::has_inputs_v<ResT>) {
1120 result->inputs =
decltype(ResT::inputs){
url.
href() };
1128template <
class regex_engine,
typename E>
1130 const pattern::component<regex_engine>& comp, std::string_view input,
1131 const regex_exec_result& exec_result)
1134 result.
input = input;
1143 const auto count = std::min(exec_result.size(), comp.group_name_list_.size() + 1);
1144 for (std::size_t index = 1; index < count; ++index) {
1145 std::string_view name = comp.group_name_list_[index - 1];
1146 result.
groups.emplace(name, exec_result.get(index, input));
1151template <
class regex_engine,
typename E>
1152template <
class ResT>
1153inline std::optional<ResT> urlpattern<regex_engine, E>::match(
1154 std::string_view protocol, std::string_view username, std::string_view password,
1155 std::string_view hostname, std::string_view port, std::string_view pathname,
1156 std::string_view search, std::string_view hash)
const
1160 regex_exec_result protocol_exec_result;
1161 if (!protocol_component_.regular_expression_.exec(protocol, protocol_exec_result))
1162 return std::nullopt;
1164 regex_exec_result username_exec_result;
1165 if (!username_component_.regular_expression_.exec(username, username_exec_result))
1166 return std::nullopt;
1168 regex_exec_result password_exec_result;
1169 if (!password_component_.regular_expression_.exec(password, password_exec_result))
1170 return std::nullopt;
1172 regex_exec_result hostname_exec_result;
1173 if (!hostname_component_.regular_expression_.exec(hostname, hostname_exec_result))
1174 return std::nullopt;
1176 regex_exec_result port_exec_result;
1177 if (!port_component_.regular_expression_.exec(port, port_exec_result))
1178 return std::nullopt;
1180 regex_exec_result pathname_exec_result;
1181 if (!pathname_component_.regular_expression_.exec(pathname, pathname_exec_result))
1182 return std::nullopt;
1184 regex_exec_result search_exec_result;
1185 if (!search_component_.regular_expression_.exec(search, search_exec_result))
1186 return std::nullopt;
1188 regex_exec_result hash_exec_result;
1189 if (!hash_component_.regular_expression_.exec(hash, hash_exec_result))
1190 return std::nullopt;
1194 result.protocol = create_component_match_result(protocol_component_, protocol, protocol_exec_result);
1195 result.username = create_component_match_result(username_component_, username, username_exec_result);
1196 result.password = create_component_match_result(password_component_, password, password_exec_result);
1197 result.hostname = create_component_match_result(hostname_component_, hostname, hostname_exec_result);
1198 result.port = create_component_match_result(port_component_, port, port_exec_result);
1199 result.pathname = create_component_match_result(pathname_component_, pathname, pathname_exec_result);
1200 result.search = create_component_match_result(search_component_, search, search_exec_result);
1201 result.hash = create_component_match_result(hash_component_, hash, hash_exec_result);
1208template <
class regex_engine,
typename E>
1211 protocol_component_.has_regexp_groups_ ||
1212 username_component_.has_regexp_groups_ ||
1213 password_component_.has_regexp_groups_ ||
1214 hostname_component_.has_regexp_groups_ ||
1215 port_component_.has_regexp_groups_ ||
1216 pathname_component_.has_regexp_groups_ ||
1217 search_component_.has_regexp_groups_ ||
1218 hash_component_.has_regexp_groups_;
1229template <
class regex_engine>
1230inline component<regex_engine>::component(std::string_view input, encoding_callback encoding_cb,
const options& opt) {
1233 const auto pt_list = parse_pattern_string(input, opt, encoding_cb);
1234 auto [regular_expression_string, name_list] =
1235 generate_regular_expression_and_name_list(pt_list, opt);
1243 if (!regular_expression_.init(regular_expression_string, opt.ignore_case))
1246 pattern_string_ = generate_pattern_string(pt_list, opt);
1247 group_name_list_ = std::move(name_list);
1248 has_regexp_groups_ = std::any_of(pt_list.begin(), pt_list.end(),
1249 [](
const auto& pt) ->
bool {
1250 return pt.type_ == part::type::REGEXP;
1256template <
class regex_engine>
1257inline bool protocol_component_matches_special_scheme(
const component<regex_engine>& protocol_component) {
1258 return [](
const auto& re,
auto... scheme) {
1259 return (... || re.test(scheme));
1260 }(protocol_component.regular_expression_,
1261 "ftp"sv,
"file"sv,
"http"sv,
"https"sv,
"ws"sv,
"wss"sv);
1266constexpr bool hostname_pattern_is_ipv6_address(std::string_view input)
noexcept {
1269 if (input.length() < 2)
1271 return input[0] ==
'[' ||
1272 (input[0] ==
'{' && input[1] ==
'[') ||
1273 (input[0] ==
'\\' && input[1] ==
'[');
1280struct constructor_string_parser {
1296 constructor_string_parser(std::string_view input);
1298 void change_state(state new_state, std::size_t skip);
1300 void rewind_and_set_state(state state);
1301 const token& get_safe_token(std::size_t index)
const;
1302 bool is_non_special_pattern_char(std::size_t index, std::string_view value)
const;
1303 bool is_protocol_suffix()
const;
1304 bool next_is_authority_slashes()
const;
1305 bool is_identity_terminator()
const;
1306 bool is_password_prefix()
const;
1307 bool is_port_prefix()
const;
1308 bool is_pathname_start()
const;
1309 bool is_search_prefix()
const;
1310 bool is_hash_prefix()
const;
1311 bool is_group_open()
const;
1312 bool is_group_close()
const;
1313 bool is_ipv6_open()
const;
1314 bool is_ipv6_close()
const;
1315 std::string_view make_component_string()
const;
1316 template <
class regex_engine>
1317 void compute_protocol_matches_special_scheme_flag();
1319 std::string_view input_;
1320 token_list token_list_;
1321 urlpattern_init result_;
1322 std::size_t component_start_ = 0;
1323 std::size_t token_index_ = 0;
1324 std::size_t token_increment_ = 1;
1325 std::size_t group_depth_ = 0;
1326 std::size_t hostname_ipv6_bracket_depth_ = 0;
1327 bool protocol_matches_special_scheme_flag_ =
false;
1328 state state_ = state::INIT;
1335inline constructor_string_parser::constructor_string_parser(std::string_view input)
1337 , token_list_{ tokenize(input, tokenize_policy::lenient) }
1340template <
class regex_engine>
1341inline urlpattern_init parse_constructor_string(std::string_view input) {
1342 using state = constructor_string_parser::state;
1344 constructor_string_parser parser{ input };
1347 while (parser.token_index_ < parser.token_list_.size()) {
1348 parser.token_increment_ = 1;
1354 if (parser.token_list_[parser.token_index_].type_ == token::type::END) {
1355 if (parser.state_ == state::INIT) {
1366 if (parser.is_hash_prefix()) {
1367 parser.change_state(state::HASH, 1);
1368 }
else if (parser.is_search_prefix()) {
1369 parser.change_state(state::SEARCH, 1);
1371 parser.change_state(state::PATHNAME, 0);
1373 parser.token_index_ += parser.token_increment_;
1377 if (parser.state_ == state::AUTHORITY) {
1381 parser.rewind_and_set_state(state::HOSTNAME);
1382 parser.token_index_ += parser.token_increment_;
1386 parser.change_state(state::DONE, 0);
1390 if (parser.is_group_open()) {
1399 ++parser.group_depth_;
1400 parser.token_index_ += parser.token_increment_;
1404 if (parser.group_depth_ > 0) {
1405 if (parser.is_group_close()) {
1406 --parser.group_depth_;
1408 parser.token_index_ += parser.token_increment_;
1413 switch (parser.state_) {
1415 if (parser.is_protocol_suffix())
1416 parser.rewind_and_set_state(state::PROTOCOL);
1418 case state::PROTOCOL:
1419 if (parser.is_protocol_suffix()) {
1420 parser.compute_protocol_matches_special_scheme_flag<regex_engine>();
1428 state next_state = state::PATHNAME;
1429 std::size_t skip = 1;
1430 if (parser.next_is_authority_slashes()) {
1431 next_state = state::AUTHORITY;
1433 }
else if (parser.protocol_matches_special_scheme_flag_) {
1434 next_state = state::AUTHORITY;
1436 parser.change_state(next_state, skip);
1439 case state::AUTHORITY:
1440 if (parser.is_identity_terminator())
1441 parser.rewind_and_set_state(state::USERNAME);
1442 else if (parser.is_pathname_start() || parser.is_search_prefix() || parser.is_hash_prefix())
1443 parser.rewind_and_set_state(state::HOSTNAME);
1445 case state::USERNAME:
1446 if (parser.is_password_prefix())
1447 parser.change_state(state::PASSWORD, 1);
1448 else if (parser.is_identity_terminator())
1449 parser.change_state(state::HOSTNAME, 1);
1451 case state::PASSWORD:
1452 if (parser.is_identity_terminator())
1453 parser.change_state(state::HOSTNAME, 1);
1455 case state::HOSTNAME:
1456 if (parser.is_ipv6_open())
1457 ++parser.hostname_ipv6_bracket_depth_;
1458 else if (parser.is_ipv6_close())
1459 --parser.hostname_ipv6_bracket_depth_;
1460 else if (parser.is_port_prefix() && parser.hostname_ipv6_bracket_depth_ == 0)
1461 parser.change_state(state::PORT, 1);
1462 else if (parser.is_pathname_start())
1463 parser.change_state(state::PATHNAME, 0);
1464 else if (parser.is_search_prefix())
1465 parser.change_state(state::SEARCH, 1);
1466 else if (parser.is_hash_prefix())
1467 parser.change_state(state::HASH, 1);
1470 if (parser.is_pathname_start())
1471 parser.change_state(state::PATHNAME, 0);
1472 else if (parser.is_search_prefix())
1473 parser.change_state(state::SEARCH, 1);
1474 else if (parser.is_hash_prefix())
1475 parser.change_state(state::HASH, 1);
1477 case state::PATHNAME:
1478 if (parser.is_search_prefix())
1479 parser.change_state(state::SEARCH, 1);
1480 else if (parser.is_hash_prefix())
1481 parser.change_state(state::HASH, 1);
1484 if (parser.is_hash_prefix())
1485 parser.change_state(state::HASH, 1);
1493 parser.token_index_ += parser.token_increment_;
1498 if (parser.result_.hostname && !parser.result_.port)
1499 parser.result_.port =
""sv;
1506 return std::move(parser.result_);
1512inline void constructor_string_parser::change_state(state new_state, std::size_t skip) {
1518 case state::PROTOCOL: result_.protocol = make_component_string();
break;
1519 case state::USERNAME: result_.username = make_component_string();
break;
1520 case state::PASSWORD: result_.password = make_component_string();
break;
1521 case state::HOSTNAME: result_.hostname = make_component_string();
break;
1522 case state::PORT: result_.port = make_component_string();
break;
1523 case state::PATHNAME: result_.pathname = make_component_string();
break;
1524 case state::SEARCH: result_.search = make_component_string();
break;
1525 case state::HASH: result_.hash = make_component_string();
break;
1530 if (state_ != state::INIT && new_state != state::DONE) {
1531 if (state_ >= state::PROTOCOL && state_ <= state::PASSWORD &&
1532 new_state >= state::PORT && new_state <= state::HASH &&
1533 !result_.hostname) {
1534 result_.hostname =
""sv;
1536 if (state_ >= state::PROTOCOL && state_ <= state::PORT &&
1537 (new_state == state::SEARCH || new_state == state::HASH) &&
1538 !result_.pathname) {
1539 result_.pathname = protocol_matches_special_scheme_flag_ ?
"/"sv :
""sv;
1541 if (state_ >= state::PROTOCOL && state_ <= state::PATHNAME &&
1542 new_state == state::HASH &&
1544 result_.search =
""sv;
1549 token_index_ += skip;
1550 component_start_ = token_index_;
1551 token_increment_ = 0;
1555inline void constructor_string_parser::rewind() {
1556 token_index_ = component_start_;
1557 token_increment_ = 0;
1561inline void constructor_string_parser::rewind_and_set_state(state state) {
1567inline const token& constructor_string_parser::get_safe_token(std::size_t index)
const {
1568 if (index < token_list_.size())
1569 return token_list_[index];
1571 assert(!token_list_.empty());
1572 const auto last_index = token_list_.size() - 1;
1573 assert(token_list_[last_index].type_ == token::type::END);
1574 return token_list_[last_index];
1578inline bool constructor_string_parser::is_non_special_pattern_char(std::size_t index, std::string_view value)
const {
1579 const token& tok = get_safe_token(index);
1580 if (tok.value_ != value)
1583 tok.type_ == token::type::CHAR ||
1584 tok.type_ == token::type::ESCAPED_CHAR ||
1585 tok.type_ == token::type::INVALID_CHAR;
1589inline bool constructor_string_parser::is_protocol_suffix()
const {
1590 return is_non_special_pattern_char(token_index_,
":"sv);
1594inline bool constructor_string_parser::next_is_authority_slashes()
const {
1596 is_non_special_pattern_char(token_index_ + 1,
"/"sv) &&
1597 is_non_special_pattern_char(token_index_ + 2,
"/"sv);
1601inline bool constructor_string_parser::is_identity_terminator()
const {
1602 return is_non_special_pattern_char(token_index_,
"@"sv);
1606inline bool constructor_string_parser::is_password_prefix()
const {
1607 return is_non_special_pattern_char(token_index_,
":"sv);
1611inline bool constructor_string_parser::is_port_prefix()
const {
1612 return is_non_special_pattern_char(token_index_,
":"sv);
1616inline bool constructor_string_parser::is_pathname_start()
const {
1617 return is_non_special_pattern_char(token_index_,
"/"sv);
1621inline bool constructor_string_parser::is_search_prefix()
const {
1622 if (is_non_special_pattern_char(token_index_,
"?"sv))
1625 if (token_list_[token_index_].value_ !=
"?"sv)
1630 if (token_index_ < 1)
1632 const token& previous_token = get_safe_token(token_index_ - 1);
1634 previous_token.type_ != token::type::NAME &&
1635 previous_token.type_ != token::type::REGEXP &&
1636 previous_token.type_ != token::type::CLOSE &&
1637 previous_token.type_ != token::type::ASTERISK;
1641inline bool constructor_string_parser::is_hash_prefix()
const {
1642 return is_non_special_pattern_char(token_index_,
"#"sv);
1646inline bool constructor_string_parser::is_group_open()
const {
1648 return token_list_[token_index_].type_ == token::type::OPEN;
1652inline bool constructor_string_parser::is_group_close()
const {
1654 return token_list_[token_index_].type_ == token::type::CLOSE;
1658inline bool constructor_string_parser::is_ipv6_open()
const {
1659 return is_non_special_pattern_char(token_index_,
"["sv);
1663inline bool constructor_string_parser::is_ipv6_close()
const {
1664 return is_non_special_pattern_char(token_index_,
"]"sv);
1668inline std::string_view constructor_string_parser::make_component_string()
const {
1669 assert(token_index_ < token_list_.size());
1670 const token& tok = token_list_[token_index_];
1671 const token& component_start_token = get_safe_token(component_start_);
1672 const auto component_start_input_index = component_start_token.index_;
1673 const auto end_index = tok.index_;
1674 return input_.substr(component_start_input_index, end_index - component_start_input_index);
1678template <
class regex_engine>
1679inline void constructor_string_parser::compute_protocol_matches_special_scheme_flag() {
1680 const auto protocol_string = make_component_string();
1681 const component<regex_engine> protocol_component{ protocol_string, canonicalize_protocol, default_options };
1682 if (protocol_component_matches_special_scheme(protocol_component))
1683 protocol_matches_special_scheme_flag_ =
true;
1691 UPA_CONSTEXPR_20 tokenizer() =
default;
1692 UPA_CONSTEXPR_20 tokenizer(std::string_view input, tokenize_policy policy)
1693 : input_{ input }, policy_{ policy } {}
1696 UPA_CONSTEXPR_20
void get_the_next_code_point() {
1697 code_point_ = get_code_point(input_, next_index_);
1701 UPA_CONSTEXPR_20
void seek_and_get_the_next_code_point(std::size_t index) {
1702 next_index_ = index;
1703 get_the_next_code_point();
1707 UPA_CONSTEXPR_20
void add_token(token::type type, std::size_t next_pos, std::size_t value_pos, std::size_t value_len) {
1708 token_list_.push_back({ type, index_, input_.substr(value_pos, value_len) });
1712 UPA_CONSTEXPR_20
void add_token_with_default_length(token::type type, std::size_t next_pos, std::size_t value_pos) {
1713 add_token(type, next_pos, value_pos, next_pos - value_pos);
1716 UPA_CONSTEXPR_20
void add_token_with_default_position_and_length(token::type type) {
1717 add_token_with_default_length(type, next_index_, index_);
1721 UPA_CONSTEXPR_20
void process_tokenizing_error(std::size_t next_pos, std::size_t value_pos) {
1722 if (policy_ == tokenize_policy::strict) {
1723 throw urlpattern_error(
"tokenizing error");
1725 assert(policy_ == tokenize_policy::lenient);
1726 add_token_with_default_length(token::type::INVALID_CHAR, next_pos, value_pos);
1730 std::string_view input_;
1731 tokenize_policy policy_ = tokenize_policy::strict;
1732 token_list token_list_;
1733 std::size_t index_ = 0;
1734 std::size_t next_index_ = 0;
1737 char32_t code_point_ = 0;
1741inline bool is_valid_name_code_point(
char32_t code_point,
bool first)
noexcept {
1743 ? table::is_identifier_start(code_point)
1744 : table::is_identifier_part(code_point);
1748constexpr bool is_ascii(
char32_t code_point)
noexcept {
1749 return code_point <= 0x7F;
1753inline token_list tokenize(std::string_view input, tokenize_policy policy) {
1754 tokenizer tokenizer{ input, policy };
1756 while (tokenizer.index_ < input.length()) {
1757 tokenizer.seek_and_get_the_next_code_point(tokenizer.index_);
1759 switch(tokenizer.code_point_) {
1761 tokenizer.add_token_with_default_position_and_length(token::type::ASTERISK);
1765 tokenizer.add_token_with_default_position_and_length(token::type::OTHER_MODIFIER);
1768 if (tokenizer.index_ == input.length() - 1) {
1769 tokenizer.process_tokenizing_error(tokenizer.next_index_, tokenizer.index_);
1772 const auto escaped_index = tokenizer.next_index_;
1773 tokenizer.get_the_next_code_point();
1774 tokenizer.add_token_with_default_length(token::type::ESCAPED_CHAR,tokenizer.next_index_,escaped_index);
1778 tokenizer.add_token_with_default_position_and_length(token::type::OPEN);
1781 tokenizer.add_token_with_default_position_and_length(token::type::CLOSE);
1784 auto name_pos = tokenizer.next_index_;
1785 const auto name_start = name_pos;
1786 while (name_pos < input.length()) {
1787 tokenizer.seek_and_get_the_next_code_point(name_pos);
1788 const bool first_code_point = name_pos == name_start;
1789 if (!is_valid_name_code_point(tokenizer.code_point_, first_code_point))
1791 name_pos = tokenizer.next_index_;
1793 if (name_pos <= name_start) {
1794 tokenizer.process_tokenizing_error(name_start, tokenizer.index_);
1797 tokenizer.add_token_with_default_length(token::type::NAME, name_pos, name_start);
1801 std::size_t depth = 1;
1802 auto regexp_pos = tokenizer.next_index_;
1803 const auto regexp_start = regexp_pos;
1806 while (regexp_pos < input.length()) {
1807 tokenizer.seek_and_get_the_next_code_point(regexp_pos);
1808 if (!is_ascii(tokenizer.code_point_)) {
1809 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1813 if (regexp_pos == regexp_start && tokenizer.code_point_ ==
'?') {
1814 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1818 if (tokenizer.code_point_ ==
'\\') {
1819 if (regexp_pos == input.length() - 1) {
1820 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1824 tokenizer.get_the_next_code_point();
1825 if (!is_ascii(tokenizer.code_point_)) {
1826 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1830 regexp_pos = tokenizer.next_index_;
1833 if (tokenizer.code_point_ ==
')') {
1835 regexp_pos = tokenizer.next_index_;
1838 }
else if (tokenizer.code_point_ ==
'(') {
1840 if (regexp_pos == input.length() - 1) {
1841 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1845 const auto temporary_pos = tokenizer.next_index_;
1846 tokenizer.get_the_next_code_point();
1847 if (tokenizer.code_point_ !=
'?') {
1848 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1852 tokenizer.next_index_ = temporary_pos;
1854 regexp_pos = tokenizer.next_index_;
1859 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1862 const auto regexp_len = regexp_pos - regexp_start - 1;
1863 if (regexp_len == 0) {
1864 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1867 tokenizer.add_token(token::type::REGEXP, regexp_pos, regexp_start, regexp_len);
1871 tokenizer.add_token_with_default_position_and_length(token::type::CHAR);
1874 tokenizer.add_token_with_default_length(token::type::END, tokenizer.index_, tokenizer.index_);
1876 return std::move(tokenizer.token_list_);
1884struct pattern_parser {
1885 UPA_CONSTEXPR_20 pattern_parser(encoding_callback encoding_cb, std::string_view segment_wildcard_regexp)
1886 : encoding_cb_(encoding_cb)
1887 , segment_wildcard_regexp_(segment_wildcard_regexp)
1890 UPA_CONSTEXPR_20
const token* try_consume_token(token::type type);
1891 UPA_CONSTEXPR_20
const token* try_consume_modifier_token();
1892 UPA_CONSTEXPR_20
const token* try_consume_regexp_or_wildcard_token(
const token* pname_token);
1893 UPA_CONSTEXPR_20
const token& consume_required_token(token::type type);
1894 UPA_CONSTEXPR_20 std::string consume_text();
1895 void maybe_add_part_from_pending_fixed_value();
1896 void add_part(std::string_view prefix,
const token* pname_token,
const token* pregexp_or_wildcard_token,
1897 std::string_view suffix,
const token* pmodifier_token);
1898 UPA_CONSTEXPR_20
bool is_duplicate_name(std::string_view name)
const noexcept;
1902 token_list token_list_;
1903 encoding_callback encoding_cb_ =
nullptr;
1904 std::string segment_wildcard_regexp_;
1905 part_list part_list_;
1906 std::string pending_fixed_value_;
1907 std::size_t index_ = 0;
1908 std::size_t next_numeric_name_ = 0;
1912inline part_list parse_pattern_string(std::string_view input,
const options& opt, encoding_callback encoding_cb)
1914 pattern_parser parser{ encoding_cb, generate_segment_wildcard_regexp(opt) };
1915 parser.token_list_ = tokenize(input, tokenize_policy::strict);
1917 while (parser.index_ < parser.token_list_.size()) {
1922 const auto* pchar_token = parser.try_consume_token(token::type::CHAR);
1923 const auto* pname_token = parser.try_consume_token(token::type::NAME);
1924 const auto* pregexp_or_wildcard_token = parser.try_consume_regexp_or_wildcard_token(pname_token);
1926 if (pname_token || pregexp_or_wildcard_token) {
1929 std::string_view prefix{};
1931 prefix = pchar_token->value_;
1932 if (!prefix.empty() && prefix != opt.prefix_code_point) {
1933 parser.pending_fixed_value_.append(prefix);
1936 parser.maybe_add_part_from_pending_fixed_value();
1937 const auto* pmodifier_token = parser.try_consume_modifier_token();
1938 parser.add_part(prefix, pname_token, pregexp_or_wildcard_token, {}, pmodifier_token);
1942 const auto* pfixed_token = pchar_token;
1946 if (pfixed_token ==
nullptr)
1947 pfixed_token = parser.try_consume_token(token::type::ESCAPED_CHAR);
1949 parser.pending_fixed_value_.append(pfixed_token->value_);
1953 const auto* popen_token = parser.try_consume_token(token::type::OPEN);
1960 auto prefix = parser.consume_text();
1961 pname_token = parser.try_consume_token(token::type::NAME);
1962 pregexp_or_wildcard_token = parser.try_consume_regexp_or_wildcard_token(pname_token);
1963 auto suffix = parser.consume_text();
1964 parser.consume_required_token(token::type::CLOSE);
1965 const auto* pmodifier_token = parser.try_consume_modifier_token();
1966 parser.add_part(prefix, pname_token, pregexp_or_wildcard_token, suffix, pmodifier_token);
1970 parser.maybe_add_part_from_pending_fixed_value();
1971 parser.consume_required_token(token::type::END);
1974 return std::move(parser.part_list_);
1978UPA_CONSTEXPR_20 std::string generate_segment_wildcard_regexp(
const options& opt) {
1979 std::string result{
"[^" };
1980 append_escape_regexp_string(result, opt.delimiter_code_point);
1981 result.append(
"]+?");
1986UPA_CONSTEXPR_20
const token* pattern_parser::try_consume_token(token::type type) {
1988 assert(index_ < token_list_.size());
1990 const auto& next_token = token_list_[index_];
1991 if (next_token.type_ != type)
1998UPA_CONSTEXPR_20
const token* pattern_parser::try_consume_modifier_token() {
1999 const auto* ptoken = try_consume_token(token::type::OTHER_MODIFIER);
2002 return try_consume_token(token::type::ASTERISK);
2006UPA_CONSTEXPR_20
const token* pattern_parser::try_consume_regexp_or_wildcard_token(
const token* pname_token) {
2007 const auto* ptoken = try_consume_token(token::type::REGEXP);
2008 if (pname_token ==
nullptr && ptoken ==
nullptr)
2009 return try_consume_token(token::type::ASTERISK);
2014UPA_CONSTEXPR_20
const token& pattern_parser::consume_required_token(token::type type) {
2015 const auto* ptoken = try_consume_token(type);
2016 if (ptoken ==
nullptr) {
2023UPA_CONSTEXPR_20 std::string pattern_parser::consume_text() {
2026 const auto* ptoken = try_consume_token(token::type::CHAR);
2027 if (ptoken ==
nullptr) {
2028 ptoken = try_consume_token(token::type::ESCAPED_CHAR);
2029 if (ptoken ==
nullptr)
break;
2031 result.append(ptoken->value_);
2037inline void pattern_parser::maybe_add_part_from_pending_fixed_value() {
2038 if (pending_fixed_value_.empty())
2040 auto encoded_value = encoding_cb_(pending_fixed_value_);
2041 pending_fixed_value_.clear();
2042 part_list_.emplace_back(part::type::FIXED_TEXT, std::move(encoded_value), part::modifier::none);
2046inline void pattern_parser::add_part(std::string_view prefix,
const token* pname_token,
2047 const token* pregexp_or_wildcard_token, std::string_view suffix,
2048 const token* pmodifier_token)
2050 part::modifier modifier = part::modifier::none;
2051 if (pmodifier_token) {
2052 if (pmodifier_token->value_ ==
"?"sv)
2053 modifier = part::modifier::optional;
2054 else if (pmodifier_token->value_ ==
"*"sv)
2055 modifier = part::modifier::zero_or_more;
2056 else if (pmodifier_token->value_ ==
"+"sv)
2057 modifier = part::modifier::one_or_more;
2059 if (pname_token ==
nullptr && pregexp_or_wildcard_token ==
nullptr && modifier == part::modifier::none) {
2063 pending_fixed_value_.append(prefix);
2066 maybe_add_part_from_pending_fixed_value();
2067 if (pname_token ==
nullptr && pregexp_or_wildcard_token ==
nullptr) {
2071 assert(suffix.empty());
2074 auto encoded_value = encoding_cb_(prefix);
2075 part_list_.emplace_back(part::type::FIXED_TEXT, std::move(encoded_value), modifier);
2079 std::string_view regexp_value{};
2083 if (pregexp_or_wildcard_token ==
nullptr)
2084 regexp_value = segment_wildcard_regexp_;
2085 else if (pregexp_or_wildcard_token->type_ == token::type::ASTERISK)
2086 regexp_value = full_wildcard_regexp_value;
2088 regexp_value = pregexp_or_wildcard_token->value_;
2090 part::type type = part::type::REGEXP;
2096 if (regexp_value == segment_wildcard_regexp_) {
2097 type = part::type::SEGMENT_WILDCARD;
2098 regexp_value =
""sv;
2099 }
else if (regexp_value == full_wildcard_regexp_value) {
2100 type = part::type::FULL_WILDCARD;
2101 regexp_value =
""sv;
2110 name = pname_token->value_;
2111 }
else if (pregexp_or_wildcard_token) {
2112 name = std::to_string(next_numeric_name_);
2113 ++next_numeric_name_;
2115 if (is_duplicate_name(name))
2120 part pt(type, std::string{ regexp_value }, modifier);
2121 pt.name_ = std::move(name);
2122 pt.prefix_ = encoding_cb_(prefix);
2123 pt.suffix_ = encoding_cb_(suffix);
2124 part_list_.push_back(std::move(pt));
2128UPA_CONSTEXPR_20
bool pattern_parser::is_duplicate_name(std::string_view name)
const noexcept {
2129 return std::any_of(part_list_.begin(), part_list_.end(), [&name](
const part& pt) {
2130 return pt.name_ == name;
2138UPA_CONSTEXPR_20 std::pair<std::string, string_list> generate_regular_expression_and_name_list(
2139 const part_list& pt_list,
const options& opt)
2141 std::string result{
"^" };
2142 string_list name_list{};
2146 for (
const auto& pt : pt_list) {
2147 if (pt.type_ == part::type::FIXED_TEXT) {
2148 if (pt.modifier_ == part::modifier::none) {
2149 append_escape_regexp_string(result, pt.value_);
2155 result.append(
"(?:");
2156 append_escape_regexp_string(result, pt.value_);
2157 result.push_back(
')');
2158 append_convert_modifier_to_string(result, pt.modifier_);
2163 assert(!pt.name_.empty());
2164 name_list.emplace_back(pt.name_);
2172 std::string_view regexp_value{ pt.value_ };
2173 std::string regexp_value_buffer;
2174 if (pt.type_ == part::type::SEGMENT_WILDCARD) {
2175 regexp_value_buffer = generate_segment_wildcard_regexp(opt);
2176 regexp_value = regexp_value_buffer;
2177 }
else if (pt.type_ == part::type::FULL_WILDCARD) {
2178 regexp_value = full_wildcard_regexp_value;
2181 if (pt.prefix_.empty() && pt.suffix_.empty()) {
2189 if (pt.modifier_ == part::modifier::none || pt.modifier_ == part::modifier::optional) {
2190 result.push_back(
'(');
2191 result.append(regexp_value);
2192 result.push_back(
')');
2193 append_convert_modifier_to_string(result, pt.modifier_);
2195 result.append(
"((?:");
2196 result.append(regexp_value);
2197 result.push_back(
')');
2198 append_convert_modifier_to_string(result, pt.modifier_);
2199 result.push_back(
')');
2204 if (pt.modifier_ == part::modifier::none || pt.modifier_ == part::modifier::optional) {
2211 result.append(
"(?:");
2212 append_escape_regexp_string(result, pt.prefix_);
2213 result.push_back(
'(');
2214 result.append(regexp_value);
2215 result.push_back(
')');
2216 append_escape_regexp_string(result, pt.suffix_);
2217 result.push_back(
')');
2218 append_convert_modifier_to_string(result, pt.modifier_);
2222 assert(pt.modifier_ == part::modifier::zero_or_more || pt.modifier_ == part::modifier::one_or_more);
2223 assert(!pt.prefix_.empty() || !pt.suffix_.empty());
2231 result.append(
"(?:");
2232 append_escape_regexp_string(result, pt.prefix_);
2233 result.append(
"((?:");
2234 result.append(regexp_value);
2235 result.append(
")(?:");
2236 append_escape_regexp_string(result, pt.suffix_);
2237 append_escape_regexp_string(result, pt.prefix_);
2238 result.append(
"(?:");
2239 result.append(regexp_value);
2240 result.append(
"))*)");
2241 append_escape_regexp_string(result, pt.suffix_);
2242 result.push_back(
')');
2243 if (pt.modifier_ == part::modifier::zero_or_more)
2244 result.push_back(
'?');
2247 result.push_back(
'$');
2249 return { result, name_list };
2253inline constexpr upa::code_point_set escape_regexp_set{ [](upa::code_point_set& self)
constexpr {
2256 0x2E, 0x2B, 0x2A, 0x3F, 0x5E, 0x24, 0x7B, 0x7D,
2258 0x28, 0x29, 0x5B, 0x5D, 0x7C, 0x2F, 0x5C });
2261UPA_CONSTEXPR_20
void append_escape_regexp_string(std::string& result, std::string_view input) {
2264 for (
const auto c : input) {
2265 if (escape_regexp_set[c])
2266 result.push_back(
'\\');
2267 result.push_back(c);
2276inline std::string generate_pattern_string(
const part_list& pt_list,
const options& opt) {
2279 for (std::size_t index = 0; index < pt_list.size(); ++index) {
2280 const auto& pt = pt_list[index];
2282 if (pt.type_ == part::type::FIXED_TEXT) {
2283 if (pt.modifier_ == part::modifier::none) {
2284 append_escape_pattern_string(result, pt.value_);
2287 result.push_back(
'{');
2288 append_escape_pattern_string(result, pt.value_);
2289 result.push_back(
'}');
2290 append_convert_modifier_to_string(result, pt.modifier_);
2294 const auto* pprevious_pt = index > 0 ? &pt_list[index - 1] :
nullptr;
2295 const auto* pnext_pt = index < pt_list.size() - 1 ? &pt_list[index + 1] :
nullptr;
2297 assert(!pt.name_.empty());
2298 const bool custom_name = !upa::detail::is_ascii_digit(pt.name_[0]);
2299 bool needs_grouping = !pt.suffix_.empty() ||
2300 (!pt.prefix_.empty() && pt.prefix_ != opt.prefix_code_point);
2302 if (!needs_grouping && custom_name &&
2303 pt.type_ == part::type::SEGMENT_WILDCARD && pt.modifier_ == part::modifier::none &&
2304 pnext_pt !=
nullptr && pnext_pt->prefix_.empty() && pnext_pt->suffix_.empty())
2306 if (pnext_pt->type_ == part::type::FIXED_TEXT)
2307 needs_grouping = is_valid_name_code_point(get_code_point(pnext_pt->value_),
false);
2309 needs_grouping = upa::detail::is_ascii_digit(pnext_pt->name_[0]);
2311 if (!needs_grouping && pt.prefix_.empty() &&
2312 pprevious_pt !=
nullptr && pprevious_pt->type_ == part::type::FIXED_TEXT &&
2314 !opt.prefix_code_point.empty() && !pprevious_pt->value_.empty() &&
2316 pprevious_pt->value_.back() == opt.prefix_code_point[0])
2317 needs_grouping =
true;
2319 assert( !pt.name_.empty());
2322 result.push_back(
'{');
2324 append_escape_pattern_string(result, pt.prefix_);
2326 result.push_back(
':');
2327 result.append(pt.name_);
2331 case part::type::REGEXP:
2332 result.push_back(
'(');
2333 result.append(pt.value_);
2334 result.push_back(
')');
2336 case part::type::SEGMENT_WILDCARD:
2339 result.push_back(
'(');
2341 result.append(generate_segment_wildcard_regexp(opt));
2342 result.push_back(
')');
2345 else if (!pt.suffix_.empty() && is_valid_name_code_point(get_code_point(pt.suffix_),
false)) {
2346 result.push_back(
'\\');
2349 case part::type::FULL_WILDCARD:
2350 if (!custom_name && (
2351 pprevious_pt ==
nullptr ||
2352 pprevious_pt->type_ == part::type::FIXED_TEXT ||
2353 pprevious_pt->modifier_ != part::modifier::none ||
2355 !pt.prefix_.empty())) {
2356 result.push_back(
'*');
2358 result.push_back(
'(');
2359 result.append(full_wildcard_regexp_value);
2360 result.push_back(
')');
2369 append_escape_pattern_string(result, pt.suffix_);
2372 result.push_back(
'}');
2374 append_convert_modifier_to_string(result, pt.modifier_);
2381inline constexpr upa::code_point_set escape_pattern_set{ [](upa::code_point_set& self)
constexpr {
2384 0x2B, 0x2A, 0x3F, 0x3A, 0x7B, 0x7D, 0x28, 0x29, 0x5C });
2387UPA_CONSTEXPR_20 std::string escape_pattern_string(std::string_view input) {
2389 append_escape_pattern_string(result, input);
2393UPA_CONSTEXPR_20
void append_escape_pattern_string(std::string& result, std::string_view input) {
2396 for (
const auto c : input) {
2397 if (escape_pattern_set[c])
2398 result.push_back(
'\\');
2399 result.push_back(c);
2404UPA_CONSTEXPR_20
void append_convert_modifier_to_string(std::string& result, part::modifier modifier) {
2406 case part::modifier::zero_or_more:
2407 result.push_back(
'*');
2409 case part::modifier::optional:
2410 result.push_back(
'?');
2412 case part::modifier::one_or_more:
2413 result.push_back(
'+');
2429inline std::string canonicalize_protocol(std::string_view value) {
2430 if (value.empty())
return {};
2436 std::string inp(value);
2437 inp.append(
"://h/");
2448inline std::string canonicalize_username(std::string_view value) {
2449 if (value.empty())
return {};
2457inline std::string canonicalize_password(std::string_view value) {
2458 return canonicalize_username(value);
2462inline std::string canonicalize_hostname(std::string_view value) {
2463 if (value.empty())
return {};
2468 upa::url dummy_url{};
2470 upa::detail::url_serializer urls(dummy_url);
2471 urls.set_scheme(
"https");
2473 const auto inp = upa::make_str_arg(value);
2474 const auto parse_result = upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(),
nullptr,
2475 upa::detail::url_parser::hostname_state);
2488inline std::string canonicalize_ipv6_hostname(std::string_view value) {
2492 for (
const auto cp : value) {
2493 if (!upa::detail::is_hex_char(cp) && cp !=
'[' && cp !=
']' && cp !=
':')
2495 result.push_back(upa::util::ascii_to_lower_char(cp));
2501inline std::string canonicalize_port(std::string_view port_value, std::optional<std::string_view> protocol_value) {
2502 if (port_value.empty())
return {};
2508 upa::url dummy_url{};
2510 upa::detail::url_serializer urls(dummy_url);
2512 urls.set_scheme(*protocol_value);
2514 urls.set_scheme(
"");
2519 urls.hostStart().push_back(
'h');
2522 const auto inp = upa::make_str_arg(port_value);
2523 const auto parse_result = upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(),
nullptr,
2524 upa::detail::url_parser::port_state);
2532inline std::string canonicalize_pathname(std::string_view value) {
2533 if (value.empty())
return {};
2537 const bool leading_slash = value[0] ==
'/';
2539 upa::url dummy_url{};
2541 std::string modified_value;
2542 const auto inp = [&]() {
2544 return upa::make_str_arg(value);
2546 modified_value =
"/-"sv;
2548 modified_value.append(value);
2561 return upa::make_str_arg(modified_value);
2566 upa::detail::url_serializer urls(dummy_url);
2567 urls.set_scheme(
"https");
2576 upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(),
nullptr,
2577 upa::detail::url_parser::path_start_state);
2582 if (!leading_slash) {
2585 if (result.length() <= 2)
2587 result.remove_prefix(2);
2589 return std::string{ result };
2593inline std::string canonicalize_opaque_pathname(std::string_view value) {
2594 if (value.empty())
return {};
2600 upa::url dummy_url{};
2602 upa::detail::url_serializer urls(dummy_url);
2605 urls.set_has_opaque_path();
2607 const auto inp = upa::make_str_arg(value);
2608 const auto parse_result = upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(),
nullptr,
2609 upa::detail::url_parser::opaque_path_state);
2617inline std::string canonicalize_search(std::string_view value) {
2618 if (value.empty())
return {};
2623 upa::url dummy_url{};
2625 upa::detail::url_serializer urls(dummy_url);
2628 urls.set_scheme(
"https");
2630 urls.hostStart().push_back(
'h');
2636 const auto inp = upa::make_str_arg(value);
2637 upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(),
nullptr,
2638 upa::detail::url_parser::query_state);
2644inline std::string canonicalize_hash(std::string_view value) {
2645 if (value.empty())
return {};
2650 upa::url dummy_url{};
2652 upa::detail::url_serializer urls(dummy_url);
2663 const auto inp = upa::make_str_arg(value);
2664 upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(),
nullptr,
2665 upa::detail::url_parser::fragment_state);
2674UPA_CONSTEXPR_20 std::string process_base_url_string(std::string_view input, urlpattern_init_type type);
2676constexpr bool is_absolute_pathname(std::string_view input, urlpattern_init_type type)
noexcept;
2677inline std::string process_protocol_for_init(std::string_view value, urlpattern_init_type type);
2678inline std::string process_username_for_init(std::string_view value, urlpattern_init_type type);
2679inline std::string process_password_for_init(std::string_view value, urlpattern_init_type type);
2680inline std::string process_hostname_for_init(std::string_view value, urlpattern_init_type type);
2681inline std::string process_port_for_init(std::string_view port_value,
2682 std::string_view protocol_value, urlpattern_init_type type);
2683inline std::string process_pathname_for_init(std::string_view pathname_value,
2684 std::string_view protocol_value, urlpattern_init_type type);
2685inline std::string process_search_for_init(std::string_view value, urlpattern_init_type type);
2686inline std::string process_hash_for_init(std::string_view value, urlpattern_init_type type);
2693 result.protocol =
""sv;
2694 result.username =
""sv;
2695 result.password =
""sv;
2696 result.hostname =
""sv;
2698 result.pathname =
""sv;
2699 result.search =
""sv;
2703 std::optional<upa::url> base_url = std::nullopt;
2704 if (init.base_url) {
2707 if (!
upa::success(base_url->parse(*init.base_url,
nullptr)))
2711 result.protocol = process_base_url_string(base_url->get_part_view(
upa::url::SCHEME), type);
2712 if (type != urlpattern_init_type::PATTERN &&
2713 !init.protocol && !init.hostname && !init.port && !init.username) {
2714 result.username = process_base_url_string(base_url->get_part_view(
upa::url::USERNAME), type);
2716 result.password = process_base_url_string(base_url->get_part_view(
upa::url::PASSWORD), type);
2718 if (!init.protocol && !init.hostname) {
2719 result.hostname = process_base_url_string(base_url->get_part_view(
upa::url::HOST), type);
2721 result.port = process_base_url_string(base_url->get_part_view(
upa::url::PORT), type);
2722 if (!init.pathname) {
2723 result.pathname = process_base_url_string(base_url->get_part_view(
upa::url::PATH), type);
2725 result.search = process_base_url_string(base_url->get_part_view(
upa::url::QUERY), type);
2727 result.hash = process_base_url_string(base_url->get_part_view(
upa::url::FRAGMENT), type);
2735 result.
protocol = process_protocol_for_init(*init.protocol, type);
2737 result.username = process_username_for_init(*init.username, type);
2739 result.password = process_password_for_init(*init.password, type);
2741 result.hostname = process_hostname_for_init(*init.hostname, type);
2743 const std::string_view result_protocol_string = result.protocol ? *result.protocol :
""sv;
2745 result.port = process_port_for_init(*init.port, result_protocol_string, type);
2746 if (init.pathname) {
2747 std::string new_pathname;
2750 std::string_view result_pathname = *init.pathname;
2756 if (base_url && !base_url->has_opaque_path() &&
2757 !is_absolute_pathname(result_pathname, type))
2759 const auto base_url_path = process_base_url_string(base_url->get_part_view(
upa::url::PATH), type);
2762 const auto slash_index = base_url_path.rfind(
'/');
2764 if (slash_index !=
decltype(base_url_path)::npos) {
2765 new_pathname = base_url_path.substr(0, slash_index + 1);
2766 new_pathname.append(result_pathname);
2767 result_pathname = new_pathname;
2770 result.pathname = process_pathname_for_init(result_pathname, result_protocol_string, type);
2773 result.search = process_search_for_init(*init.search, type);
2775 result.hash = process_hash_for_init(*init.hash, type);
2781UPA_CONSTEXPR_20 std::string process_base_url_string(std::string_view input, urlpattern_init_type type) {
2782 if (input.empty())
return {};
2783 if (type != urlpattern_init_type::PATTERN)
2784 return std::string(input);
2785 return escape_pattern_string(input);
2789constexpr bool is_absolute_pathname(std::string_view input, urlpattern_init_type type)
noexcept {
2790 if (input.empty())
return false;
2792 if (input[0] ==
'/')
2794 if (type == urlpattern_init_type::URL)
2798 if (input.length() < 2)
2800 if (input[0] ==
'\\' && input[1] ==
'/')
2802 if (input[0] ==
'{' && input[1] ==
'/')
2808inline std::string process_protocol_for_init(std::string_view value, urlpattern_init_type type) {
2810 const std::string_view stripped_value =
2811 (!value.empty() && value.back() ==
':')
2812 ? value.substr(0, value.length() - 1)
2814 if (type == urlpattern_init_type::PATTERN)
2815 return std::string{ stripped_value };
2816 return canonicalize_protocol(stripped_value);
2820inline std::string process_username_for_init(std::string_view value, urlpattern_init_type type) {
2821 if (type == urlpattern_init_type::PATTERN)
2822 return std::string{ value };
2823 return canonicalize_username(value);
2827inline std::string process_password_for_init(std::string_view value, urlpattern_init_type type) {
2828 if (type == urlpattern_init_type::PATTERN)
2829 return std::string{ value };
2830 return canonicalize_password(value);
2834inline std::string process_hostname_for_init(std::string_view value, urlpattern_init_type type) {
2835 if (type == urlpattern_init_type::PATTERN)
2836 return std::string{ value };
2837 return canonicalize_hostname(value);
2841inline std::string process_port_for_init(std::string_view port_value,
2842 std::string_view protocol_value, urlpattern_init_type type)
2844 if (type == urlpattern_init_type::PATTERN)
2845 return std::string{ port_value };
2846 return canonicalize_port(port_value, protocol_value);
2850inline std::string process_pathname_for_init(std::string_view pathname_value,
2851 std::string_view protocol_value, urlpattern_init_type type)
2853 if (type == urlpattern_init_type::PATTERN)
2854 return std::string{ pathname_value };
2857 if (protocol_value.empty() || is_special_scheme(protocol_value))
2858 return canonicalize_pathname(pathname_value);
2863 return canonicalize_opaque_pathname(pathname_value);
2867inline std::string process_search_for_init(std::string_view value, urlpattern_init_type type) {
2869 const std::string_view stripped_value =
2870 (!value.empty() && value.front() ==
'?')
2873 if (type == urlpattern_init_type::PATTERN)
2874 return std::string{ stripped_value };
2875 return canonicalize_search(stripped_value);
2879inline std::string process_hash_for_init(std::string_view value, urlpattern_init_type type) {
2881 const std::string_view stripped_value =
2882 (!value.empty() && value.front() ==
'#')
2885 if (type == urlpattern_init_type::PATTERN)
2886 return std::string{ stripped_value };
2887 return canonicalize_hash(stripped_value);
bool is_valid() const noexcept
Returns whether the URL is valid.
bool href(const StrT &str)
The href setter.
validation_errc parse(const T &str_url, const url *base=nullptr)
Parses given URL string against base URL.
std::string_view get_part_view(PartType t) const
Gets URL's part (URL record member) as string.
urlpattern exception class
urlpattern_error(const char *what_arg)
URL pattern class template.
bool test(const urlpattern_init &input) const
Test whether URL pattern matches the input.
std::string_view get_protocol() const noexcept
urlpattern(const urlpattern_init &init={}, urlpattern_options opt={})
Constructs urlpattern object from upa::urlpattern_init object.
std::string_view get_password() const noexcept
std::string_view get_pathname() const noexcept
std::string_view get_search() const noexcept
urlpattern(const T &input, TB &&base_url, urlpattern_options opt={})
Constructs urlpattern object from URL pattern string and optional base URL string.
std::string_view get_hash() const noexcept
urlpattern(const T &input, urlpattern_options opt={})
Constructs urlpattern object from URL pattern string.
bool has_regexp_groups() const noexcept
std::optional< ResT > exec(const urlpattern_init &input) const
Executes the URL pattern against the input.
std::string_view get_port() const noexcept
std::string_view get_username() const noexcept
std::string_view get_hostname() const noexcept
constexpr bool success(validation_errc res) noexcept
Check validation error code indicates success.
constexpr bool is_regex_engine_v
constexpr code_point_set userinfo_no_encode_set
std::variant< std::monostate, std::string_view, std::u16string_view, std::u32string_view, std::wstring_view, const urlpattern_init * > urlpattern_input
URLPatternComponentResult struct.
std::unordered_map< std::string_view, std::optional< std::string > > groups
std::optional< std::string > pathname
std::optional< std::string > protocol
std::optional< std::string > hash
std::optional< std::string > username
std::optional< std::string_view > get(std::string_view name) const
Get value of member by name.
std::optional< std::string > password
std::optional< std::string > base_url
std::optional< std::string > search
constexpr bool operator==(const urlpattern_init &other) const
std::optional< std::string > hostname
std::optional< std::string > port
void set(std::string_view name, T &&value)
Set value of member by name.
The result of executing the URL pattern when there is a match.
urlpattern_component_result protocol
urlpattern_component_result username
urlpattern_component_result search
urlpattern_component_result hostname
urlpattern_component_result password
urlpattern_component_result pathname
urlpattern_component_result hash
urlpattern_component_result port