src/detail/pattern.cpp

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