src/detail/pattern.cpp

100.0% Lines (360 / 360) 100.0% Functions (10 / 10)
pattern.cpp
f(x) Functions (10)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/url
8 //
9
10
11 #include <boost/url/detail/config.hpp>
12 #include "pattern.hpp"
13 #include "pct_format.hpp"
14 #include "boost/url/detail/replacement_field_rule.hpp"
15 #include <boost/url/grammar/alpha_chars.hpp>
16 #include <boost/url/grammar/optional_rule.hpp>
17 #include <boost/url/grammar/token_rule.hpp>
18 #include <boost/url/rfc/detail/charsets.hpp>
19 #include <boost/url/rfc/detail/host_rule.hpp>
20 #include <boost/url/rfc/detail/path_rules.hpp>
21 #include <boost/url/rfc/detail/port_rule.hpp>
22 #include <boost/url/rfc/detail/scheme_rule.hpp>
23
24 namespace boost {
25 namespace urls {
26 namespace detail {
27
28 static constexpr auto lhost_chars = host_chars + ':';
29
30 void
31 161x pattern::
32 apply(
33 url_base& u,
34 format_args const& args) const
35 {
36 // measure total
37 struct sizes
38 {
39 std::size_t scheme = 0;
40 std::size_t user = 0;
41 std::size_t pass = 0;
42 std::size_t host = 0;
43 std::size_t port = 0;
44 std::size_t path = 0;
45 std::size_t query = 0;
46 std::size_t frag = 0;
47 };
48 161x sizes n;
49
50 161x format_parse_context pctx(nullptr, nullptr, 0);
51 161x measure_context mctx(args);
52 161x if (!scheme.empty())
53 {
54 70x pctx = {scheme, pctx.next_arg_id()};
55 70x n.scheme = pct_vmeasure(
56 grammar::alpha_chars, pctx, mctx);
57 70x mctx.advance_to(0);
58 }
59 161x if (has_authority)
60 {
61 62x if (has_user)
62 {
63 10x pctx = {user, pctx.next_arg_id()};
64 10x n.user = pct_vmeasure(
65 user_chars, pctx, mctx);
66 10x mctx.advance_to(0);
67 10x if (has_pass)
68 {
69 7x pctx = {pass, pctx.next_arg_id()};
70 7x n.pass = pct_vmeasure(
71 password_chars, pctx, mctx);
72 7x mctx.advance_to(0);
73 }
74 }
75 62x if (host.starts_with('['))
76 {
77 2x BOOST_ASSERT(host.ends_with(']'));
78 2x pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
79 2x n.host = pct_vmeasure(
80 2x lhost_chars, pctx, mctx) + 2;
81 2x mctx.advance_to(0);
82 }
83 else
84 {
85 60x pctx = {host, pctx.next_arg_id()};
86 60x n.host = pct_vmeasure(
87 host_chars, pctx, mctx);
88 60x mctx.advance_to(0);
89 }
90 62x if (has_port)
91 {
92 22x pctx = {port, pctx.next_arg_id()};
93 22x n.port = pct_vmeasure(
94 grammar::digit_chars, pctx, mctx);
95 22x mctx.advance_to(0);
96 }
97 }
98 161x if (!path.empty())
99 {
100 123x pctx = {path, pctx.next_arg_id()};
101 123x n.path = pct_vmeasure(
102 path_chars, pctx, mctx);
103 121x mctx.advance_to(0);
104 }
105 159x if (has_query)
106 {
107 13x pctx = {query, pctx.next_arg_id()};
108 13x n.query = pct_vmeasure(
109 query_chars, pctx, mctx);
110 13x mctx.advance_to(0);
111 }
112 159x if (has_frag)
113 {
114 7x pctx = {frag, pctx.next_arg_id()};
115 7x n.frag = pct_vmeasure(
116 fragment_chars, pctx, mctx);
117 7x mctx.advance_to(0);
118 }
119 159x std::size_t const n_total =
120 159x n.scheme +
121 159x (n.scheme != 0) * 1 + // ":"
122 159x has_authority * 2 + // "//"
123 159x n.user +
124 159x has_pass * 1 + // ":"
125 159x n.pass +
126 159x has_user * 1 + // "@"
127 159x n.host +
128 159x has_port * 1 + // ":"
129 159x n.port +
130 159x n.path +
131 159x has_query * 1 + // "?"
132 159x n.query +
133 159x has_frag * 1 + // "#"
134 159x n.frag;
135 159x u.reserve(n_total);
136
137 // Apply
138 158x pctx = {nullptr, nullptr, 0};
139 158x format_context fctx(nullptr, args);
140 158x url_base::op_t op(u);
141 using parts = parts_base;
142 158x if (!scheme.empty())
143 {
144 138x auto dest = u.resize_impl(
145 parts::id_scheme,
146 69x n.scheme + 1, op);
147 69x pctx = {scheme, pctx.next_arg_id()};
148 69x fctx.advance_to(dest);
149 69x const char* dest1 = pct_vformat(
150 grammar::alpha_chars, pctx, fctx);
151 69x dest[n.scheme] = ':';
152 // validate
153 69x if (!grammar::parse({dest, dest1}, scheme_rule()))
154 {
155 1x throw_invalid_argument();
156 }
157 }
158 157x if (has_authority)
159 {
160 60x if (has_user)
161 {
162 10x auto dest = u.set_user_impl(
163 n.user, op);
164 10x pctx = {user, pctx.next_arg_id()};
165 10x fctx.advance_to(dest);
166 10x char const* dest1 = pct_vformat(
167 user_chars, pctx, fctx);
168 10x u.impl_.decoded_[parts::id_user] =
169 10x detail::to_size_type(
170 10x pct_string_view(dest, dest1 - dest)
171 ->decoded_size());
172 10x if (has_pass)
173 {
174 7x char* destp = u.set_password_impl(
175 n.pass, op);
176 7x pctx = {pass, pctx.next_arg_id()};
177 7x fctx.advance_to(destp);
178 7x dest1 = pct_vformat(
179 password_chars, pctx, fctx);
180 7x u.impl_.decoded_[parts::id_pass] =
181 7x detail::to_size_type(
182 14x pct_string_view({destp, dest1})
183 ->decoded_size());
184 }
185 }
186 60x auto dest = u.set_host_impl(
187 n.host, op);
188 60x if (host.starts_with('['))
189 {
190 2x BOOST_ASSERT(host.ends_with(']'));
191 2x pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
192 2x *dest++ = '[';
193 2x fctx.advance_to(dest);
194 char* dest1 =
195 2x pct_vformat(lhost_chars, pctx, fctx);
196 2x *dest1++ = ']';
197 2x u.impl_.decoded_[parts::id_host] =
198 2x detail::to_size_type(
199 4x pct_string_view(dest - 1, dest1 - dest + 1)
200 ->decoded_size());
201 }
202 else
203 {
204 58x pctx = {host, pctx.next_arg_id()};
205 58x fctx.advance_to(dest);
206 char const* dest1 =
207 58x pct_vformat(host_chars, pctx, fctx);
208 58x u.impl_.decoded_[parts::id_host] =
209 58x detail::to_size_type(
210 116x pct_string_view(dest, dest1 - dest)
211 ->decoded_size());
212 }
213 60x auto uh = u.encoded_host();
214 60x auto h = grammar::parse(uh, host_rule).value();
215 60x std::memcpy(
216 60x u.impl_.ip_addr_,
217 h.addr,
218 sizeof(u.impl_.ip_addr_));
219 60x u.impl_.host_type_ = h.host_type;
220 60x if (has_port)
221 {
222 22x dest = u.set_port_impl(n.port, op);
223 22x pctx = {port, pctx.next_arg_id()};
224 22x fctx.advance_to(dest);
225 22x char const* dest1 = pct_vformat(
226 grammar::digit_chars, pctx, fctx);
227 22x core::string_view up = {dest - 1, dest1};
228 22x auto p = grammar::parse(up, detail::port_part_rule).value();
229 22x if (p.has_port)
230 22x u.impl_.port_number_ = p.port_number;
231 }
232 }
233 157x if (!path.empty())
234 {
235 121x auto dest = u.resize_impl(
236 parts::id_path,
237 n.path, op);
238 121x pctx = {path, pctx.next_arg_id()};
239 121x fctx.advance_to(dest);
240 121x auto dest1 = pct_vformat(
241 path_chars, pctx, fctx);
242 121x pct_string_view npath(dest, dest1 - dest);
243 121x u.impl_.decoded_[parts::id_path] +=
244 121x detail::to_size_type(
245 npath.decoded_size());
246 121x if (!npath.empty())
247 {
248 242x u.impl_.nseg_ = detail::to_size_type(
249 121x std::count(
250 121x npath.begin() + 1,
251 242x npath.end(), '/') + 1);
252 }
253 // handle edge cases
254 // 1) path is first component and the
255 // first segment contains an unencoded ':'
256 // This is impossible because the template
257 // "{}" would be a host.
258 204x if (u.scheme().empty() &&
259 83x !u.has_authority())
260 {
261 83x auto fseg = u.encoded_segments().front();
262 83x std::size_t nc = std::count(
263 83x fseg.begin(), fseg.end(), ':');
264 83x if (nc)
265 {
266 6x std::size_t diff = nc * 2;
267 6x u.reserve(n_total + diff);
268 12x dest = u.resize_impl(
269 parts::id_path,
270 6x n.path + diff, op);
271 6x char* dest0 = dest + diff;
272 6x std::memmove(dest0, dest, n.path);
273 38x while (dest0 != dest)
274 {
275 32x if (*dest0 != ':')
276 {
277 22x *dest++ = *dest0++;
278 }
279 else
280 {
281 10x *dest++ = '%';
282 10x *dest++ = '3';
283 10x *dest++ = 'A';
284 10x dest0++;
285 }
286 }
287 6x n.path += diff;
288 }
289 }
290 // 2) url has no authority and path
291 // starts with "//"
292 213x if (!u.has_authority() &&
293 213x u.encoded_path().starts_with("//"))
294 {
295 2x u.reserve(n_total + 2);
296 4x dest = u.resize_impl(
297 parts::id_path,
298 2x n.path + 2, op);
299 2x std::memmove(dest + 2, dest, n.path);
300 2x *dest++ = '/';
301 2x *dest = '.';
302 }
303 }
304 157x if (has_query)
305 {
306 26x auto dest = u.resize_impl(
307 parts::id_query,
308 13x n.query + 1, op);
309 13x *dest++ = '?';
310 13x pctx = {query, pctx.next_arg_id()};
311 13x fctx.advance_to(dest);
312 13x auto dest1 = pct_vformat(
313 query_chars, pctx, fctx);
314 13x pct_string_view nquery(dest, dest1 - dest);
315 13x u.impl_.decoded_[parts::id_query] +=
316 13x detail::to_size_type(
317 13x nquery.decoded_size() + 1);
318 13x if (!nquery.empty())
319 {
320 26x u.impl_.nparam_ = detail::to_size_type(
321 13x std::count(
322 nquery.begin(),
323 26x nquery.end(), '&') + 1);
324 }
325 }
326 157x if (has_frag)
327 {
328 14x auto dest = u.resize_impl(
329 parts::id_frag,
330 7x n.frag + 1, op);
331 7x *dest++ = '#';
332 7x pctx = {frag, pctx.next_arg_id()};
333 7x fctx.advance_to(dest);
334 7x auto dest1 = pct_vformat(
335 fragment_chars, pctx, fctx);
336 7x u.impl_.decoded_[parts::id_frag] +=
337 7x detail::to_size_type(
338 14x make_pct_string_view(
339 7x core::string_view(dest, dest1 - dest))
340 7x ->decoded_size() + 1);
341 }
342 158x }
343
344 // This rule represents a pct-encoded string
345 // that contains an arbitrary number of
346 // replacement ids in it
347 template<class CharSet>
348 struct pct_encoded_fmt_string_rule_t
349 {
350 using value_type = pct_string_view;
351
352 constexpr
353 pct_encoded_fmt_string_rule_t(
354 CharSet const& cs) noexcept
355 : cs_(cs)
356 {
357 }
358
359 template<class CharSet_>
360 friend
361 constexpr
362 auto
363 pct_encoded_fmt_string_rule(
364 CharSet_ const& cs) noexcept ->
365 pct_encoded_fmt_string_rule_t<CharSet_>;
366
367 system::result<value_type>
368 301x parse(
369 char const*& it,
370 char const* end) const noexcept
371 {
372 301x auto const start = it;
373 301x if(it == end)
374 {
375 // this might be empty
376 1x return {};
377 }
378
379 // consume some with literal rule
380 // this might be an empty literal
381 300x auto literal_rule = pct_encoded_rule(cs_);
382 300x auto rv = literal_rule.parse(it, end);
383 580x while (rv)
384 {
385 580x auto it0 = it;
386 // consume some with replacement id
387 // rule
388 580x if (!replacement_field_rule.parse(it, end))
389 {
390 300x it = it0;
391 300x break;
392 }
393 280x rv = literal_rule.parse(it, end);
394 }
395
396 300x return core::string_view(start, it - start);
397 }
398
399 private:
400 CharSet cs_;
401 };
402
403 template<class CharSet>
404 constexpr
405 auto
406 pct_encoded_fmt_string_rule(
407 CharSet const& cs) noexcept ->
408 pct_encoded_fmt_string_rule_t<CharSet>
409 {
410 // If an error occurs here it means that
411 // the value of your type does not meet
412 // the requirements. Please check the
413 // documentation!
414 static_assert(
415 grammar::is_charset<CharSet>::value,
416 "CharSet requirements not met");
417
418 return pct_encoded_fmt_string_rule_t<CharSet>(cs);
419 }
420
421 // This rule represents a regular string with
422 // only chars from the specified charset and
423 // an arbitrary number of replacement ids in it
424 template<class CharSet>
425 struct fmt_token_rule_t
426 {
427 using value_type = pct_string_view;
428
429 constexpr
430 fmt_token_rule_t(
431 CharSet const& cs) noexcept
432 : cs_(cs)
433 {
434 }
435
436 template<class CharSet_>
437 friend
438 constexpr
439 auto
440 fmt_token_rule(
441 CharSet_ const& cs) noexcept ->
442 fmt_token_rule_t<CharSet_>;
443
444 system::result<value_type>
445 22x parse(
446 char const*& it,
447 char const* end) const noexcept
448 {
449 22x auto const start = it;
450 22x BOOST_ASSERT(it != end);
451 /*
452 // This should never happen because
453 // all tokens are optional and will
454 // already return `none`:
455 if(it == end)
456 {
457 BOOST_URL_RETURN_EC(
458 grammar::error::need_more);
459 }
460 */
461
462 // consume some with literal rule
463 // this might be an empty literal
464 auto partial_token_rule =
465 22x grammar::optional_rule(
466 22x grammar::token_rule(cs_));
467 22x auto rv = partial_token_rule.parse(it, end);
468 41x while (rv)
469 {
470 41x auto it0 = it;
471 // consume some with replacement id
472 41x if (!replacement_field_rule.parse(it, end))
473 {
474 // no replacement and no more cs
475 // before: nothing else to consume
476 22x it = it0;
477 22x break;
478 }
479 // after {...}, consume any more chars
480 // in the charset
481 19x rv = partial_token_rule.parse(it, end);
482 }
483
484 22x if(it == start)
485 {
486 // it != end but we consumed nothing
487 1x BOOST_URL_RETURN_EC(
488 grammar::error::need_more);
489 }
490
491 21x return core::string_view(start, it - start);
492 }
493
494 private:
495 CharSet cs_;
496 };
497
498 template<class CharSet>
499 constexpr
500 auto
501 fmt_token_rule(
502 CharSet const& cs) noexcept ->
503 fmt_token_rule_t<CharSet>
504 {
505 // If an error occurs here it means that
506 // the value of your type does not meet
507 // the requirements. Please check the
508 // documentation!
509 static_assert(
510 grammar::is_charset<CharSet>::value,
511 "CharSet requirements not met");
512
513 return fmt_token_rule_t<CharSet>(cs);
514 }
515
516 struct userinfo_template_rule_t
517 {
518 struct value_type
519 {
520 core::string_view user;
521 core::string_view password;
522 bool has_password = false;
523 };
524
525 auto
526 63x parse(
527 char const*& it,
528 char const* end
529 ) const noexcept ->
530 system::result<value_type>
531 {
532 static constexpr auto uchars =
533 unreserved_chars +
534 sub_delim_chars;
535 static constexpr auto pwchars =
536 uchars + ':';
537
538 63x value_type t;
539
540 // user
541 static constexpr auto user_fmt_rule =
542 pct_encoded_fmt_string_rule(uchars);
543 63x auto rv = grammar::parse(
544 it, end, user_fmt_rule);
545 63x BOOST_ASSERT(rv);
546 63x t.user = *rv;
547
548 // ':'
549 63x if( it == end ||
550 46x *it != ':')
551 {
552 38x t.has_password = false;
553 38x t.password = {};
554 38x return t;
555 }
556 25x ++it;
557
558 // pass
559 static constexpr auto pass_fmt_rule =
560 pct_encoded_fmt_string_rule(grammar::ref(pwchars));
561 25x rv = grammar::parse(
562 it, end, pass_fmt_rule);
563 25x BOOST_ASSERT(rv);
564 25x t.has_password = true;
565 25x t.password = *rv;
566
567 25x return t;
568 }
569 };
570
571 constexpr userinfo_template_rule_t userinfo_template_rule{};
572
573 struct host_template_rule_t
574 {
575 using value_type = core::string_view;
576
577 auto
578 64x parse(
579 char const*& it,
580 char const* end
581 ) const noexcept ->
582 system::result<value_type>
583 {
584 64x if(it == end)
585 {
586 // empty host
587 1x return {};
588 }
589
590 // the host type will be ultimately
591 // validated when applying the replacement
592 // strings. Any chars allowed in hosts
593 // are allowed here.
594 63x if (*it != '[')
595 {
596 // IPv4address and reg-name have the
597 // same char sets.
598 60x constexpr auto any_host_template_rule =
599 pct_encoded_fmt_string_rule(host_chars);
600 60x auto rv = grammar::parse(
601 it, end, any_host_template_rule);
602 // any_host_template_rule can always
603 // be empty, so it's never invalid
604 60x BOOST_ASSERT(rv);
605 60x return detail::to_sv(*rv);
606 }
607 // IP-literals need to be enclosed in
608 // "[]" if using ':' in the template
609 // string, because the ':' would be
610 // ambiguous with the port in fmt string.
611 // The "[]:" can be used in replacement
612 // strings without the "[]" though.
613 3x constexpr auto ip_literal_template_rule =
614 pct_encoded_fmt_string_rule(lhost_chars);
615 3x auto it0 = it;
616 auto rv = grammar::parse(
617 it, end,
618 3x grammar::optional_rule(
619 3x grammar::tuple_rule(
620 3x grammar::squelch(
621 3x grammar::delim_rule('[')),
622 ip_literal_template_rule,
623 3x grammar::squelch(
624 6x grammar::delim_rule(']')))));
625 // ip_literal_template_rule can always
626 // be empty, so it's never invalid, but
627 // the rule might fail to match the
628 // closing "]"
629 3x BOOST_ASSERT(rv);
630 (void)rv;
631 3x return core::string_view{it0, it};
632 }
633 };
634
635 constexpr host_template_rule_t host_template_rule{};
636
637 struct authority_template_rule_t
638 {
639 using value_type = pattern;
640
641 system::result<value_type>
642 64x parse(
643 char const*& it,
644 char const* end
645 ) const noexcept
646 {
647 64x pattern u;
648
649 // [ userinfo "@" ]
650 {
651 auto rv = grammar::parse(
652 it, end,
653 64x grammar::optional_rule(
654 64x grammar::tuple_rule(
655 userinfo_template_rule,
656 64x grammar::squelch(
657 128x grammar::delim_rule('@')))));
658 64x BOOST_ASSERT(rv);
659 64x if(rv->has_value())
660 {
661 11x auto& r = **rv;
662 11x u.has_user = true;
663 11x u.user = r.user;
664 11x u.has_pass = r.has_password;
665 11x u.pass = r.password;
666 }
667 }
668
669 // host
670 {
671 64x auto rv = grammar::parse(
672 it, end,
673 host_template_rule);
674 // host is allowed to be empty
675 64x BOOST_ASSERT(rv);
676 64x u.host = *rv;
677 }
678
679 // [ ":" port ]
680 {
681 constexpr auto port_template_rule =
682 grammar::optional_rule(
683 fmt_token_rule(grammar::digit_chars));
684 64x auto it0 = it;
685 auto rv = grammar::parse(
686 it, end,
687 64x grammar::tuple_rule(
688 64x grammar::squelch(
689 64x grammar::delim_rule(':')),
690 64x port_template_rule));
691 64x if (!rv)
692 {
693 41x it = it0;
694 }
695 else
696 {
697 23x u.has_port = true;
698 23x if (rv->has_value())
699 {
700 21x u.port = **rv;
701 }
702 }
703 }
704
705 64x return u;
706 }
707 };
708
709 constexpr authority_template_rule_t authority_template_rule{};
710
711 struct scheme_template_rule_t
712 {
713 using value_type = core::string_view;
714
715 system::result<value_type>
716 168x parse(
717 char const*& it,
718 char const* end) const noexcept
719 {
720 168x auto const start = it;
721 168x if(it == end)
722 {
723 // scheme can't be empty
724 1x BOOST_URL_RETURN_EC(
725 grammar::error::mismatch);
726 }
727 305x if(!grammar::alpha_chars(*it) &&
728 138x *it != '{')
729 {
730 // expected alpha
731 22x BOOST_URL_RETURN_EC(
732 grammar::error::mismatch);
733 }
734
735 // it starts with replacement id or alpha char
736 145x if (!grammar::alpha_chars(*it))
737 {
738 116x if (!replacement_field_rule.parse(it, end))
739 {
740 // replacement_field_rule is invalid
741 2x BOOST_URL_RETURN_EC(
742 grammar::error::mismatch);
743 }
744 }
745 else
746 {
747 // skip first
748 29x ++it;
749 }
750
751 static
752 constexpr
753 grammar::lut_chars scheme_chars(
754 "0123456789" "+-."
755 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
756 "abcdefghijklmnopqrstuvwxyz");
757
758 // non-scheme chars might be a new
759 // replacement-id or just an invalid char
760 143x it = grammar::find_if_not(
761 it, end, scheme_chars);
762 146x while (it != end)
763 {
764 92x auto it0 = it;
765 92x if (!replacement_field_rule.parse(it, end))
766 {
767 89x it = it0;
768 89x break;
769 }
770 3x it = grammar::find_if_not(
771 it, end, scheme_chars);
772 }
773 143x return core::string_view(start, it - start);
774 }
775 };
776
777 constexpr scheme_template_rule_t scheme_template_rule{};
778
779 // This rule should consider all url types at the
780 // same time according to the format string
781 // - relative urls with no scheme/authority
782 // - absolute urls have no fragment
783 struct pattern_rule_t
784 {
785 using value_type = pattern;
786
787 system::result<value_type>
788 168x parse(
789 char const*& it,
790 char const* const end
791 ) const noexcept
792 {
793 168x pattern u;
794
795 // optional scheme
796 {
797 168x auto it0 = it;
798 168x auto rv = grammar::parse(
799 it, end,
800 168x grammar::tuple_rule(
801 scheme_template_rule,
802 168x grammar::squelch(
803 168x grammar::delim_rule(':'))));
804 168x if(rv)
805 75x u.scheme = *rv;
806 else
807 93x it = it0;
808 }
809
810 // hier_part (authority + path)
811 // if there are less than 2 chars left,
812 // we are parsing the path
813 168x if (it == end)
814 {
815 // this is over, so we can consider
816 // that a "path-empty"
817 4x return u;
818 }
819 164x if(end - it == 1)
820 {
821 // only one char left
822 // it can be a single separator "/",
823 // representing an empty absolute path,
824 // or a single-char segment
825 5x if(*it == '/')
826 {
827 // path-absolute
828 2x u.path = {it, 1};
829 2x ++it;
830 2x return u;
831 }
832 // this can be a:
833 // - path-noscheme if there's no scheme, or
834 // - path-rootless with a single char, or
835 // - path-empty (and consume nothing)
836 4x if (!u.scheme.empty() ||
837 1x *it != ':')
838 {
839 // path-rootless with a single char
840 // this needs to be a segment because
841 // the authority needs two slashes
842 // "//"
843 // path-noscheme also matches here
844 // because we already validated the
845 // first char
846 3x auto rv = grammar::parse(
847 it, end, urls::detail::segment_rule);
848 3x if(! rv)
849 1x return rv.error();
850 2x u.path = *rv;
851 }
852 2x return u;
853 }
854
855 // authority
856 159x if( it[0] == '/' &&
857 79x it[1] == '/')
858 {
859 // "//" always indicates authority
860 64x it += 2;
861 64x auto rv = grammar::parse(
862 it, end,
863 authority_template_rule);
864 // authority is allowed to be empty
865 64x BOOST_ASSERT(rv);
866 64x u.has_authority = true;
867 64x u.has_user = rv->has_user;
868 64x u.user = rv->user;
869 64x u.has_pass = rv->has_pass;
870 64x u.pass = rv->pass;
871 64x u.host = rv->host;
872 64x u.has_port = rv->has_port;
873 64x u.port = rv->port;
874 }
875
876 // the authority requires an absolute path
877 // or an empty path
878 159x if (it == end ||
879 132x (u.has_authority &&
880 37x (*it != '/' &&
881 8x *it != '?' &&
882 2x *it != '#')))
883 {
884 // path-empty
885 29x return u;
886 }
887
888 // path-abempty
889 // consume the whole path at once because
890 // we're going to count number of segments
891 // later after the replacements happen
892 static constexpr auto segment_fmt_rule =
893 pct_encoded_fmt_string_rule(path_chars);
894 130x auto rp = grammar::parse(
895 it, end, segment_fmt_rule);
896 // path-abempty is allowed to be empty
897 130x BOOST_ASSERT(rp);
898 130x u.path = *rp;
899
900 // [ "?" query ]
901 {
902 static constexpr auto query_fmt_rule =
903 pct_encoded_fmt_string_rule(query_chars);
904 130x auto rv = grammar::parse(
905 it, end,
906 130x grammar::tuple_rule(
907 130x grammar::squelch(
908 130x grammar::delim_rule('?')),
909 query_fmt_rule));
910 // query is allowed to be empty but
911 // delim rule is not
912 130x if (rv)
913 {
914 13x u.has_query = true;
915 13x u.query = *rv;
916 }
917 }
918
919 // [ "#" fragment ]
920 {
921 static constexpr auto frag_fmt_rule =
922 pct_encoded_fmt_string_rule(fragment_chars);
923 130x auto rv = grammar::parse(
924 it, end,
925 130x grammar::tuple_rule(
926 130x grammar::squelch(
927 130x grammar::delim_rule('#')),
928 frag_fmt_rule));
929 // frag is allowed to be empty but
930 // delim rule is not
931 130x if (rv)
932 {
933 7x u.has_frag = true;
934 7x u.frag = *rv;
935 }
936 }
937
938 130x return u;
939 }
940 };
941
942 constexpr pattern_rule_t pattern_rule{};
943
944 system::result<pattern>
945 168x parse_pattern(
946 core::string_view s)
947 {
948 168x return grammar::parse(
949 168x s, pattern_rule);
950 }
951
952 } // detail
953 } // urls
954 } // boost
955