TLA Line data 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 HIT 161 : 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 161 : sizes n;
49 :
50 161 : format_parse_context pctx(nullptr, nullptr, 0);
51 161 : measure_context mctx(args);
52 161 : if (!scheme.empty())
53 : {
54 70 : pctx = {scheme, pctx.next_arg_id()};
55 70 : n.scheme = pct_vmeasure(
56 : grammar::alpha_chars, pctx, mctx);
57 70 : mctx.advance_to(0);
58 : }
59 161 : if (has_authority)
60 : {
61 62 : if (has_user)
62 : {
63 10 : pctx = {user, pctx.next_arg_id()};
64 10 : n.user = pct_vmeasure(
65 : user_chars, pctx, mctx);
66 10 : mctx.advance_to(0);
67 10 : if (has_pass)
68 : {
69 7 : pctx = {pass, pctx.next_arg_id()};
70 7 : n.pass = pct_vmeasure(
71 : password_chars, pctx, mctx);
72 7 : mctx.advance_to(0);
73 : }
74 : }
75 62 : if (host.starts_with('['))
76 : {
77 2 : BOOST_ASSERT(host.ends_with(']'));
78 2 : pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
79 2 : n.host = pct_vmeasure(
80 2 : lhost_chars, pctx, mctx) + 2;
81 2 : mctx.advance_to(0);
82 : }
83 : else
84 : {
85 60 : pctx = {host, pctx.next_arg_id()};
86 60 : n.host = pct_vmeasure(
87 : host_chars, pctx, mctx);
88 60 : mctx.advance_to(0);
89 : }
90 62 : if (has_port)
91 : {
92 22 : pctx = {port, pctx.next_arg_id()};
93 22 : n.port = pct_vmeasure(
94 : grammar::digit_chars, pctx, mctx);
95 22 : mctx.advance_to(0);
96 : }
97 : }
98 161 : if (!path.empty())
99 : {
100 123 : pctx = {path, pctx.next_arg_id()};
101 123 : n.path = pct_vmeasure(
102 : path_chars, pctx, mctx);
103 121 : mctx.advance_to(0);
104 : }
105 159 : 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 159 : 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 159 : std::size_t const n_total =
120 159 : n.scheme +
121 159 : (n.scheme != 0) * 1 + // ":"
122 159 : has_authority * 2 + // "//"
123 159 : n.user +
124 159 : has_pass * 1 + // ":"
125 159 : n.pass +
126 159 : has_user * 1 + // "@"
127 159 : n.host +
128 159 : has_port * 1 + // ":"
129 159 : n.port +
130 159 : n.path +
131 159 : has_query * 1 + // "?"
132 159 : n.query +
133 159 : has_frag * 1 + // "#"
134 159 : n.frag;
135 159 : u.reserve(n_total);
136 :
137 : // Apply
138 158 : pctx = {nullptr, nullptr, 0};
139 158 : format_context fctx(nullptr, args);
140 158 : url_base::op_t op(u);
141 : using parts = parts_base;
142 158 : if (!scheme.empty())
143 : {
144 138 : auto dest = u.resize_impl(
145 : parts::id_scheme,
146 69 : n.scheme + 1, op);
147 69 : pctx = {scheme, pctx.next_arg_id()};
148 69 : fctx.advance_to(dest);
149 69 : const char* dest1 = pct_vformat(
150 : grammar::alpha_chars, pctx, fctx);
151 69 : dest[n.scheme] = ':';
152 : // validate
153 69 : if (!grammar::parse({dest, dest1}, scheme_rule()))
154 : {
155 1 : throw_invalid_argument();
156 : }
157 : }
158 157 : if (has_authority)
159 : {
160 60 : if (has_user)
161 : {
162 10 : auto dest = u.set_user_impl(
163 : n.user, op);
164 10 : pctx = {user, pctx.next_arg_id()};
165 10 : fctx.advance_to(dest);
166 10 : char const* dest1 = pct_vformat(
167 : user_chars, pctx, fctx);
168 10 : u.impl_.decoded_[parts::id_user] =
169 10 : detail::to_size_type(
170 10 : pct_string_view(dest, dest1 - dest)
171 : ->decoded_size());
172 10 : if (has_pass)
173 : {
174 7 : char* destp = u.set_password_impl(
175 : n.pass, op);
176 7 : pctx = {pass, pctx.next_arg_id()};
177 7 : fctx.advance_to(destp);
178 7 : dest1 = pct_vformat(
179 : password_chars, pctx, fctx);
180 7 : u.impl_.decoded_[parts::id_pass] =
181 7 : detail::to_size_type(
182 14 : pct_string_view({destp, dest1})
183 : ->decoded_size());
184 : }
185 : }
186 60 : auto dest = u.set_host_impl(
187 : n.host, op);
188 60 : if (host.starts_with('['))
189 : {
190 2 : BOOST_ASSERT(host.ends_with(']'));
191 2 : pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
192 2 : *dest++ = '[';
193 2 : fctx.advance_to(dest);
194 : char* dest1 =
195 2 : pct_vformat(lhost_chars, pctx, fctx);
196 2 : *dest1++ = ']';
197 2 : u.impl_.decoded_[parts::id_host] =
198 2 : detail::to_size_type(
199 4 : pct_string_view(dest - 1, dest1 - dest + 1)
200 : ->decoded_size());
201 : }
202 : else
203 : {
204 58 : pctx = {host, pctx.next_arg_id()};
205 58 : fctx.advance_to(dest);
206 : char const* dest1 =
207 58 : pct_vformat(host_chars, pctx, fctx);
208 58 : u.impl_.decoded_[parts::id_host] =
209 58 : detail::to_size_type(
210 116 : pct_string_view(dest, dest1 - dest)
211 : ->decoded_size());
212 : }
213 60 : auto uh = u.encoded_host();
214 60 : auto h = grammar::parse(uh, host_rule).value();
215 60 : std::memcpy(
216 60 : u.impl_.ip_addr_,
217 : h.addr,
218 : sizeof(u.impl_.ip_addr_));
219 60 : u.impl_.host_type_ = h.host_type;
220 60 : if (has_port)
221 : {
222 22 : dest = u.set_port_impl(n.port, op);
223 22 : pctx = {port, pctx.next_arg_id()};
224 22 : fctx.advance_to(dest);
225 22 : char const* dest1 = pct_vformat(
226 : grammar::digit_chars, pctx, fctx);
227 22 : core::string_view up = {dest - 1, dest1};
228 22 : auto p = grammar::parse(up, detail::port_part_rule).value();
229 22 : if (p.has_port)
230 22 : u.impl_.port_number_ = p.port_number;
231 : }
232 : }
233 157 : if (!path.empty())
234 : {
235 121 : auto dest = u.resize_impl(
236 : parts::id_path,
237 : n.path, op);
238 121 : pctx = {path, pctx.next_arg_id()};
239 121 : fctx.advance_to(dest);
240 121 : auto dest1 = pct_vformat(
241 : path_chars, pctx, fctx);
242 121 : pct_string_view npath(dest, dest1 - dest);
243 121 : u.impl_.decoded_[parts::id_path] +=
244 121 : detail::to_size_type(
245 : npath.decoded_size());
246 121 : if (!npath.empty())
247 : {
248 242 : u.impl_.nseg_ = detail::to_size_type(
249 121 : std::count(
250 121 : npath.begin() + 1,
251 242 : 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 204 : if (u.scheme().empty() &&
259 83 : !u.has_authority())
260 : {
261 83 : auto fseg = u.encoded_segments().front();
262 83 : std::size_t nc = std::count(
263 83 : fseg.begin(), fseg.end(), ':');
264 83 : if (nc)
265 : {
266 6 : std::size_t diff = nc * 2;
267 6 : u.reserve(n_total + diff);
268 12 : dest = u.resize_impl(
269 : parts::id_path,
270 6 : n.path + diff, op);
271 6 : char* dest0 = dest + diff;
272 6 : std::memmove(dest0, dest, n.path);
273 38 : while (dest0 != dest)
274 : {
275 32 : if (*dest0 != ':')
276 : {
277 22 : *dest++ = *dest0++;
278 : }
279 : else
280 : {
281 10 : *dest++ = '%';
282 10 : *dest++ = '3';
283 10 : *dest++ = 'A';
284 10 : dest0++;
285 : }
286 : }
287 6 : n.path += diff;
288 : }
289 : }
290 : // 2) url has no authority and path
291 : // starts with "//"
292 213 : if (!u.has_authority() &&
293 213 : u.encoded_path().starts_with("//"))
294 : {
295 2 : u.reserve(n_total + 2);
296 4 : dest = u.resize_impl(
297 : parts::id_path,
298 2 : n.path + 2, op);
299 2 : std::memmove(dest + 2, dest, n.path);
300 2 : *dest++ = '/';
301 2 : *dest = '.';
302 : }
303 : }
304 157 : if (has_query)
305 : {
306 26 : auto dest = u.resize_impl(
307 : parts::id_query,
308 13 : n.query + 1, op);
309 13 : *dest++ = '?';
310 13 : pctx = {query, pctx.next_arg_id()};
311 13 : fctx.advance_to(dest);
312 13 : auto dest1 = pct_vformat(
313 : query_chars, pctx, fctx);
314 13 : pct_string_view nquery(dest, dest1 - dest);
315 13 : u.impl_.decoded_[parts::id_query] +=
316 13 : detail::to_size_type(
317 13 : nquery.decoded_size() + 1);
318 13 : if (!nquery.empty())
319 : {
320 26 : u.impl_.nparam_ = detail::to_size_type(
321 13 : std::count(
322 : nquery.begin(),
323 26 : nquery.end(), '&') + 1);
324 : }
325 : }
326 157 : if (has_frag)
327 : {
328 14 : auto dest = u.resize_impl(
329 : parts::id_frag,
330 7 : n.frag + 1, op);
331 7 : *dest++ = '#';
332 7 : pctx = {frag, pctx.next_arg_id()};
333 7 : fctx.advance_to(dest);
334 7 : auto dest1 = pct_vformat(
335 : fragment_chars, pctx, fctx);
336 7 : u.impl_.decoded_[parts::id_frag] +=
337 7 : detail::to_size_type(
338 14 : make_pct_string_view(
339 7 : core::string_view(dest, dest1 - dest))
340 7 : ->decoded_size() + 1);
341 : }
342 158 : }
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 301 : parse(
369 : char const*& it,
370 : char const* end) const noexcept
371 : {
372 301 : auto const start = it;
373 301 : if(it == end)
374 : {
375 : // this might be empty
376 1 : return {};
377 : }
378 :
379 : // consume some with literal rule
380 : // this might be an empty literal
381 300 : auto literal_rule = pct_encoded_rule(cs_);
382 300 : auto rv = literal_rule.parse(it, end);
383 580 : while (rv)
384 : {
385 580 : auto it0 = it;
386 : // consume some with replacement id
387 : // rule
388 580 : if (!replacement_field_rule.parse(it, end))
389 : {
390 300 : it = it0;
391 300 : break;
392 : }
393 280 : rv = literal_rule.parse(it, end);
394 : }
395 :
396 300 : 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 22 : parse(
446 : char const*& it,
447 : char const* end) const noexcept
448 : {
449 22 : auto const start = it;
450 22 : 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 22 : grammar::optional_rule(
466 22 : grammar::token_rule(cs_));
467 22 : auto rv = partial_token_rule.parse(it, end);
468 41 : while (rv)
469 : {
470 41 : auto it0 = it;
471 : // consume some with replacement id
472 41 : if (!replacement_field_rule.parse(it, end))
473 : {
474 : // no replacement and no more cs
475 : // before: nothing else to consume
476 22 : it = it0;
477 22 : break;
478 : }
479 : // after {...}, consume any more chars
480 : // in the charset
481 19 : rv = partial_token_rule.parse(it, end);
482 : }
483 :
484 22 : if(it == start)
485 : {
486 : // it != end but we consumed nothing
487 1 : BOOST_URL_RETURN_EC(
488 : grammar::error::need_more);
489 : }
490 :
491 21 : 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 63 : 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 63 : value_type t;
539 :
540 : // user
541 : static constexpr auto user_fmt_rule =
542 : pct_encoded_fmt_string_rule(uchars);
543 63 : auto rv = grammar::parse(
544 : it, end, user_fmt_rule);
545 63 : BOOST_ASSERT(rv);
546 63 : t.user = *rv;
547 :
548 : // ':'
549 63 : if( it == end ||
550 46 : *it != ':')
551 : {
552 38 : t.has_password = false;
553 38 : t.password = {};
554 38 : return t;
555 : }
556 25 : ++it;
557 :
558 : // pass
559 : static constexpr auto pass_fmt_rule =
560 : pct_encoded_fmt_string_rule(grammar::ref(pwchars));
561 25 : rv = grammar::parse(
562 : it, end, pass_fmt_rule);
563 25 : BOOST_ASSERT(rv);
564 25 : t.has_password = true;
565 25 : t.password = *rv;
566 :
567 25 : 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 64 : parse(
579 : char const*& it,
580 : char const* end
581 : ) const noexcept ->
582 : system::result<value_type>
583 : {
584 64 : if(it == end)
585 : {
586 : // empty host
587 1 : 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 63 : if (*it != '[')
595 : {
596 : // IPv4address and reg-name have the
597 : // same char sets.
598 60 : constexpr auto any_host_template_rule =
599 : pct_encoded_fmt_string_rule(host_chars);
600 60 : 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 60 : BOOST_ASSERT(rv);
605 60 : 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 3 : constexpr auto ip_literal_template_rule =
614 : pct_encoded_fmt_string_rule(lhost_chars);
615 3 : auto it0 = it;
616 : auto rv = grammar::parse(
617 : it, end,
618 3 : grammar::optional_rule(
619 3 : grammar::tuple_rule(
620 3 : grammar::squelch(
621 3 : grammar::delim_rule('[')),
622 : ip_literal_template_rule,
623 3 : grammar::squelch(
624 6 : 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 3 : BOOST_ASSERT(rv);
630 : (void)rv;
631 3 : 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 64 : parse(
643 : char const*& it,
644 : char const* end
645 : ) const noexcept
646 : {
647 64 : pattern u;
648 :
649 : // [ userinfo "@" ]
650 : {
651 : auto rv = grammar::parse(
652 : it, end,
653 64 : grammar::optional_rule(
654 64 : grammar::tuple_rule(
655 : userinfo_template_rule,
656 64 : grammar::squelch(
657 128 : grammar::delim_rule('@')))));
658 64 : BOOST_ASSERT(rv);
659 64 : if(rv->has_value())
660 : {
661 11 : auto& r = **rv;
662 11 : u.has_user = true;
663 11 : u.user = r.user;
664 11 : u.has_pass = r.has_password;
665 11 : u.pass = r.password;
666 : }
667 : }
668 :
669 : // host
670 : {
671 64 : auto rv = grammar::parse(
672 : it, end,
673 : host_template_rule);
674 : // host is allowed to be empty
675 64 : BOOST_ASSERT(rv);
676 64 : 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 64 : auto it0 = it;
685 : auto rv = grammar::parse(
686 : it, end,
687 64 : grammar::tuple_rule(
688 64 : grammar::squelch(
689 64 : grammar::delim_rule(':')),
690 64 : port_template_rule));
691 64 : if (!rv)
692 : {
693 41 : it = it0;
694 : }
695 : else
696 : {
697 23 : u.has_port = true;
698 23 : if (rv->has_value())
699 : {
700 21 : u.port = **rv;
701 : }
702 : }
703 : }
704 :
705 64 : 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 168 : parse(
717 : char const*& it,
718 : char const* end) const noexcept
719 : {
720 168 : auto const start = it;
721 168 : if(it == end)
722 : {
723 : // scheme can't be empty
724 1 : BOOST_URL_RETURN_EC(
725 : grammar::error::mismatch);
726 : }
727 305 : if(!grammar::alpha_chars(*it) &&
728 138 : *it != '{')
729 : {
730 : // expected alpha
731 22 : BOOST_URL_RETURN_EC(
732 : grammar::error::mismatch);
733 : }
734 :
735 : // it starts with replacement id or alpha char
736 145 : if (!grammar::alpha_chars(*it))
737 : {
738 116 : if (!replacement_field_rule.parse(it, end))
739 : {
740 : // replacement_field_rule is invalid
741 2 : BOOST_URL_RETURN_EC(
742 : grammar::error::mismatch);
743 : }
744 : }
745 : else
746 : {
747 : // skip first
748 29 : ++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 143 : it = grammar::find_if_not(
761 : it, end, scheme_chars);
762 146 : while (it != end)
763 : {
764 92 : auto it0 = it;
765 92 : if (!replacement_field_rule.parse(it, end))
766 : {
767 89 : it = it0;
768 89 : break;
769 : }
770 3 : it = grammar::find_if_not(
771 : it, end, scheme_chars);
772 : }
773 143 : 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 168 : parse(
789 : char const*& it,
790 : char const* const end
791 : ) const noexcept
792 : {
793 168 : pattern u;
794 :
795 : // optional scheme
796 : {
797 168 : auto it0 = it;
798 168 : auto rv = grammar::parse(
799 : it, end,
800 168 : grammar::tuple_rule(
801 : scheme_template_rule,
802 168 : grammar::squelch(
803 168 : grammar::delim_rule(':'))));
804 168 : if(rv)
805 75 : u.scheme = *rv;
806 : else
807 93 : 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 168 : if (it == end)
814 : {
815 : // this is over, so we can consider
816 : // that a "path-empty"
817 4 : return u;
818 : }
819 164 : 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 5 : if(*it == '/')
826 : {
827 : // path-absolute
828 2 : u.path = {it, 1};
829 2 : ++it;
830 2 : 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 4 : if (!u.scheme.empty() ||
837 1 : *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 3 : auto rv = grammar::parse(
847 : it, end, urls::detail::segment_rule);
848 3 : if(! rv)
849 1 : return rv.error();
850 2 : u.path = *rv;
851 : }
852 2 : return u;
853 : }
854 :
855 : // authority
856 159 : if( it[0] == '/' &&
857 79 : it[1] == '/')
858 : {
859 : // "//" always indicates authority
860 64 : it += 2;
861 64 : auto rv = grammar::parse(
862 : it, end,
863 : authority_template_rule);
864 : // authority is allowed to be empty
865 64 : BOOST_ASSERT(rv);
866 64 : u.has_authority = true;
867 64 : u.has_user = rv->has_user;
868 64 : u.user = rv->user;
869 64 : u.has_pass = rv->has_pass;
870 64 : u.pass = rv->pass;
871 64 : u.host = rv->host;
872 64 : u.has_port = rv->has_port;
873 64 : u.port = rv->port;
874 : }
875 :
876 : // the authority requires an absolute path
877 : // or an empty path
878 159 : if (it == end ||
879 132 : (u.has_authority &&
880 37 : (*it != '/' &&
881 8 : *it != '?' &&
882 2 : *it != '#')))
883 : {
884 : // path-empty
885 29 : 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 130 : auto rp = grammar::parse(
895 : it, end, segment_fmt_rule);
896 : // path-abempty is allowed to be empty
897 130 : BOOST_ASSERT(rp);
898 130 : u.path = *rp;
899 :
900 : // [ "?" query ]
901 : {
902 : static constexpr auto query_fmt_rule =
903 : pct_encoded_fmt_string_rule(query_chars);
904 130 : auto rv = grammar::parse(
905 : it, end,
906 130 : grammar::tuple_rule(
907 130 : grammar::squelch(
908 130 : grammar::delim_rule('?')),
909 : query_fmt_rule));
910 : // query is allowed to be empty but
911 : // delim rule is not
912 130 : if (rv)
913 : {
914 13 : u.has_query = true;
915 13 : 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 130 : auto rv = grammar::parse(
924 : it, end,
925 130 : grammar::tuple_rule(
926 130 : grammar::squelch(
927 130 : grammar::delim_rule('#')),
928 : frag_fmt_rule));
929 : // frag is allowed to be empty but
930 : // delim rule is not
931 130 : if (rv)
932 : {
933 7 : u.has_frag = true;
934 7 : u.frag = *rv;
935 : }
936 : }
937 :
938 130 : return u;
939 : }
940 : };
941 :
942 : constexpr pattern_rule_t pattern_rule{};
943 :
944 : system::result<pattern>
945 168 : parse_pattern(
946 : core::string_view s)
947 : {
948 168 : return grammar::parse(
949 168 : s, pattern_rule);
950 : }
951 :
952 : } // detail
953 : } // urls
954 : } // boost
|