Upa URL C++ library
A WHATWG URL Standard implementation
Loading...
Searching...
No Matches
urlpattern.h
Go to the documentation of this file.
1// Copyright 2023-2026 Rimas Misevičius
2// Distributed under the BSD-style license that can be
3// found in the LICENSE file.
4//
5#ifndef UPA_URLPATTERN_H
6#define UPA_URLPATTERN_H
7
8#include "url.h" // NOLINT(llvm-include-order)
9#include "unicode_id.h"
10
11#ifndef UPA_MODULE
12# include <algorithm>
13# include <cassert>
14# include <charconv>
15# include <cstdint>
16# include <optional>
17# include <stdexcept>
18# include <string>
19# include <string_view>
20# include <type_traits>
21# include <unordered_map>
22# include <utility>
23# include <variant>
24# include <vector>
25#endif // UPA_MODULE
26
27namespace upa {
28namespace pattern {
29
30using namespace std::string_view_literals;
31
32// Scheme info
33
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;
37}
38
39inline bool is_special_scheme_default_port(std::string_view scheme, std::string_view port) {
40 // scheme is special?
41 const auto* scheme_inf = detail::get_scheme_info(scheme);
42 if (scheme_inf != nullptr && scheme_inf->is_special && scheme_inf->default_port >= 0) {
43 // port is valid and is the default scheme port?
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;
49 }
50 return false;
51}
52
53// TODO: make public in URL library:
54// * make the list of special schemes public (see: protocol_component_matches_special_scheme)
55
56// Parse URL against base URL
57
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) {
62 upa::url url;
63
64 if constexpr (upa::is_nullopt_v<TB>) {
65 url.parse(input);
66 } else if constexpr (upa::is_optional_v<TB>) {
67 if (base_url_str)
68 url.parse(input, *base_url_str);
69 else
70 url.parse(input);
71 } else {
72 url.parse(input, base_url_str);
73 }
74 return url;
75}
76
77// Get code point from a string
78
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;
84}
85
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();
92 return cp;
93}
94
96// Check if T has an `inputs` member.
97
98template<class, class = void>
99inline constexpr bool has_inputs_v = false;
100
101template<class T>
102inline constexpr bool has_inputs_v<T, std::void_t<decltype(T::inputs)>> = true;
103
105// Requirements for regex_engine
106
107template<class T, class = void>
108struct has_regex_engine_members : std::false_type {};
109
110template<class T>
111struct has_regex_engine_members<T, std::void_t<
112 typename T::result,
113 // T::result members
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>())),
117 // T members
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<
123 // Check the return value types of T::result members
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>>,
127 // Check the return value types of T members
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>
133 > {};
134
135} // namespace pattern
136
137UPA_EXPORT_BEGIN
138
139template<class T>
140constexpr bool is_regex_engine_v =
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;
147
149// 1. The URLPattern class
150// 1.1. Introduction
151// 1.2. The URLPattern class
152// https://urlpattern.spec.whatwg.org/#urlpattern-class
153// https://urlpattern.spec.whatwg.org/#dictdef-urlpatterninit
154
160 std::optional<std::string> protocol;
161 std::optional<std::string> username;
162 std::optional<std::string> password;
163 std::optional<std::string> hostname;
164 std::optional<std::string> port;
165 std::optional<std::string> pathname;
166 std::optional<std::string> search;
167 std::optional<std::string> hash;
168 std::optional<std::string> base_url;
169
170#ifdef UPA_CPP_20
171 constexpr bool operator==(const urlpattern_init&) const = default;
172#else
173 constexpr bool operator==(const urlpattern_init& other) const {
174 return protocol == other.protocol && username == other.username &&
175 password == other.password && hostname == other.hostname && port == other.port
176 && pathname == other.pathname && search == other.search && hash == other.hash
177 && base_url == other.base_url;
178 }
179#endif
180
184 [[nodiscard]] inline std::optional<std::string_view> get(std::string_view name) const {
185 if (auto ptr = get_member(name))
186 return this->*ptr;
187 return std::nullopt;
188 }
189
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);
197 }
198
199private:
200 // Get member by name
201 [[nodiscard]] UPA_API static std::optional<std::string> urlpattern_init::*
202 get_member(std::string_view name);
203};
204
205UPA_EXPORT_END
206
207namespace pattern {
208
209// 1.6. Constructor string parsing
210// https://urlpattern.spec.whatwg.org/#constructor-string-parsing
211// https://urlpattern.spec.whatwg.org/#parse-a-constructor-string
212
213template <class regex_engine>
214inline urlpattern_init parse_constructor_string(std::string_view input);
215
216// 2. Pattern strings
217// https://urlpattern.spec.whatwg.org/#pattern-strings
218
219// https://urlpattern.spec.whatwg.org/#pattern-string
220// A pattern string is a string that is written to match a set of target strings. A well formed
221// pattern string conforms to a particular pattern syntax. This pattern syntax is directly based
222// on the syntax used by the popular path-to-regexp JavaScript library.
223
224// 2.1. Parsing pattern strings
225// https://urlpattern.spec.whatwg.org/#parsing-pattern-strings
226
227// 2.1.1. Tokens
228// https://urlpattern.spec.whatwg.org/#tokens
229
230// https://urlpattern.spec.whatwg.org/#token
231struct token {
232 enum class type {
233 // The token represents a U+007B ({) code point.
234 OPEN,
235 // The token represents a U+007D (}) code point.
236 CLOSE,
237 // The token represents a string of the form "(<regular expression>)". The
238 // regular expression is required to consist of only ASCII code points.
239 REGEXP,
240 // The token represents a string of the form ":<name>". The name value is
241 // restricted to code points that are consistent with JavaScript identifiers.
242 NAME,
243 // The token represents a valid pattern code point without any special
244 // syntactical meaning.
245 CHAR,
246 // The token represents a code point escaped using a backslash like "<char>".
247 ESCAPED_CHAR,
248 // The token represents a matching group modifier that is either the U+003F (?)
249 // or U+002B (+) code points.
250 OTHER_MODIFIER,
251 // The token represents a U+002A (*) code point that can be either a wildcard
252 // matching group or a matching group modifier.
253 ASTERISK,
254 // The token represents the end of the pattern string.
255 END,
256 // The token represents a code point that is invalid in the pattern. This could
257 // be because of the code point value itself or due to its location within the
258 // pattern relative to other syntactic elements.
259 INVALID_CHAR
260 };
261
262 type type_;
263 std::size_t index_;
264 std::string_view value_;
265};
266
267// https://urlpattern.spec.whatwg.org/#token-list
268using token_list = std::vector<token>;
269
270// 2.1.2. Tokenizing
271// https://urlpattern.spec.whatwg.org/#tokenizing
272
273// https://urlpattern.spec.whatwg.org/#tokenize-policy
274enum class tokenize_policy {
275 strict,
276 lenient
277};
278
279// https://urlpattern.spec.whatwg.org/#tokenize
280inline token_list tokenize(std::string_view input, tokenize_policy policy);
281
282// 2.1.3. Parts
283// https://urlpattern.spec.whatwg.org/#parts
284
285// https://urlpattern.spec.whatwg.org/#part
286struct part {
287 enum class type {
288 // The part represents a simple fixed text string.
289 FIXED_TEXT,
290 // The part represents a matching group with a custom regular expression.
291 REGEXP,
292 // The part represents a matching group that matches code points up to the next
293 // separator code point. This is typically used for a named group like ":foo" that
294 // does not have a custom regular expression.
295 SEGMENT_WILDCARD,
296 // The part represents a matching group that greedily matches all code points.
297 // This is typically used for the "*" wildcard matching group.
298 FULL_WILDCARD
299 };
300
301 enum class modifier {
302 // The part does not have a modifier.
303 none,
304 // The part has an optional modifier indicated by the U+003F (?) code point.
305 optional,
306 // The part has a "zero or more" modifier indicated by the U+002A (*) code point.
307 zero_or_more,
308 // The part has a "one or more" modifier indicated by the U+002B (+) code point.
309 one_or_more
310 };
311
312 UPA_CONSTEXPR_20 part(type t, std::string&& value, modifier m)
313 : type_{ t }
314 , value_{ std::move(value) }
315 , modifier_{ m }
316 {}
317
318 type type_;
319 std::string value_;
320 modifier modifier_;
321 std::string name_;
322 std::string prefix_;
323 std::string suffix_;
324};
325
326// https://urlpattern.spec.whatwg.org/#part-list
327using part_list = std::vector<part>;
328
329
330// 2.1.4.Options
331// https://urlpattern.spec.whatwg.org/#options-header
332
333// https://urlpattern.spec.whatwg.org/#options
334struct options {
335 // ASCII code point or the empty string
336 std::string_view delimiter_code_point; // TODO: maybe char32_t?
337 std::string_view prefix_code_point; // TODO: maybe char32_t?
338 bool ignore_case = false;
339};
340
341// 2.1.5. Parsing
342// https://urlpattern.spec.whatwg.org/#parsing
343
344// An encoding callback is an abstract algorithm that takes a given string input.
345// The input will be a simple text piece of a pattern string. An implementing
346// algorithm will validate and encode the input. It must return the encoded string
347// or throw an exception
348// https://urlpattern.spec.whatwg.org/#encoding-callback
349using encoding_callback = std::string (*)(std::string_view input);
350
351// https://urlpattern.spec.whatwg.org/#parse-a-pattern-string
352// input - pattern string to parse
353inline part_list parse_pattern_string(std::string_view input, const options& opt, encoding_callback encoding_cb);
354
355// https://urlpattern.spec.whatwg.org/#full-wildcard-regexp-value
356inline constexpr std::string_view full_wildcard_regexp_value{ ".*"sv };
357
358// https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp
359UPA_CONSTEXPR_20 std::string generate_segment_wildcard_regexp(const options& opt);
360
361// 2.2. Converting part lists to regular expressions
362// https://urlpattern.spec.whatwg.org/#converting-part-lists-to-regular-expressions
363
364using string_list = std::vector<std::string>;
365
366// https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list
367UPA_CONSTEXPR_20 std::pair<std::string, string_list> generate_regular_expression_and_name_list(
368 const part_list& pt_list, const options& opt);
369
370UPA_CONSTEXPR_20 void append_escape_regexp_string(std::string& result, std::string_view input);
371
372// 2.3. Converting part lists to pattern strings
373// https://urlpattern.spec.whatwg.org/#converting-part-lists-to-pattern-strings
374
375inline std::string generate_pattern_string(const part_list& pt_list, const options& opt);
376
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);
380
381// 3. Canonicalization
382// https://urlpattern.spec.whatwg.org/#canon
383
384// 3.1. Encoding callbacks
385// https://urlpattern.spec.whatwg.org/#canon-encoding-callbacks
386
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);
395}
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);
400
401// 3.2. URLPatternInit processing
402// https://urlpattern.spec.whatwg.org/#canon-processing-for-init
403
404enum class urlpattern_init_type { PATTERN, URL };
405
406inline urlpattern_init process_urlpattern_init(const urlpattern_init& init, urlpattern_init_type type, bool set_empty);
407
408
410
411// 1.3. The URL pattern struct
412// https://urlpattern.spec.whatwg.org/#component
413
414template <class regex_engine>
415struct component {
416 component() = default;
417 component(const component&) = delete;
418 component(component&&) noexcept = default;
419 // compile a component
420 component(std::string_view input, encoding_callback encoding_cb, const options& opt);
421 // destructor
422 ~component() = default;
423
424 component& operator=(const component&) = delete;
425 component& operator=(component&&) noexcept = default;
426
427 // well formed pattern string
428 std::string pattern_string_;
429 regex_engine regular_expression_;
430 string_list group_name_list_;
431 bool has_regexp_groups_ = false;
432};
433
434// 1.5. Internals
435// https://urlpattern.spec.whatwg.org/#urlpattern-internals
436
437// ....
438
439// https://urlpattern.spec.whatwg.org/#default-options
440// The default options is an options struct with delimiter code point set to the empty string and
441// prefix code point set to the empty string.
442inline constexpr options default_options{};
443
444// https://urlpattern.spec.whatwg.org/#hostname-options
445// TODO: if C++20 use designated initializers
446inline constexpr options hostname_options { "."sv };
447
448template <class regex_engine>
449inline bool protocol_component_matches_special_scheme(const component<regex_engine>& protocol_component);
450// input - pattern string to check
451constexpr bool hostname_pattern_is_ipv6_address(std::string_view input) noexcept;
452
453} // namespace pattern
454
455UPA_EXPORT_BEGIN
456
458// 1.2. The URLPattern class
459// https://urlpattern.spec.whatwg.org/#urlpattern-class
460
461// URLPatternInput
462using urlpattern_input = std::variant<
463 std::monostate,
464 std::string_view,
465#ifdef __cpp_char8_t
466 std::u8string_view,
467#endif
468 std::u16string_view,
469 std::u32string_view,
470 std::wstring_view,
471 const urlpattern_init*>;
472
473// URLPatternOptions
475 bool ignore_case = false;
476};
477
478// sequence<URLPatternInput>
480public:
481 using array_type = std::array<urlpattern_input, 2>;
482 using value_type = array_type::value_type;
483 using size_type = array_type::size_type;
484 using difference_type = array_type::difference_type;
485 using reference = array_type::const_reference;
486 using const_reference = array_type::const_reference;
487 using pointer = array_type::const_pointer;
488 using const_pointer = array_type::const_pointer;
489 using iterator = array_type::const_iterator;
490 using const_iterator = array_type::const_iterator;
491
492 constexpr urlpattern_inputs() noexcept = default;
493 // initializes with one or two strings
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>
496 constexpr urlpattern_inputs(const T& str0, const TB& str1 = std::nullopt) noexcept
497 : size_{ 1u + get_optional_count(str1) }
498 , arr_{ make_string_view(str0), get_optional_item(str1) }
499 {}
500 // initializes with urlpattern_init
501 constexpr urlpattern_inputs(const urlpattern_init& init) noexcept
502 : size_{ 1u }
503 , arr_{ std::addressof(init) }
504 {}
505
506 constexpr const_reference operator[](size_type pos) const {
507 assert(pos < size_);
508 return arr_[pos];
509 }
510
511 constexpr const_iterator begin() const noexcept { return arr_.begin(); }
512 constexpr const_iterator end() const noexcept { return arr_.begin() + size_; }
513
514 constexpr bool empty() const noexcept { return size_ == 0; }
515 constexpr size_type size() const noexcept { return size_; }
516
517private:
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());
522 }
523 template <class T>
524 static constexpr size_type get_optional_count(const T& ostr) {
525 if constexpr (upa::is_nullopt_v<T>)
526 return 0u;
527 else if constexpr (upa::is_optional_v<T>)
528 return ostr ? 1u : 0u;
529 else
530 return 1u;
531 }
532 template <class T>
533 static constexpr value_type get_optional_item(const T& ostr) {
534 if constexpr (upa::is_nullopt_v<T>)
535 return value_type{};
536 else if constexpr (upa::is_optional_v<T>)
537 return ostr ? value_type{ make_string_view(*ostr) } : value_type{};
538 else
539 return make_string_view(ostr);
540 }
541
542 size_type size_ = 0;
543 array_type arr_;
544};
545
551 std::string input;
552 std::unordered_map<std::string_view, std::optional<std::string>> groups;
553};
554
572
584
621template <class regex_engine,
622 typename = std::enable_if_t<is_regex_engine_v<regex_engine>>>
624public:
641 urlpattern(const urlpattern_init& init = {}, urlpattern_options opt = {});
642
659 template <class T, class TB, upa::enable_if_str_arg_t<T> = 0,
660 upa::enable_if_optional_str_arg_t<TB> = 0>
661 inline urlpattern(const T& input, TB&& base_url, urlpattern_options opt = {})
662 : urlpattern{ make_urlpattern_init(input, std::forward<TB>(base_url)), opt } {}
663
676 template <class T, upa::enable_if_str_arg_t<T> = 0>
677 inline urlpattern(const T& input, urlpattern_options opt = {})
678 : urlpattern{ make_urlpattern_init(input, std::nullopt), opt } {}
679
689 [[nodiscard]] bool test(const urlpattern_init& input) const;
690
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;
702
707 [[nodiscard]] bool test(const upa::url& url) const;
708
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;
741
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;
765
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;
784
786 [[nodiscard]] std::string_view get_protocol() const noexcept;
787
789 [[nodiscard]] std::string_view get_username() const noexcept;
790
792 [[nodiscard]] std::string_view get_password() const noexcept;
793
795 [[nodiscard]] std::string_view get_hostname() const noexcept;
796
798 [[nodiscard]] std::string_view get_port() const noexcept;
799
801 [[nodiscard]] std::string_view get_pathname() const noexcept;
802
804 [[nodiscard]] std::string_view get_search() const noexcept;
805
807 [[nodiscard]] std::string_view get_hash() const noexcept;
808
811 [[nodiscard]] bool has_regexp_groups() const noexcept;
812
813private:
814 using regex_exec_result = typename regex_engine::result;
815
816 bool match_for_test(
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;
820
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;
826
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);
830
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);
834
835 // The URL pattern struct
836 // https://urlpattern.spec.whatwg.org/#url-pattern
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_;
845};
846
848
852class UPA_SO_VISIBLE urlpattern_error : public std::runtime_error {
853public:
857 inline explicit urlpattern_error(const char* what_arg)
858 : std::runtime_error(what_arg)
859 {}
860};
861
862UPA_EXPORT_END
863
865// 1.2. The URLPattern class
866// https://urlpattern.spec.whatwg.org/#urlpattern-class
867
868// initialize (as constructors)
869// https://urlpattern.spec.whatwg.org/#urlpattern-initialize
870
871// 1.4. High-level operations: To create a URL pattern ...
872// https://urlpattern.spec.whatwg.org/#url-pattern-create
873
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)
877{
878 urlpattern_init init{ pattern::parse_constructor_string<regex_engine>(upa::make_string(input)) };
879 if constexpr (upa::is_nullopt_v<TB>) {
880 if (!init.protocol)
881 throw urlpattern_error("No base URL");
882 } else if constexpr (upa::is_optional_v<TB>) {
883 if (base_url)
884 init.base_url = upa::make_string(*std::forward<TB>(base_url));
885 else if (!init.protocol)
886 throw urlpattern_error("No base URL");
887 } else {
888 init.base_url = upa::make_string(std::forward<TB>(base_url));
889 }
890 return init;
891}
892
893template <class regex_engine, typename E>
895 using namespace std::string_view_literals;
896
897 // Let processedInit be the result of process a URLPatternInit given init, "pattern",
898 // null, null, null, null, null, null, null, and null.
899 auto processed_init = process_urlpattern_init(init, pattern::urlpattern_init_type::PATTERN, false/*all nulls*/);
900
901 // For each componentName of { "protocol", "username", "password", "hostname", "port", "pathname",
902 // "search", "hash" }:
903 // - If processedInit[componentName] does not exist, then set processedInit[componentName] to "*"
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;
912
913 // If processedInit["protocol"] is a special scheme and processedInit["port"] is a string
914 // which represents its corresponding default port in radix-10 using ASCII digits then set
915 // processedInit["port"] to the empty string
916 if (pattern::is_special_scheme_default_port(*processed_init.protocol, *processed_init.port))
917 processed_init.port = ""sv;
918
919 // component constructor performs `compile a component`
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);
926
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);
930 else
931 hostname_component_ = pattern::component<regex_engine>(*processed_init.hostname,
932 pattern::canonicalize_hostname, pattern::hostname_options);
933
934 port_component_ = pattern::component<regex_engine>(*processed_init.port,
935 pattern::canonicalize_port, pattern::default_options);
936
937 // Let compileOptions be a copy of the default options with
938 // the ignore case property set to options["ignoreCase"].
939 const pattern::options compile_opt{ ""sv, ""sv, opt.ignore_case };
940 if (pattern::protocol_component_matches_special_scheme(protocol_component_)) {
941 // pathname options
942 // https://urlpattern.spec.whatwg.org/#pathname-options
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);
946 } else {
947 pathname_component_ = pattern::component<regex_engine>(*processed_init.pathname,
948 pattern::canonicalize_opaque_pathname, compile_opt);
949 }
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);
954}
955
956// https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol
957template <class regex_engine, typename E>
958inline std::string_view urlpattern<regex_engine, E>::get_protocol() const noexcept {
959 return protocol_component_.pattern_string_;
960}
961template <class regex_engine, typename E>
962inline std::string_view urlpattern<regex_engine, E>::get_username() const noexcept {
963 return username_component_.pattern_string_;
964}
965template <class regex_engine, typename E>
966inline std::string_view urlpattern<regex_engine, E>::get_password() const noexcept {
967 return password_component_.pattern_string_;
968}
969template <class regex_engine, typename E>
970inline std::string_view urlpattern<regex_engine, E>::get_hostname() const noexcept {
971 return hostname_component_.pattern_string_;
972}
973template <class regex_engine, typename E>
974inline std::string_view urlpattern<regex_engine, E>::get_port() const noexcept {
975 return port_component_.pattern_string_;
976}
977template <class regex_engine, typename E>
978inline std::string_view urlpattern<regex_engine, E>::get_pathname() const noexcept {
979 return pathname_component_.pattern_string_;
980}
981template <class regex_engine, typename E>
982inline std::string_view urlpattern<regex_engine, E>::get_search() const noexcept {
983 return search_component_.pattern_string_;
984}
985template <class regex_engine, typename E>
986inline std::string_view urlpattern<regex_engine, E>::get_hash() const noexcept {
987 return hash_component_.pattern_string_;
988}
989
990// https://urlpattern.spec.whatwg.org/#dom-urlpattern-test
991// https://urlpattern.spec.whatwg.org/#url-pattern-match
992
993template <class regex_engine, typename E>
994inline bool urlpattern<regex_engine, E>::test(const urlpattern_init& input) const {
995 urlpattern_init apply_result;
996 try {
997 apply_result = process_urlpattern_init(input, pattern::urlpattern_init_type::URL, true);
998 }
999 catch (std::exception&) {
1000 return false;
1001 }
1002 return match_for_test(
1003 *apply_result.protocol, *apply_result.username, *apply_result.password,
1004 *apply_result.hostname, *apply_result.port, *apply_result.pathname,
1005 *apply_result.search, *apply_result.hash);
1006}
1007
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>>
1010inline bool urlpattern<regex_engine, E>::test(const T& input, const TB& base_url_str) const {
1011 return test(pattern::parse_url_against_base(input, base_url_str));
1012}
1013
1014template <class regex_engine, typename E>
1029
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
1035{
1036 return
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);
1045}
1046
1047// https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec
1048// https://urlpattern.spec.whatwg.org/#url-pattern-match
1049
1050template <class regex_engine, typename E>
1051template <class ResT, std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>, int>>
1052inline std::optional<ResT> urlpattern<regex_engine, E>::exec(const urlpattern_init& input) const {
1053 urlpattern_init apply_result;
1054 try {
1055 apply_result = process_urlpattern_init(input, pattern::urlpattern_init_type::URL, true);
1056 }
1057 catch (std::exception&) {
1058 return std::nullopt;
1059 }
1060
1061 auto result = match<ResT>(
1062 *apply_result.protocol, *apply_result.username, *apply_result.password,
1063 *apply_result.hostname, *apply_result.port, *apply_result.pathname,
1064 *apply_result.search, *apply_result.hash);
1065 if constexpr (pattern::has_inputs_v<ResT>) {
1066 // Append input to inputs
1067 if (result)
1068 result->inputs = decltype(ResT::inputs){ input };
1069 }
1070 return result;
1071}
1072
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>>
1077inline std::optional<ResT> urlpattern<regex_engine, E>::exec(const T& input,
1078 const TB& base_url_str) const
1079{
1080 // Parse input
1081 const auto url = pattern::parse_url_against_base(input, base_url_str);
1082 if (!url.is_valid())
1083 return std::nullopt;
1084
1085 auto result = match<ResT>(
1094 if constexpr (pattern::has_inputs_v<ResT>) {
1095 // Append input to inputs
1096 if (result)
1097 result->inputs = decltype(ResT::inputs){ input, base_url_str };
1098 }
1099 return result;
1100}
1101
1102template <class regex_engine, typename E>
1103template <class ResT, std::enable_if_t<std::is_base_of_v<urlpattern_result, ResT>, int>>
1104inline std::optional<ResT> urlpattern<regex_engine, E>::exec(const upa::url& url) const {
1105 if (!url.is_valid())
1106 return std::nullopt;
1107
1108 auto result = match<ResT>(
1117 if constexpr (pattern::has_inputs_v<ResT>) {
1118 // If input is a URL, then append the serialization of input to inputs.
1119 if (result)
1120 result->inputs = decltype(ResT::inputs){ url.href() };
1121 }
1122 return result;
1123}
1124
1125// create a component match result
1126// https://urlpattern.spec.whatwg.org/#create-a-component-match-result
1127
1128template <class regex_engine, typename E>
1129inline urlpattern_component_result urlpattern<regex_engine, E>::create_component_match_result(
1130 const pattern::component<regex_engine>& comp, std::string_view input,
1131 const regex_exec_result& exec_result)
1132{
1134 result.input = input;
1135
1136 // If the regular expression contains named capture groups, such as (?<x>...), then
1137 // exec_result.size() will be greater than comp.group_name_list.size() + 1. Therefore,
1138 // it is safer to use the smaller of the two values for the count.
1139 // For more info see:
1140 // * https://github.com/whatwg/urlpattern/pull/283
1141 // * https://github.com/web-platform-tests/wpt/pull/58594
1142 // * https://hg-edge.mozilla.org/mozilla-central/rev/08bf6b5ee560
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));
1147 }
1148 return result;
1149}
1150
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
1157{
1158 // Let protocolExecResult be RegExpBuiltinExec(urlpattern's protocol component's
1159 // regular expression, protocol).
1160 regex_exec_result protocol_exec_result;
1161 if (!protocol_component_.regular_expression_.exec(protocol, protocol_exec_result))
1162 return std::nullopt;
1163
1164 regex_exec_result username_exec_result;
1165 if (!username_component_.regular_expression_.exec(username, username_exec_result))
1166 return std::nullopt;
1167
1168 regex_exec_result password_exec_result;
1169 if (!password_component_.regular_expression_.exec(password, password_exec_result))
1170 return std::nullopt;
1171
1172 regex_exec_result hostname_exec_result;
1173 if (!hostname_component_.regular_expression_.exec(hostname, hostname_exec_result))
1174 return std::nullopt;
1175
1176 regex_exec_result port_exec_result;
1177 if (!port_component_.regular_expression_.exec(port, port_exec_result))
1178 return std::nullopt;
1179
1180 regex_exec_result pathname_exec_result;
1181 if (!pathname_component_.regular_expression_.exec(pathname, pathname_exec_result))
1182 return std::nullopt;
1183
1184 regex_exec_result search_exec_result;
1185 if (!search_component_.regular_expression_.exec(search, search_exec_result))
1186 return std::nullopt;
1187
1188 regex_exec_result hash_exec_result;
1189 if (!hash_component_.regular_expression_.exec(hash, hash_exec_result))
1190 return std::nullopt;
1191
1192 // Let result be a new URLPatternResult.
1193 ResT result;
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);
1202
1203 return result;
1204}
1205
1206// https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups
1207
1208template <class regex_engine, typename E>
1210 return
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_;
1219}
1220
1221namespace pattern {
1222
1223// 1.5. Internals
1224// https://urlpattern.spec.whatwg.org/#urlpattern-internals
1225
1226// compile a component
1227// https://urlpattern.spec.whatwg.org/#compile-a-component
1228
1229template <class regex_engine>
1230inline component<regex_engine>::component(std::string_view input, encoding_callback encoding_cb, const options& opt) {
1231 // Let part list be the result of running parse a pattern string given
1232 // input, options, and encoding callback
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);
1236
1237 // Note
1238 // The specification uses regular expressions to perform all matching, but this is not mandated.
1239 // Implementations are free to perform matching directly against the part list when possible;
1240 // e.g. when there are no custom regexp matching groups. If there are custom regular
1241 // expressions, however, its important that they be immediately evaluated in the compile
1242 // a component algorithm so an error can be thrown if they are invalid.
1243 if (!regular_expression_.init(regular_expression_string, opt.ignore_case))
1244 throw urlpattern_error("regular expression is not valid");
1245
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;
1251 });
1252}
1253
1254// https://urlpattern.spec.whatwg.org/#protocol-component-matches-a-special-scheme
1255
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);
1262}
1263
1264// https://urlpattern.spec.whatwg.org/#hostname-pattern-is-an-ipv6-address
1265
1266constexpr bool hostname_pattern_is_ipv6_address(std::string_view input) noexcept {
1267 // If input's code point length is less than 2, then return false.
1268 // TODO: code point (not necessary)
1269 if (input.length() < 2)
1270 return false;
1271 return input[0] == '[' ||
1272 (input[0] == '{' && input[1] == '[') ||
1273 (input[0] == '\\' && input[1] == '[');
1274}
1275
1276// 1.6. Constructor string parsing
1277// https://urlpattern.spec.whatwg.org/#constructor-string-parsing
1278
1279// https://urlpattern.spec.whatwg.org/#constructor-string-parser
1280struct constructor_string_parser {
1281 // https://urlpattern.spec.whatwg.org/#constructor-string-parser-state
1282 enum class state {
1283 INIT,
1284 PROTOCOL,
1285 AUTHORITY,
1286 USERNAME,
1287 PASSWORD,
1288 HOSTNAME,
1289 PORT,
1290 PATHNAME,
1291 SEARCH,
1292 HASH,
1293 DONE
1294 };
1295
1296 constructor_string_parser(std::string_view input);
1297
1298 void change_state(state new_state, std::size_t skip);
1299 void rewind();
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();
1318
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;
1329};
1330
1331// https://urlpattern.spec.whatwg.org/#parse-a-constructor-string
1332// 1. Let parser be a new constructor string parser whose input is input and token
1333// list is the result of running tokenize given input and "lenient".
1334
1335inline constructor_string_parser::constructor_string_parser(std::string_view input)
1336 : input_{ input }
1337 , token_list_{ tokenize(input, tokenize_policy::lenient) }
1338{}
1339
1340template <class regex_engine>
1341inline urlpattern_init parse_constructor_string(std::string_view input) {
1342 using state = constructor_string_parser::state;
1343
1344 constructor_string_parser parser{ input };
1345
1346 // 2. While parser's token index is less than parser's token list size:
1347 while (parser.token_index_ < parser.token_list_.size()) {
1348 parser.token_increment_ = 1;
1349 // Note
1350 // On every iteration of the parse loop the parser's token index will be incremented by its
1351 // token increment value. Typically this means incrementing by 1, but at certain times it is
1352 // set to zero. The token increment is then always reset back to 1 at the top of the loop.
1353
1354 if (parser.token_list_[parser.token_index_].type_ == token::type::END) {
1355 if (parser.state_ == state::INIT) {
1356 // Note
1357 // If we reached the end of the string in the "init" state, then we failed to find a
1358 // protocol terminator and this has to be a relative URLPattern constructor string.
1359
1360 parser.rewind();
1361 // Note
1362 // We next determine at which component the relative pattern begins. Relative
1363 // pathnames are most common, but URLs and URLPattern constructor strings can begin
1364 // with the search or hash components as well.
1365
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);
1370 } else {
1371 parser.change_state(state::PATHNAME, 0);
1372 }
1373 parser.token_index_ += parser.token_increment_;
1374 continue;
1375 }
1376
1377 if (parser.state_ == state::AUTHORITY) {
1378 // Note
1379 // If we reached the end of the string in the "authority" state, then we failed to
1380 // find an "@". Therefore there is no username or password.
1381 parser.rewind_and_set_state(state::HOSTNAME);
1382 parser.token_index_ += parser.token_increment_;
1383 continue;
1384 }
1385
1386 parser.change_state(state::DONE, 0);
1387 break;
1388 }
1389
1390 if (parser.is_group_open()) {
1391 // Note
1392 // We ignore all code points within "{ ... }" pattern groupings. It would not make
1393 // sense to allow a URL component boundary to lie within a grouping; e.g.
1394 // "https://example.c{om/fo}o". While not supported within well formed pattern strings,
1395 // we handle nested groupings here to avoid parser confusion.
1396 //
1397 // It is not necessary to perform this logic for regexp or named groups since those
1398 // values are collapsed into individual tokens by the tokenize algorithm.
1399 ++parser.group_depth_;
1400 parser.token_index_ += parser.token_increment_;
1401 continue;
1402 }
1403
1404 if (parser.group_depth_ > 0) {
1405 if (parser.is_group_close()) {
1406 --parser.group_depth_;
1407 } else {
1408 parser.token_index_ += parser.token_increment_;
1409 continue;
1410 }
1411 }
1412
1413 switch (parser.state_) {
1414 case state::INIT:
1415 if (parser.is_protocol_suffix())
1416 parser.rewind_and_set_state(state::PROTOCOL);
1417 break;
1418 case state::PROTOCOL:
1419 if (parser.is_protocol_suffix()) {
1420 parser.compute_protocol_matches_special_scheme_flag<regex_engine>();
1421 // Note
1422 // We need to eagerly compile the protocol component to determine if it matches any
1423 // special schemes. If it does then certain special rules apply. It determines if
1424 // the pathname defaults to a "/" and also whether we will look for the username,
1425 // password, hostname, and port components. Authority slashes can also cause us to
1426 // look for these components as well. Otherwise we treat this as an "opaque path
1427 // URL" and go straight to the pathname component.
1428 state next_state = state::PATHNAME;
1429 std::size_t skip = 1;
1430 if (parser.next_is_authority_slashes()) {
1431 next_state = state::AUTHORITY;
1432 skip = 3;
1433 } else if (parser.protocol_matches_special_scheme_flag_) {
1434 next_state = state::AUTHORITY;
1435 }
1436 parser.change_state(next_state, skip);
1437 }
1438 break;
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);
1444 break;
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);
1450 break;
1451 case state::PASSWORD:
1452 if (parser.is_identity_terminator())
1453 parser.change_state(state::HOSTNAME, 1);
1454 break;
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);
1468 break;
1469 case state::PORT:
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);
1476 break;
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);
1482 break;
1483 case state::SEARCH:
1484 if (parser.is_hash_prefix())
1485 parser.change_state(state::HASH, 1);
1486 break;
1487 case state::HASH:
1488 break; // Do nothing
1489 case state::DONE:
1490 assert(false); // This step is never reached
1491 break;
1492 }
1493 parser.token_index_ += parser.token_increment_;
1494 } // while
1495
1496 // 3. If parser's result contains "hostname" and not "port", then set parser's
1497 // result["port"] to the empty string
1498 if (parser.result_.hostname && !parser.result_.port)
1499 parser.result_.port = ""sv;
1500 // Note
1501 // This is special-cased because when an author does not specify a port, they usually intend
1502 // the default port. If any port is acceptable, the author can specify it as a wildcard
1503 // explicitly. For example, "https://example.com/*" does not match URLs beginning with
1504 // "https://example.com:8443/", which is a different origin.
1505
1506 return std::move(parser.result_);
1507}
1508
1509// constructor_string_parser class
1510
1511// https://urlpattern.spec.whatwg.org/#change-state
1512inline void constructor_string_parser::change_state(state new_state, std::size_t skip) {
1513 // If parser's state is not "init", not "authority", and not "done", then set parser's
1514 // result[parser's state] to the result of running make a component string given parser.
1515 //
1516 // if (state_ != state::INIT && state_ != state::AUTHORITY && state_ != state::DONE)
1517 switch (state_) {
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;
1526 default: break;
1527 }
1528
1529 // If parser's state is not "init" and new state is not "done", then:
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;
1535 }
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;
1540 }
1541 if (state_ >= state::PROTOCOL && state_ <= state::PATHNAME &&
1542 new_state == state::HASH &&
1543 !result_.search) {
1544 result_.search = ""sv;
1545 }
1546 }
1547
1548 state_ = new_state;
1549 token_index_ += skip;
1550 component_start_ = token_index_;
1551 token_increment_ = 0;
1552}
1553
1554// https://urlpattern.spec.whatwg.org/#rewind
1555inline void constructor_string_parser::rewind() {
1556 token_index_ = component_start_;
1557 token_increment_ = 0;
1558}
1559
1560// https://urlpattern.spec.whatwg.org/#rewind-and-set-state
1561inline void constructor_string_parser::rewind_and_set_state(state state) {
1562 rewind();
1563 state_ = state;
1564}
1565
1566// https://urlpattern.spec.whatwg.org/#get-a-safe-token
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];
1570
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];
1575}
1576
1577// https://urlpattern.spec.whatwg.org/#is-a-non-special-pattern-char
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)
1581 return false;
1582 return
1583 tok.type_ == token::type::CHAR ||
1584 tok.type_ == token::type::ESCAPED_CHAR ||
1585 tok.type_ == token::type::INVALID_CHAR;
1586}
1587
1588// https://urlpattern.spec.whatwg.org/#is-a-protocol-suffix
1589inline bool constructor_string_parser::is_protocol_suffix() const {
1590 return is_non_special_pattern_char(token_index_, ":"sv);
1591}
1592
1593// https://urlpattern.spec.whatwg.org/#next-is-authority-slashes
1594inline bool constructor_string_parser::next_is_authority_slashes() const {
1595 return
1596 is_non_special_pattern_char(token_index_ + 1, "/"sv) &&
1597 is_non_special_pattern_char(token_index_ + 2, "/"sv);
1598}
1599
1600// https://urlpattern.spec.whatwg.org/#is-an-identity-terminator
1601inline bool constructor_string_parser::is_identity_terminator() const {
1602 return is_non_special_pattern_char(token_index_, "@"sv);
1603}
1604
1605// https://urlpattern.spec.whatwg.org/#is-a-password-prefix
1606inline bool constructor_string_parser::is_password_prefix() const {
1607 return is_non_special_pattern_char(token_index_, ":"sv);
1608}
1609
1610// https://urlpattern.spec.whatwg.org/#is-a-port-prefix
1611inline bool constructor_string_parser::is_port_prefix() const {
1612 return is_non_special_pattern_char(token_index_, ":"sv);
1613}
1614
1615// https://urlpattern.spec.whatwg.org/#is-a-pathname-start
1616inline bool constructor_string_parser::is_pathname_start() const {
1617 return is_non_special_pattern_char(token_index_, "/"sv);
1618}
1619
1620// https://urlpattern.spec.whatwg.org/#is-a-search-prefix
1621inline bool constructor_string_parser::is_search_prefix() const {
1622 if (is_non_special_pattern_char(token_index_, "?"sv))
1623 return true;
1624 // FIXME: maybe get_safe_token?
1625 if (token_list_[token_index_].value_ != "?"sv)
1626 return false;
1627
1628 // 3. Let previous index be parser's token index - 1.
1629 // 4. If previous index is less than 0, then return true.
1630 if (token_index_ < 1)
1631 return true;
1632 const token& previous_token = get_safe_token(token_index_ - 1);
1633 return
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;
1638}
1639
1640// https://urlpattern.spec.whatwg.org/#is-a-hash-prefix
1641inline bool constructor_string_parser::is_hash_prefix() const {
1642 return is_non_special_pattern_char(token_index_, "#"sv);
1643}
1644
1645// https://urlpattern.spec.whatwg.org/#is-a-group-open
1646inline bool constructor_string_parser::is_group_open() const {
1647 // FIXME: maybe use get_safe_token?
1648 return token_list_[token_index_].type_ == token::type::OPEN;
1649}
1650
1651// https://urlpattern.spec.whatwg.org/#is-a-group-close
1652inline bool constructor_string_parser::is_group_close() const {
1653 // FIXME: maybe use get_safe_token?
1654 return token_list_[token_index_].type_ == token::type::CLOSE;
1655}
1656
1657// https://urlpattern.spec.whatwg.org/#is-an-ipv6-open
1658inline bool constructor_string_parser::is_ipv6_open() const {
1659 return is_non_special_pattern_char(token_index_, "["sv);
1660}
1661
1662// https://urlpattern.spec.whatwg.org/#is-an-ipv6-close
1663inline bool constructor_string_parser::is_ipv6_close() const {
1664 return is_non_special_pattern_char(token_index_, "]"sv);
1665}
1666
1667// https://urlpattern.spec.whatwg.org/#make-a-component-string
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);
1675}
1676
1677// https://urlpattern.spec.whatwg.org/#compute-protocol-matches-a-special-scheme-flag
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;
1684}
1685
1686// 2.1.2. Tokenizing
1687// https://urlpattern.spec.whatwg.org/#tokenizing
1688
1689// https://urlpattern.spec.whatwg.org/#tokenizer
1690struct tokenizer {
1691 UPA_CONSTEXPR_20 tokenizer() = default;
1692 UPA_CONSTEXPR_20 tokenizer(std::string_view input, tokenize_policy policy)
1693 : input_{ input }, policy_{ policy } {}
1694
1695 // https://urlpattern.spec.whatwg.org/#get-the-next-code-point
1696 UPA_CONSTEXPR_20 void get_the_next_code_point() {
1697 code_point_ = get_code_point(input_, next_index_);
1698 }
1699
1700 // https://urlpattern.spec.whatwg.org/#seek-and-get-the-next-code-point
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();
1704 }
1705
1706 // https://urlpattern.spec.whatwg.org/#add-a-token
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) });
1709 index_ = next_pos;
1710 }
1711 // https://urlpattern.spec.whatwg.org/#add-a-token-with-default-length
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);
1714 }
1715 // https://urlpattern.spec.whatwg.org/#add-a-token-with-default-position-and-length
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_);
1718 }
1719
1720 // https://urlpattern.spec.whatwg.org/#process-a-tokenizing-error
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");
1724 }
1725 assert(policy_ == tokenize_policy::lenient);
1726 add_token_with_default_length(token::type::INVALID_CHAR, next_pos, value_pos);
1727 }
1728
1729 // members
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;
1735 // Unicode code point, initially null. But we don't need null value, because
1736 // tokenize function initializes it to not null before accessing it's value.
1737 char32_t code_point_ = 0;
1738};
1739
1740// https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point
1741inline bool is_valid_name_code_point(char32_t code_point, bool first) noexcept {
1742 return first
1743 ? table::is_identifier_start(code_point)
1744 : table::is_identifier_part(code_point);
1745}
1746
1747// https://infra.spec.whatwg.org/#ascii-code-point
1748constexpr bool is_ascii(char32_t code_point) noexcept {
1749 return code_point <= 0x7F;
1750}
1751
1752// https://urlpattern.spec.whatwg.org/#tokenize
1753inline token_list tokenize(std::string_view input, tokenize_policy policy) {
1754 tokenizer tokenizer{ input, policy };
1755
1756 while (tokenizer.index_ < input.length()) {
1757 tokenizer.seek_and_get_the_next_code_point(tokenizer.index_);
1758
1759 switch(tokenizer.code_point_) {
1760 case '*':
1761 tokenizer.add_token_with_default_position_and_length(token::type::ASTERISK);
1762 continue;
1763 case '+':
1764 case '?':
1765 tokenizer.add_token_with_default_position_and_length(token::type::OTHER_MODIFIER);
1766 continue;
1767 case '\\': {
1768 if (tokenizer.index_ == input.length() - 1) {
1769 tokenizer.process_tokenizing_error(tokenizer.next_index_, tokenizer.index_);
1770 continue;
1771 }
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);
1775 continue;
1776 }
1777 case '{':
1778 tokenizer.add_token_with_default_position_and_length(token::type::OPEN);
1779 continue;
1780 case '}':
1781 tokenizer.add_token_with_default_position_and_length(token::type::CLOSE);
1782 continue;
1783 case ':': {
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))
1790 break;
1791 name_pos = tokenizer.next_index_;
1792 }
1793 if (name_pos <= name_start) {
1794 tokenizer.process_tokenizing_error(name_start, tokenizer.index_);
1795 continue;
1796 }
1797 tokenizer.add_token_with_default_length(token::type::NAME, name_pos, name_start);
1798 continue;
1799 }
1800 case '(': {
1801 std::size_t depth = 1;
1802 auto regexp_pos = tokenizer.next_index_;
1803 const auto regexp_start = regexp_pos;
1804 bool error = false;
1805
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_);
1810 error = true;
1811 break;
1812 }
1813 if (regexp_pos == regexp_start && tokenizer.code_point_ == '?') {
1814 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1815 error = true;
1816 break;
1817 }
1818 if (tokenizer.code_point_ == '\\') {
1819 if (regexp_pos == input.length() - 1) {
1820 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1821 error = true;
1822 break;
1823 }
1824 tokenizer.get_the_next_code_point();
1825 if (!is_ascii(tokenizer.code_point_)) {
1826 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1827 error = true;
1828 break;
1829 }
1830 regexp_pos = tokenizer.next_index_;
1831 continue;
1832 }
1833 if (tokenizer.code_point_ == ')') {
1834 if (--depth == 0) {
1835 regexp_pos = tokenizer.next_index_;
1836 break;
1837 }
1838 } else if (tokenizer.code_point_ == '(') {
1839 ++depth;
1840 if (regexp_pos == input.length() - 1) {
1841 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1842 error = true;
1843 break;
1844 }
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_);
1849 error = true;
1850 break;
1851 }
1852 tokenizer.next_index_ = temporary_pos;
1853 }
1854 regexp_pos = tokenizer.next_index_;
1855 }
1856 if (error)
1857 continue;
1858 if (depth) {
1859 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1860 continue;
1861 }
1862 const auto regexp_len = regexp_pos - regexp_start - 1;
1863 if (regexp_len == 0) {
1864 tokenizer.process_tokenizing_error(regexp_start, tokenizer.index_);
1865 continue;
1866 }
1867 tokenizer.add_token(token::type::REGEXP, regexp_pos, regexp_start, regexp_len);
1868 continue;
1869 }
1870 }
1871 tokenizer.add_token_with_default_position_and_length(token::type::CHAR);
1872 } // while
1873
1874 tokenizer.add_token_with_default_length(token::type::END, tokenizer.index_, tokenizer.index_);
1875
1876 return std::move(tokenizer.token_list_);
1877}
1878
1879
1880// 2.1.5. Parsing
1881// https://urlpattern.spec.whatwg.org/#parsing
1882
1883// https://urlpattern.spec.whatwg.org/#pattern-parser
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)
1888 {}
1889
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;
1899
1900 // members
1901
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;
1909};
1910
1911// https://urlpattern.spec.whatwg.org/#parse-a-pattern-string
1912inline part_list parse_pattern_string(std::string_view input, const options& opt, encoding_callback encoding_cb)
1913{
1914 pattern_parser parser{ encoding_cb, generate_segment_wildcard_regexp(opt) };
1915 parser.token_list_ = tokenize(input, tokenize_policy::strict);
1916
1917 while (parser.index_ < parser.token_list_.size()) {
1918 // Example
1919 // This first section is looking for the sequence: <prefix char><name><regexp><modifier>.
1920 // There could be zero to all of these tokens.
1921 // TODO?: EXAMPLE 1
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);
1925
1926 if (pname_token || pregexp_or_wildcard_token) {
1927 // Note
1928 // If there is a matching group, we need to add the part immediately.
1929 std::string_view prefix{};
1930 if (pchar_token)
1931 prefix = pchar_token->value_;
1932 if (!prefix.empty() && prefix != opt.prefix_code_point) {
1933 parser.pending_fixed_value_.append(prefix);
1934 prefix = {}; // set prefix to the empty string
1935 }
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);
1939 continue;
1940 }
1941
1942 const auto* pfixed_token = pchar_token;
1943 // Note
1944 // If there was no matching group, then we need to buffer any fixed text. We want
1945 // to collect as much text as possible before adding it as a "fixed-text" part.
1946 if (pfixed_token == nullptr)
1947 pfixed_token = parser.try_consume_token(token::type::ESCAPED_CHAR);
1948 if (pfixed_token) {
1949 parser.pending_fixed_value_.append(pfixed_token->value_);
1950 continue;
1951 }
1952
1953 const auto* popen_token = parser.try_consume_token(token::type::OPEN);
1954 // Example
1955 // Next we look for the sequence
1956 // <open><char prefix><name><regexp><char suffix><close><modifier>.
1957 // The open and close are necessary, but the other tokens are not.
1958 // TODO?: EXAMPLE 2
1959 if (popen_token) {
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);
1967 continue;
1968 }
1969
1970 parser.maybe_add_part_from_pending_fixed_value();
1971 parser.consume_required_token(token::type::END);
1972 } // while
1973
1974 return std::move(parser.part_list_);
1975}
1976
1977// https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp
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("]+?");
1982 return result;
1983}
1984
1985// https://urlpattern.spec.whatwg.org/#try-to-consume-a-token
1986UPA_CONSTEXPR_20 const token* pattern_parser::try_consume_token(token::type type) {
1987 // Assert: parser's index is less than parser's token list size.
1988 assert(index_ < token_list_.size());
1989
1990 const auto& next_token = token_list_[index_];
1991 if (next_token.type_ != type)
1992 return nullptr;
1993 ++index_;
1994 return &next_token;
1995}
1996
1997// https://urlpattern.spec.whatwg.org/#try-to-consume-a-modifier-token
1998UPA_CONSTEXPR_20 const token* pattern_parser::try_consume_modifier_token() {
1999 const auto* ptoken = try_consume_token(token::type::OTHER_MODIFIER);
2000 if (ptoken)
2001 return ptoken;
2002 return try_consume_token(token::type::ASTERISK);
2003}
2004
2005// https://urlpattern.spec.whatwg.org/#try-to-consume-a-regexp-or-wildcard-token
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);
2010 return ptoken;
2011}
2012
2013// https://urlpattern.spec.whatwg.org/#consume-a-required-token
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) {
2017 throw urlpattern_error("missing required token");
2018 }
2019 return *ptoken;
2020}
2021
2022// https://urlpattern.spec.whatwg.org/#consume-text
2023UPA_CONSTEXPR_20 std::string pattern_parser::consume_text() {
2024 std::string result;
2025 while (true) {
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;
2030 }
2031 result.append(ptoken->value_);
2032 }
2033 return result;
2034}
2035
2036// https://urlpattern.spec.whatwg.org/#maybe-add-a-part-from-the-pending-fixed-value
2037inline void pattern_parser::maybe_add_part_from_pending_fixed_value() {
2038 if (pending_fixed_value_.empty())
2039 return;
2040 auto encoded_value = encoding_cb_(pending_fixed_value_);
2041 pending_fixed_value_.clear(); // set to the empty string
2042 part_list_.emplace_back(part::type::FIXED_TEXT, std::move(encoded_value), part::modifier::none);
2043}
2044
2045// https://urlpattern.spec.whatwg.org/#add-a-part
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)
2049{
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;
2058 }
2059 if (pname_token == nullptr && pregexp_or_wildcard_token == nullptr && modifier == part::modifier::none) {
2060 // Note
2061 // This was a "{foo}" grouping. We add this to the pending fixed value
2062 // so that it will be combined with any previous or subsequent text.
2063 pending_fixed_value_.append(prefix);
2064 return;
2065 }
2066 maybe_add_part_from_pending_fixed_value();
2067 if (pname_token == nullptr && pregexp_or_wildcard_token == nullptr) {
2068 // Note
2069 // This was a "{foo}?" grouping. The modifier means we cannot combine
2070 // it with other text. Therefore we add it as a part immediately.
2071 assert(suffix.empty());
2072 if (prefix.empty())
2073 return;
2074 auto encoded_value = encoding_cb_(prefix);
2075 part_list_.emplace_back(part::type::FIXED_TEXT, std::move(encoded_value), modifier);
2076 return;
2077 }
2078
2079 std::string_view regexp_value{};
2080
2081 // Note
2082 // Next, we convert the regexp or wildcard token into a regular expression.
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;
2087 else
2088 regexp_value = pregexp_or_wildcard_token->value_;
2089
2090 part::type type = part::type::REGEXP;
2091
2092 // Note
2093 // Next, we convert regexp value into a part type. We make sure to go to a regular
2094 // expression first so that an equivalent "regexp" token will be treated the same
2095 // as a "name" or "asterisk" token.
2096 if (regexp_value == segment_wildcard_regexp_) {
2097 type = part::type::SEGMENT_WILDCARD;
2098 regexp_value = ""sv; // set to the empty string
2099 } else if (regexp_value == full_wildcard_regexp_value) {
2100 type = part::type::FULL_WILDCARD;
2101 regexp_value = ""sv; // set to the empty string
2102 }
2103
2104 std::string name{};
2105
2106 // Note
2107 // Next, we determine the part name. This can be explicitly provided by a
2108 // "name" token or be automatically assigned.
2109 if (pname_token) {
2110 name = pname_token->value_;
2111 } else if (pregexp_or_wildcard_token) {
2112 name = std::to_string(next_numeric_name_);
2113 ++next_numeric_name_;
2114 }
2115 if (is_duplicate_name(name))
2116 throw urlpattern_error("duplicate part name");
2117
2118 // Note
2119 // Finally, we encode the fixed text values and create the part.
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));
2125}
2126
2127// https://urlpattern.spec.whatwg.org/#is-a-duplicate-name
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;
2131 });
2132}
2133
2134// 2.2. Converting part lists to regular expressions
2135// https://urlpattern.spec.whatwg.org/#converting-part-lists-to-regular-expressions
2136
2137// https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list
2138UPA_CONSTEXPR_20 std::pair<std::string, string_list> generate_regular_expression_and_name_list(
2139 const part_list& pt_list, const options& opt)
2140{
2141 std::string result{ "^" };
2142 string_list name_list{};
2143
2144 // TODO?: use std::format instead of append, push_back
2145
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_);
2150 } else {
2151 // Note
2152 // A "fixed-text" part with a modifier uses a non capturing group.
2153 // It uses the following form:
2154 // (?:<fixed text>)<modifier>
2155 result.append("(?:");
2156 append_escape_regexp_string(result, pt.value_);
2157 result.push_back(')');
2158 append_convert_modifier_to_string(result, pt.modifier_);
2159 }
2160 continue;
2161 }
2162
2163 assert(!pt.name_.empty());
2164 name_list.emplace_back(pt.name_);
2165 // Note
2166 // We collect the list of matching group names in a parallel list. This is largely done for
2167 // legacy reasons to match path-to-regexp. We could attempt to convert this to use regular
2168 // expression named captured groups, but given the complexity of this algorithm there is a
2169 // real risk of introducing unintended bugs. In addition, if we ever end up exposing the
2170 // generated regular expressions to the web we would like to maintain compability with
2171 // path-to-regexp which has indicated its unlikely to switch to using named capture groups.
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;
2179 }
2180
2181 if (pt.prefix_.empty() && pt.suffix_.empty()) {
2182 // Note
2183 // If there is no prefix or suffix then generation depends on the modifier. If there
2184 // is no modifier or just the optional modifier, it uses the following simple form:
2185 // (<regexp value>)<modifier>
2186 //
2187 // If there is a repeating modifier, however, we will use the more complex form:
2188 // ((?:<regexp value>)<modifier>)
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_);
2194 } else {
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(')');
2200 }
2201 continue;
2202 }
2203
2204 if (pt.modifier_ == part::modifier::none || pt.modifier_ == part::modifier::optional) {
2205 // Note
2206 // This section handles non-repeating parts with a prefix or suffix. There is an inner capturing
2207 // group that contains the primary regexp value. The inner group is then combined with the prefix
2208 // or suffix in an outer non-capturing group. Finally the modifier is applied. The resulting form
2209 // is as follows.
2210 // (?:<prefix>(<regexp value>)<suffix>)<modifier>
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_);
2219 continue;
2220 }
2221
2222 assert(pt.modifier_ == part::modifier::zero_or_more || pt.modifier_ == part::modifier::one_or_more);
2223 assert(!pt.prefix_.empty() || !pt.suffix_.empty());
2224 // Note
2225 // Repeating parts with a prefix or suffix are dramatically more complicated. We want to exclude
2226 // the initial prefix and the final suffix, but include them between any repeated elements. To achieve
2227 // this we provide a separate initial expression that excludes the prefix. Then the expression is
2228 // duplicated with the prefix/suffix values included in an optional repeating element. If zero values
2229 // are permitted then a final optional modifier can be appended. The resulting form is as follows.
2230 // (?:<prefix>((?:<regexp value>)(?:<suffix><prefix>(?:<regexp value>))*)<suffix>)?
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('?');
2245 } // for
2246
2247 result.push_back('$');
2248
2249 return { result, name_list };
2250}
2251
2252// https://urlpattern.spec.whatwg.org/#escape-a-regexp-string
2253inline constexpr upa::code_point_set escape_regexp_set{ [](upa::code_point_set& self) constexpr {
2254 self.include({
2255 // . + * ? ^ $ { }
2256 0x2E, 0x2B, 0x2A, 0x3F, 0x5E, 0x24, 0x7B, 0x7D,
2257 // ( ) [ ] | / '\'
2258 0x28, 0x29, 0x5B, 0x5D, 0x7C, 0x2F, 0x5C });
2259} };
2260
2261UPA_CONSTEXPR_20 void append_escape_regexp_string(std::string& result, std::string_view input) {
2262 //TODO: assert(is_ascii_string(input));
2263 //TODO: optimize
2264 for (const auto c : input) {
2265 if (escape_regexp_set[c])
2266 result.push_back('\\');
2267 result.push_back(c);
2268 }
2269}
2270
2271
2272// 2.3. Converting part lists to pattern strings
2273// https://urlpattern.spec.whatwg.org/#converting-part-lists-to-pattern-strings
2274
2275// https://urlpattern.spec.whatwg.org/#generate-a-pattern-string
2276inline std::string generate_pattern_string(const part_list& pt_list, const options& opt) {
2277 std::string result;
2278
2279 for (std::size_t index = 0; index < pt_list.size(); ++index) {
2280 const auto& pt = pt_list[index];
2281 // pprevious_pt, pnext_pt will be defined below
2282 if (pt.type_ == part::type::FIXED_TEXT) {
2283 if (pt.modifier_ == part::modifier::none) {
2284 append_escape_pattern_string(result, pt.value_);
2285 continue;
2286 }
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_);
2291 continue;
2292 }
2293
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;
2296
2297 assert(!pt.name_.empty()); // MANO
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);
2301
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())
2305 {
2306 if (pnext_pt->type_ == part::type::FIXED_TEXT)
2307 needs_grouping = is_valid_name_code_point(get_code_point(pnext_pt->value_), false);
2308 else
2309 needs_grouping = upa::detail::is_ascii_digit(pnext_pt->name_[0]);
2310 }
2311 if (!needs_grouping && pt.prefix_.empty() &&
2312 pprevious_pt != nullptr && pprevious_pt->type_ == part::type::FIXED_TEXT &&
2313 // previous part's value's last code point is options's prefix code point
2314 !opt.prefix_code_point.empty() && !pprevious_pt->value_.empty() &&
2315 // TODO: code point (not necessary)
2316 pprevious_pt->value_.back() == opt.prefix_code_point[0])
2317 needs_grouping = true;
2318
2319 assert(/*TODO???: pt.name_ != nullptr && */ !pt.name_.empty());
2320
2321 if (needs_grouping)
2322 result.push_back('{');
2323
2324 append_escape_pattern_string(result, pt.prefix_);
2325 if (custom_name) {
2326 result.push_back(':');
2327 result.append(pt.name_);
2328 }
2329
2330 switch (pt.type_) {
2331 case part::type::REGEXP:
2332 result.push_back('(');
2333 result.append(pt.value_);
2334 result.push_back(')');
2335 break;
2336 case part::type::SEGMENT_WILDCARD:
2337 // 14. custom_name is false
2338 if (!custom_name) {
2339 result.push_back('(');
2340 // TODO: append_generate_segment_wildcard_regexp
2341 result.append(generate_segment_wildcard_regexp(opt));
2342 result.push_back(')');
2343 }
2344 // 16. custom_name is true
2345 else if (!pt.suffix_.empty() && is_valid_name_code_point(get_code_point(pt.suffix_), false)) {
2346 result.push_back('\\');
2347 }
2348 break;
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 ||
2354 needs_grouping ||
2355 !pt.prefix_.empty())) {
2356 result.push_back('*');
2357 } else {
2358 result.push_back('(');
2359 result.append(full_wildcard_regexp_value);
2360 result.push_back(')');
2361 }
2362 break;
2363 default:
2364 break;
2365 }
2366
2367 // 17. Append the result of running escape a pattern string given part's suffix
2368 // to the end of result.
2369 append_escape_pattern_string(result, pt.suffix_);
2370
2371 if (needs_grouping)
2372 result.push_back('}');
2373
2374 append_convert_modifier_to_string(result, pt.modifier_);
2375 } // for
2376
2377 return result;
2378}
2379
2380// https://urlpattern.spec.whatwg.org/#escape-a-pattern-string
2381inline constexpr upa::code_point_set escape_pattern_set{ [](upa::code_point_set& self) constexpr {
2382 self.include({
2383 // + * ? : { } ( ) '\'
2384 0x2B, 0x2A, 0x3F, 0x3A, 0x7B, 0x7D, 0x28, 0x29, 0x5C });
2385} };
2386
2387UPA_CONSTEXPR_20 std::string escape_pattern_string(std::string_view input) {
2388 std::string result;
2389 append_escape_pattern_string(result, input);
2390 return result;
2391}
2392
2393UPA_CONSTEXPR_20 void append_escape_pattern_string(std::string& result, std::string_view input) {
2394 //TODO: assert(is_ascii_string(input));
2395 //TODO: optimize
2396 for (const auto c : input) {
2397 if (escape_pattern_set[c])
2398 result.push_back('\\');
2399 result.push_back(c);
2400 }
2401}
2402
2403// https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string
2404UPA_CONSTEXPR_20 void append_convert_modifier_to_string(std::string& result, part::modifier modifier) {
2405 switch (modifier) {
2406 case part::modifier::zero_or_more:
2407 result.push_back('*');
2408 break;
2409 case part::modifier::optional:
2410 result.push_back('?');
2411 break;
2412 case part::modifier::one_or_more:
2413 result.push_back('+');
2414 break;
2415 default:
2416 break;
2417 }
2418}
2419
2420// 3. Canonicalization
2421// https://urlpattern.spec.whatwg.org/#canon
2422
2423// 3.1. Encoding callbacks
2424// https://urlpattern.spec.whatwg.org/#canon-encoding-callbacks
2425
2426// TODO: (optimize): write a protocol parser to avoid having to use a URL parser
2427
2428// https://urlpattern.spec.whatwg.org/#canonicalize-a-protocol
2429inline std::string canonicalize_protocol(std::string_view value) {
2430 if (value.empty()) return {};
2431
2432 // * Let parseResult (res_url) be the result of running the basic URL parser given
2433 // value followed by "://dummy.invalid/".
2434 // HACK: To improve performance, we use "h" instead of "dummy.invalid".
2435 // TODO: concatenate strings
2436 std::string inp(value);
2437 inp.append("://h/");
2438
2439 // Note, state override is not used here because it enforces restrictions that are only appropriate for the
2440 // protocol setter. Instead we use the protocol to parse a dummy URL using the normal parsing entry point.
2441 upa::url res_url;
2442 if (!upa::success(res_url.parse(inp, nullptr)))
2443 throw urlpattern_error("invalid protocol");
2444 return std::string{ res_url.get_part_view(upa::url::SCHEME) };
2445}
2446
2447// https://urlpattern.spec.whatwg.org/#canonicalize-a-username
2448inline std::string canonicalize_username(std::string_view value) {
2449 if (value.empty()) return {};
2450
2451 std::string result;
2452 upa::detail::append_utf8_percent_encoded(value.data(), value.data() + value.size(), upa::userinfo_no_encode_set, result);
2453 return result;
2454}
2455
2456// https://urlpattern.spec.whatwg.org/#canonicalize-a-password
2457inline std::string canonicalize_password(std::string_view value) {
2458 return canonicalize_username(value);
2459}
2460
2461// https://urlpattern.spec.whatwg.org/#canonicalize-a-hostname
2462inline std::string canonicalize_hostname(std::string_view value) {
2463 if (value.empty()) return {};
2464
2465 // * Let dummyURL be the result of creating a dummy URL.
2466 // * Let parseResult be the result of running the basic URL parser given value
2467 // with dummyURL as url and hostname state as state override.
2468 upa::url dummy_url{};
2469 {
2470 upa::detail::url_serializer urls(dummy_url);
2471 urls.set_scheme("https");
2472
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);
2476
2477 // TODO: Optimization possibility:
2478 // Remove all ASCII tab or newline from input, find end of host (:, /, ?, #) and then:
2479 // const auto parse_result = upa::detail::url_parser::parse_host(urls, inp.begin(), inp.end());
2480
2481 if (!upa::success(parse_result))
2482 throw urlpattern_error("canonicalize a hostname error");
2483 }
2484 return std::string{ dummy_url.get_part_view(upa::url::HOST) };
2485}
2486
2487// https://urlpattern.spec.whatwg.org/#canonicalize-an-ipv6-hostname
2488inline std::string canonicalize_ipv6_hostname(std::string_view value) {
2489 std::string result;
2490
2491 // TODO: code point (not necessary)
2492 for (const auto cp : value) {
2493 if (!upa::detail::is_hex_char(cp) && cp != '[' && cp != ']' && cp != ':')
2494 throw urlpattern_error("canonicalize an IPv6 hostname error");
2495 result.push_back(upa::util::ascii_to_lower_char(cp));
2496 }
2497 return result;
2498}
2499
2500// https://urlpattern.spec.whatwg.org/#canonicalize-a-port
2501inline std::string canonicalize_port(std::string_view port_value, std::optional<std::string_view> protocol_value) {
2502 if (port_value.empty()) return {};
2503
2504 // * Let dummyURL be the result of creating a dummy URL.
2505 // * If protocolValue was given, then set dummyURL’s scheme to protocolValue.
2506 // * Let parseResult be the result of running basic URL parser given portValue
2507 // with dummyURL as url and port state as state override.
2508 upa::url dummy_url{};
2509 {
2510 upa::detail::url_serializer urls(dummy_url);
2511 if (protocol_value)
2512 urls.set_scheme(*protocol_value);
2513 else
2514 urls.set_scheme("");
2515 // Note, we set the URL record's scheme in order for the basic URL parser
2516 // to recognize and normalize default port values.
2517
2518 // HACK: To improve performance, we use "h" instead of "dummy.invalid".
2519 urls.hostStart().push_back('h');
2520 urls.hostDone(upa::HostType::Domain);
2521
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);
2525 if (!upa::success(parse_result))
2526 throw urlpattern_error("canonicalize a port error");
2527 }
2528 return std::string{ dummy_url.get_part_view(upa::url::PORT) };
2529}
2530
2531// https://urlpattern.spec.whatwg.org/#canonicalize-a-pathname
2532inline std::string canonicalize_pathname(std::string_view value) {
2533 if (value.empty()) return {};
2534
2535 // Let leading slash be true if the first code point
2536 // in value is U+002F (/) and otherwise false
2537 const bool leading_slash = value[0] == '/';
2538
2539 upa::url dummy_url{};
2540 {
2541 std::string modified_value;
2542 const auto inp = [&]() {
2543 if (leading_slash)
2544 return upa::make_str_arg(value);
2545 // Let modified value be "/-" if leading slash is false
2546 modified_value = "/-"sv;
2547 // Append value to the end of modified value
2548 modified_value.append(value);
2549 // Note
2550 // The URL parser will automatically prepend a leading slash to the canonicalized pathname.
2551 // This does not work here unfortunately. This algorithm is called for pieces of the pathname,
2552 // instead of the entire pathname, when used as an encoding callback. Therefore we disable the
2553 // prepending of the slash by inserting our own. An additional character is also inserted here
2554 // in order to avoid inadvertantly collapsing a leading dot due to the fake leading slash
2555 // being interpreted as a "/." sequence. These inserted characters are then removed from the
2556 // result below.
2557 //
2558 // Note, implementations are free to simply disable slash prepending in their URL parsing code
2559 // instead of paying the performance penalty of inserting and removing characters in this
2560 // algorithm.
2561 return upa::make_str_arg(modified_value);
2562 }();
2563
2564 // * Let dummyURL be the result of creating a dummy URL.
2565 // * Empty dummyURL’s path.
2566 upa::detail::url_serializer urls(dummy_url);
2567 urls.set_scheme("https");
2568 /*** This code is unnecessary ***
2569 // HACK: To improve performance, we use "h" instead of "dummy.invalid".
2570 urls.hostStart().push_back('h');
2571 urls.hostDone(upa::HostType::Domain);
2572 ***/
2573
2574 // * Run basic URL parser given modified value with dummyURL as url and
2575 // path start state as state override.
2576 upa::detail::url_parser::url_parse(urls, inp.begin(), inp.end(), nullptr,
2577 upa::detail::url_parser::path_start_state);
2578 }
2579 auto result = dummy_url.get_part_view(upa::url::PATH);
2580 // If leading slash is false, then set result to the code point
2581 // substring from 2 to the end of the string within result.
2582 if (!leading_slash) {
2583 // The result length may be less than 2. For example, if the value is "path/..",
2584 // then the modified_value is "/-path/..", and the result is "/".
2585 if (result.length() <= 2)
2586 return {};
2587 result.remove_prefix(2);
2588 }
2589 return std::string{ result };
2590}
2591
2592// https://urlpattern.spec.whatwg.org/#canonicalize-an-opaque-pathname
2593inline std::string canonicalize_opaque_pathname(std::string_view value) {
2594 if (value.empty()) return {};
2595
2596 // * Let dummyURL be the result of creating a dummy URL.
2597 // * Set dummyURL’s path to the empty string.
2598 // * Let parseResult be the result of running URL parsing given value with
2599 // dummyURL as url and opaque path state as state override.
2600 upa::url dummy_url{};
2601 {
2602 upa::detail::url_serializer urls(dummy_url);
2603 // Set dummyURL’s path to the empty string (so path becomes opaque,
2604 // see: https://url.spec.whatwg.org/#url-opaque-path)
2605 urls.set_has_opaque_path();
2606
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);
2610 if (!upa::success(parse_result))
2611 throw urlpattern_error("canonicalize an opaque pathname error");
2612 }
2613 return std::string{ dummy_url.get_part_view(upa::url::PATH) };
2614}
2615
2616// https://urlpattern.spec.whatwg.org/#canonicalize-a-search
2617inline std::string canonicalize_search(std::string_view value) {
2618 if (value.empty()) return {};
2619
2620 // * Let dummyURL be the result of creating a dummy URL.
2621 // * Set dummyURL’s query to the empty string.
2622 // * Run basic URL parser given value with dummyURL as url and query state as state override.
2623 upa::url dummy_url{};
2624 {
2625 upa::detail::url_serializer urls(dummy_url);
2626 // Setting the scheme to special ensures that the query will be percent
2627 // encoded using the special-query percent-encode set.
2628 urls.set_scheme("https");
2629 // HACK: To improve performance, we use "h" instead of "dummy.invalid".
2630 urls.hostStart().push_back('h');
2631 urls.hostDone(upa::HostType::Domain);
2632
2633 // Set dummyURL's query to the empty string.
2634 //TODO: urls.set_flag(upa::url::QUERY_FLAG);
2635
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);
2639 }
2640 return std::string{ dummy_url.get_part_view(upa::url::QUERY) };
2641}
2642
2643// https://urlpattern.spec.whatwg.org/#canonicalize-a-hash
2644inline std::string canonicalize_hash(std::string_view value) {
2645 if (value.empty()) return {};
2646
2647 // * Let dummyURL be the result of creating a dummy URL.
2648 // * Set dummyURL’s fragment to the empty string.
2649 // * Run basic URL parser given value with dummyURL as url and fragment state as state override.
2650 upa::url dummy_url{};
2651 {
2652 upa::detail::url_serializer urls(dummy_url);
2653 /*** This code is unnecessary ***
2654 urls.set_scheme("https");
2655 // HACK: To improve performance, we use "h" instead of "dummy.invalid".
2656 urls.hostStart().push_back('h');
2657 urls.hostDone(upa::HostType::Domain);
2658 ***/
2659
2660 // Set dummyURL's fragment to the empty string.
2661 //TODO: urls.set_flag(upa::url::FRAGMENT_FLAG);
2662
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);
2666 }
2667 return std::string{ dummy_url.get_part_view(upa::url::FRAGMENT) };
2668}
2669
2670
2671// 3.2. URLPatternInit processing
2672// https://urlpattern.spec.whatwg.org/#canon-processing-for-init
2673
2674UPA_CONSTEXPR_20 std::string process_base_url_string(std::string_view input, urlpattern_init_type type);
2675// input - pattern string to check
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);
2687
2688// https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit
2689inline urlpattern_init process_urlpattern_init(const urlpattern_init& init, urlpattern_init_type type, bool set_empty) {
2690 urlpattern_init result;
2691
2692 if (set_empty) {
2693 result.protocol = ""sv;
2694 result.username = ""sv;
2695 result.password = ""sv;
2696 result.hostname = ""sv;
2697 result.port = ""sv;
2698 result.pathname = ""sv;
2699 result.search = ""sv;
2700 result.hash = ""sv;
2701 }
2702
2703 std::optional<upa::url> base_url = std::nullopt;
2704 if (init.base_url) {
2705 // construct and parse base_url
2706 base_url.emplace();
2707 if (!upa::success(base_url->parse(*init.base_url, nullptr)))
2708 throw urlpattern_error("invalid base URL");
2709
2710 if (!init.protocol)
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);
2715 if (!init.password)
2716 result.password = process_base_url_string(base_url->get_part_view(upa::url::PASSWORD), type);
2717 }
2718 if (!init.protocol && !init.hostname) {
2719 result.hostname = process_base_url_string(base_url->get_part_view(upa::url::HOST), type);
2720 if (!init.port) {
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);
2724 if (!init.search) {
2725 result.search = process_base_url_string(base_url->get_part_view(upa::url::QUERY), type);
2726 if (!init.hash)
2727 result.hash = process_base_url_string(base_url->get_part_view(upa::url::FRAGMENT), type);
2728 }
2729 }
2730 }
2731 }
2732 }
2733
2734 if (init.protocol)
2735 result.protocol = process_protocol_for_init(*init.protocol, type);
2736 if (init.username)
2737 result.username = process_username_for_init(*init.username, type);
2738 if (init.password)
2739 result.password = process_password_for_init(*init.password, type);
2740 if (init.hostname)
2741 result.hostname = process_hostname_for_init(*init.hostname, type);
2742 // Let resultProtocolString be result["protocol"] if it exists; otherwise the empty string
2743 const std::string_view result_protocol_string = result.protocol ? *result.protocol : ""sv;
2744 if (init.port)
2745 result.port = process_port_for_init(*init.port, result_protocol_string, type);
2746 if (init.pathname) {
2747 std::string new_pathname; // must have lifetime as result_pathname
2748
2749 // Set result["pathname"] to init["pathname"]
2750 std::string_view result_pathname = *init.pathname;
2751
2752 // If the following are all true:
2753 // * baseURL is not null;
2754 // * baseURL does not have an opaque path; and
2755 // * the result of running is an absolute pathname given result["pathname"] and type is false,
2756 if (base_url && !base_url->has_opaque_path() &&
2757 !is_absolute_pathname(result_pathname, type))
2758 {
2759 const auto base_url_path = process_base_url_string(base_url->get_part_view(upa::url::PATH), type);
2760 // Let slash index be the index of the last U+002F (/) code point found in baseURLPath, interpreted
2761 // as a sequence of code points, or null if there are no instances of the code point.
2762 const auto slash_index = base_url_path.rfind('/'); // TODO: code point (not necessary)
2763 // If slash index is not null:
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;
2768 }
2769 }
2770 result.pathname = process_pathname_for_init(result_pathname, result_protocol_string, type);
2771 }
2772 if (init.search)
2773 result.search = process_search_for_init(*init.search, type);
2774 if (init.hash)
2775 result.hash = process_hash_for_init(*init.hash, type);
2776
2777 return result;
2778}
2779
2780// https://urlpattern.spec.whatwg.org/#process-a-base-url-string
2781UPA_CONSTEXPR_20 std::string process_base_url_string(std::string_view input, urlpattern_init_type type) {
2782 if (input.empty()) return {}; // MANO: optimization
2783 if (type != urlpattern_init_type::PATTERN)
2784 return std::string(input);
2785 return escape_pattern_string(input);
2786}
2787
2788// https://urlpattern.spec.whatwg.org/#is-an-absolute-pathname
2789constexpr bool is_absolute_pathname(std::string_view input, urlpattern_init_type type) noexcept {
2790 if (input.empty()) return false;
2791
2792 if (input[0] == '/')
2793 return true;
2794 if (type == urlpattern_init_type::URL)
2795 return false;
2796
2797 // TODO: code point (not necessary)
2798 if (input.length() < 2)
2799 return false;
2800 if (input[0] == '\\' && input[1] == '/')
2801 return true;
2802 if (input[0] == '{' && input[1] == '/')
2803 return true;
2804 return false;
2805}
2806
2807// https://urlpattern.spec.whatwg.org/#process-protocol-for-init
2808inline std::string process_protocol_for_init(std::string_view value, urlpattern_init_type type) {
2809 // Let strippedValue be the given value with a single trailing U+003A (:) removed, if any.
2810 const std::string_view stripped_value =
2811 (!value.empty() && value.back() == ':')
2812 ? value.substr(0, value.length() - 1)
2813 : value;
2814 if (type == urlpattern_init_type::PATTERN)
2815 return std::string{ stripped_value };
2816 return canonicalize_protocol(stripped_value);
2817}
2818
2819// https://urlpattern.spec.whatwg.org/#process-username-for-init
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);
2824}
2825
2826// https://urlpattern.spec.whatwg.org/#process-password-for-init
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);
2831}
2832
2833// https://urlpattern.spec.whatwg.org/#process-hostname-for-init
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);
2838}
2839
2840// https://urlpattern.spec.whatwg.org/#process-port-for-init
2841inline std::string process_port_for_init(std::string_view port_value,
2842 std::string_view protocol_value, urlpattern_init_type type)
2843{
2844 if (type == urlpattern_init_type::PATTERN)
2845 return std::string{ port_value };
2846 return canonicalize_port(port_value, protocol_value);
2847}
2848
2849// https://urlpattern.spec.whatwg.org/#process-pathname-for-init
2850inline std::string process_pathname_for_init(std::string_view pathname_value,
2851 std::string_view protocol_value, urlpattern_init_type type)
2852{
2853 if (type == urlpattern_init_type::PATTERN)
2854 return std::string{ pathname_value };
2855 // If protocolValue is a special scheme or the empty string, then return
2856 // the result of running canonicalize a pathname given pathnameValue.
2857 if (protocol_value.empty() || is_special_scheme(protocol_value))
2858 return canonicalize_pathname(pathname_value);
2859 // Note
2860 // If the protocolValue is the empty string then no value was provided for protocol in the constructor
2861 // dictionary. Normally we do not special case empty string dictionary values, but in this case we
2862 // treat it as a special scheme in order to default to the most common pathname canonicalization.
2863 return canonicalize_opaque_pathname(pathname_value);
2864}
2865
2866// https://urlpattern.spec.whatwg.org/#process-search-for-init
2867inline std::string process_search_for_init(std::string_view value, urlpattern_init_type type) {
2868 // Let strippedValue be the given value with a single leading U+003F (?) removed, if any.
2869 const std::string_view stripped_value =
2870 (!value.empty() && value.front() == '?')
2871 ? value.substr(1)
2872 : value;
2873 if (type == urlpattern_init_type::PATTERN)
2874 return std::string{ stripped_value };
2875 return canonicalize_search(stripped_value);
2876}
2877
2878// https://urlpattern.spec.whatwg.org/#process-hash-for-init
2879inline std::string process_hash_for_init(std::string_view value, urlpattern_init_type type) {
2880 // Let strippedValue be the given value with a single leading U+0023 (#) removed, if any.
2881 const std::string_view stripped_value =
2882 (!value.empty() && value.front() == '#')
2883 ? value.substr(1)
2884 : value;
2885 if (type == urlpattern_init_type::PATTERN)
2886 return std::string{ stripped_value };
2887 return canonicalize_hash(stripped_value);
2888}
2889
2890
2891} // namespace pattern
2892} // namespace upa
2893
2894#endif // UPA_URLPATTERN_H
URL class.
Definition url.h:84
bool is_valid() const noexcept
Returns whether the URL is valid.
Definition url.h:1319
@ QUERY
Definition url.h:98
@ USERNAME
Definition url.h:91
@ PORT
Definition url.h:95
@ HOST
Definition url.h:94
@ PATH
Definition url.h:97
@ SCHEME
Definition url.h:89
@ FRAGMENT
Definition url.h:99
@ PASSWORD
Definition url.h:92
bool href(const StrT &str)
The href setter.
Definition url.h:1494
validation_errc parse(const T &str_url, const url *base=nullptr)
Parses given URL string against base URL.
Definition url.h:199
std::string_view get_part_view(PartType t) const
Gets URL's part (URL record member) as string.
Definition url.h:1323
urlpattern exception class
Definition urlpattern.h:852
urlpattern_error(const char *what_arg)
Definition urlpattern.h:857
array_type::value_type value_type
Definition urlpattern.h:482
array_type::const_iterator const_iterator
Definition urlpattern.h:490
constexpr urlpattern_inputs(const urlpattern_init &init) noexcept
Definition urlpattern.h:501
constexpr const_iterator end() const noexcept
Definition urlpattern.h:512
constexpr size_type size() const noexcept
Definition urlpattern.h:515
constexpr const_iterator begin() const noexcept
Definition urlpattern.h:511
array_type::size_type size_type
Definition urlpattern.h:483
array_type::const_reference const_reference
Definition urlpattern.h:486
array_type::const_iterator iterator
Definition urlpattern.h:489
array_type::const_pointer pointer
Definition urlpattern.h:487
std::array< urlpattern_input, 2 > array_type
Definition urlpattern.h:481
constexpr const_reference operator[](size_type pos) const
Definition urlpattern.h:506
array_type::difference_type difference_type
Definition urlpattern.h:484
constexpr urlpattern_inputs() noexcept=default
array_type::const_reference reference
Definition urlpattern.h:485
array_type::const_pointer const_pointer
Definition urlpattern.h:488
constexpr bool empty() const noexcept
Definition urlpattern.h:514
URL pattern class template.
Definition urlpattern.h:623
bool test(const urlpattern_init &input) const
Test whether URL pattern matches the input.
Definition urlpattern.h:994
std::string_view get_protocol() const noexcept
Definition urlpattern.h:958
urlpattern(const urlpattern_init &init={}, urlpattern_options opt={})
Constructs urlpattern object from upa::urlpattern_init object.
Definition urlpattern.h:894
std::string_view get_password() const noexcept
Definition urlpattern.h:966
std::string_view get_pathname() const noexcept
Definition urlpattern.h:978
std::string_view get_search() const noexcept
Definition urlpattern.h:982
urlpattern(const T &input, TB &&base_url, urlpattern_options opt={})
Constructs urlpattern object from URL pattern string and optional base URL string.
Definition urlpattern.h:661
std::string_view get_hash() const noexcept
Definition urlpattern.h:986
urlpattern(const T &input, urlpattern_options opt={})
Constructs urlpattern object from URL pattern string.
Definition urlpattern.h:677
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
Definition urlpattern.h:974
std::string_view get_username() const noexcept
Definition urlpattern.h:962
std::string_view get_hostname() const noexcept
Definition urlpattern.h:970
Definition url.h:3418
Definition url.h:50
constexpr bool success(validation_errc res) noexcept
Check validation error code indicates success.
Definition url_result.h:98
constexpr bool is_regex_engine_v
Definition urlpattern.h:140
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
Definition urlpattern.h:462
URLPatternComponentResult struct.
Definition urlpattern.h:550
std::unordered_map< std::string_view, std::optional< std::string > > groups
Definition urlpattern.h:552
URLPatternInit struct.
Definition urlpattern.h:159
std::optional< std::string > pathname
Definition urlpattern.h:165
std::optional< std::string > protocol
Definition urlpattern.h:160
std::optional< std::string > hash
Definition urlpattern.h:167
std::optional< std::string > username
Definition urlpattern.h:161
std::optional< std::string_view > get(std::string_view name) const
Get value of member by name.
Definition urlpattern.h:184
std::optional< std::string > password
Definition urlpattern.h:162
std::optional< std::string > base_url
Definition urlpattern.h:168
std::optional< std::string > search
Definition urlpattern.h:166
constexpr bool operator==(const urlpattern_init &other) const
Definition urlpattern.h:173
std::optional< std::string > hostname
Definition urlpattern.h:163
std::optional< std::string > port
Definition urlpattern.h:164
void set(std::string_view name, T &&value)
Set value of member by name.
Definition urlpattern.h:194
The result of executing the URL pattern when there is a match.
Definition urlpattern.h:581
The result of executing the URL pattern when there is a match.
Definition urlpattern.h:562
urlpattern_component_result protocol
Definition urlpattern.h:563
urlpattern_component_result username
Definition urlpattern.h:564
urlpattern_component_result search
Definition urlpattern.h:569
urlpattern_component_result hostname
Definition urlpattern.h:566
urlpattern_component_result password
Definition urlpattern.h:565
urlpattern_component_result pathname
Definition urlpattern.h:568
urlpattern_component_result hash
Definition urlpattern.h:570
urlpattern_component_result port
Definition urlpattern.h:567