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