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