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 154 : 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 154 : sizes n;
49 :
50 154 : format_parse_context pctx(nullptr, nullptr, 0);
51 154 : measure_context mctx(args);
52 154 : 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 154 : 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 154 : if (!path.empty())
99 : {
100 116 : pctx = {path, pctx.next_arg_id()};
101 116 : n.path = pct_vmeasure(
102 : path_chars, pctx, mctx);
103 114 : mctx.advance_to(0);
104 : }
105 152 : 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 152 : 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 152 : std::size_t const n_total =
120 152 : n.scheme +
121 152 : (n.scheme != 0) * 1 + // ":"
122 152 : has_authority * 2 + // "//"
123 152 : n.user +
124 152 : has_pass * 1 + // ":"
125 152 : n.pass +
126 152 : has_user * 1 + // "@"
127 152 : n.host +
128 152 : has_port * 1 + // ":"
129 152 : n.port +
130 152 : n.path +
131 152 : has_query * 1 + // "?"
132 152 : n.query +
133 152 : has_frag * 1 + // "#"
134 152 : n.frag;
135 152 : u.reserve(n_total);
136 :
137 : // Apply
138 151 : pctx = {nullptr, nullptr, 0};
139 151 : format_context fctx(nullptr, args);
140 151 : url_base::op_t op(u);
141 : using parts = parts_base;
142 151 : 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 150 : 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 150 : if (!path.empty())
238 : {
239 114 : auto dest = u.resize_impl(
240 : parts::id_path,
241 : n.path, op);
242 114 : pctx = {path, pctx.next_arg_id()};
243 114 : fctx.advance_to(dest);
244 114 : auto dest1 = pct_vformat(
245 : path_chars, pctx, fctx);
246 114 : pct_string_view npath(dest, dest1 - dest);
247 114 : u.impl_.decoded_[parts::id_path] +=
248 114 : detail::to_size_type(
249 : npath.decoded_size());
250 114 : if (!npath.empty())
251 : {
252 228 : u.impl_.nseg_ = detail::to_size_type(
253 114 : std::count(
254 114 : npath.begin() + 1,
255 228 : 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 193 : 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 202 : if (!u.has_authority() &&
296 202 : 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 150 : 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 150 : 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 151 : }
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 287 : parse(
372 : char const*& it,
373 : char const* end) const noexcept
374 : {
375 287 : auto const start = it;
376 287 : 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 286 : auto literal_rule = pct_encoded_rule(cs_);
385 286 : auto rv = literal_rule.parse(it, end);
386 562 : while (rv)
387 : {
388 562 : auto it0 = it;
389 : // consume some with replacement id
390 : // rule
391 562 : if (!replacement_field_rule.parse(it, end))
392 : {
393 286 : it = it0;
394 286 : break;
395 : }
396 276 : rv = literal_rule.parse(it, end);
397 : }
398 :
399 286 : 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 21 : parse(
449 : char const*& it,
450 : char const* end) const noexcept
451 : {
452 21 : auto const start = it;
453 21 : 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 21 : grammar::optional_rule(
469 21 : grammar::token_rule(cs_));
470 21 : auto rv = partial_token_rule.parse(it, end);
471 40 : while (rv)
472 : {
473 40 : auto it0 = it;
474 : // consume some with replacement id
475 40 : if (!replacement_field_rule.parse(it, end))
476 : {
477 : // no replacement and no more cs
478 : // before: nothing else to consume
479 21 : it = it0;
480 21 : break;
481 : }
482 : // after {...}, consume any more chars
483 : // in the charset
484 19 : rv = partial_token_rule.parse(it, end);
485 : }
486 :
487 21 : 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 20 : return core::string_view(start, it - start);
495 : }
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 60 : 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 60 : value_type t;
542 :
543 : // user
544 : static constexpr auto user_fmt_rule =
545 : pct_encoded_fmt_string_rule(uchars);
546 60 : auto rv = grammar::parse(
547 : it, end, user_fmt_rule);
548 60 : BOOST_ASSERT(rv);
549 60 : t.user = *rv;
550 :
551 : // ':'
552 60 : if( it == end ||
553 43 : *it != ':')
554 : {
555 36 : t.has_password = false;
556 36 : t.password = {};
557 36 : return t;
558 : }
559 24 : ++it;
560 :
561 : // pass
562 : static constexpr auto pass_fmt_rule =
563 : pct_encoded_fmt_string_rule(grammar::ref(pwchars));
564 24 : rv = grammar::parse(
565 : it, end, pass_fmt_rule);
566 24 : BOOST_ASSERT(rv);
567 24 : t.has_password = true;
568 24 : t.password = *rv;
569 :
570 24 : 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 61 : parse(
582 : char const*& it,
583 : char const* end
584 : ) const noexcept ->
585 : system::result<value_type>
586 : {
587 61 : 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 60 : if (*it != '[')
598 : {
599 : // IPv4address and reg-name have the
600 : // same char sets.
601 58 : constexpr auto any_host_template_rule =
602 : pct_encoded_fmt_string_rule(host_chars);
603 58 : 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 58 : BOOST_ASSERT(rv);
608 58 : 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 : (void)rv;
634 2 : return core::string_view{it0, it};
635 : }
636 : };
637 :
638 : constexpr host_template_rule_t host_template_rule{};
639 :
640 : struct authority_template_rule_t
641 : {
642 : using value_type = pattern;
643 :
644 : system::result<value_type>
645 61 : parse(
646 : char const*& it,
647 : char const* end
648 : ) const noexcept
649 : {
650 61 : pattern u;
651 :
652 : // [ userinfo "@" ]
653 : {
654 : auto rv = grammar::parse(
655 : it, end,
656 61 : grammar::optional_rule(
657 61 : grammar::tuple_rule(
658 : userinfo_template_rule,
659 61 : grammar::squelch(
660 122 : grammar::delim_rule('@')))));
661 61 : BOOST_ASSERT(rv);
662 61 : if(rv->has_value())
663 : {
664 9 : auto& r = **rv;
665 9 : u.has_user = true;
666 9 : u.user = r.user;
667 9 : u.has_pass = r.has_password;
668 9 : u.pass = r.password;
669 : }
670 : }
671 :
672 : // host
673 : {
674 61 : auto rv = grammar::parse(
675 : it, end,
676 : host_template_rule);
677 : // host is allowed to be empty
678 61 : BOOST_ASSERT(rv);
679 61 : u.host = *rv;
680 : }
681 :
682 : // [ ":" port ]
683 : {
684 : constexpr auto port_template_rule =
685 : grammar::optional_rule(
686 : fmt_token_rule(grammar::digit_chars));
687 61 : auto it0 = it;
688 : auto rv = grammar::parse(
689 : it, end,
690 61 : grammar::tuple_rule(
691 61 : grammar::squelch(
692 61 : grammar::delim_rule(':')),
693 61 : port_template_rule));
694 61 : if (!rv)
695 : {
696 39 : it = it0;
697 : }
698 : else
699 : {
700 22 : u.has_port = true;
701 22 : if (rv->has_value())
702 : {
703 20 : u.port = **rv;
704 : }
705 : }
706 : }
707 :
708 61 : return u;
709 : }
710 : };
711 :
712 : constexpr authority_template_rule_t authority_template_rule{};
713 :
714 : struct scheme_template_rule_t
715 : {
716 : using value_type = core::string_view;
717 :
718 : system::result<value_type>
719 161 : parse(
720 : char const*& it,
721 : char const* end) const noexcept
722 : {
723 161 : auto const start = it;
724 161 : if(it == end)
725 : {
726 : // scheme can't be empty
727 1 : BOOST_URL_RETURN_EC(
728 : grammar::error::mismatch);
729 : }
730 294 : if(!grammar::alpha_chars(*it) &&
731 134 : *it != '{')
732 : {
733 : // expected alpha
734 20 : BOOST_URL_RETURN_EC(
735 : grammar::error::mismatch);
736 : }
737 :
738 : // it starts with replacement id or alpha char
739 140 : if (!grammar::alpha_chars(*it))
740 : {
741 114 : if (!replacement_field_rule.parse(it, end))
742 : {
743 : // replacement_field_rule is invalid
744 2 : BOOST_URL_RETURN_EC(
745 : grammar::error::mismatch);
746 : }
747 : }
748 : else
749 : {
750 : // skip first
751 26 : ++it;
752 : }
753 :
754 : static
755 : constexpr
756 : grammar::lut_chars scheme_chars(
757 : "0123456789" "+-."
758 : "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
759 : "abcdefghijklmnopqrstuvwxyz");
760 :
761 : // non-scheme chars might be a new
762 : // replacement-id or just an invalid char
763 138 : it = grammar::find_if_not(
764 : it, end, scheme_chars);
765 141 : while (it != end)
766 : {
767 89 : auto it0 = it;
768 89 : if (!replacement_field_rule.parse(it, end))
769 : {
770 86 : it = it0;
771 86 : break;
772 : }
773 3 : it = grammar::find_if_not(
774 : it, end, scheme_chars);
775 : }
776 138 : return core::string_view(start, it - start);
777 : }
778 : };
779 :
780 : constexpr scheme_template_rule_t scheme_template_rule{};
781 :
782 : // This rule should consider all url types at the
783 : // same time according to the format string
784 : // - relative urls with no scheme/authority
785 : // - absolute urls have no fragment
786 : struct pattern_rule_t
787 : {
788 : using value_type = pattern;
789 :
790 : system::result<value_type>
791 161 : parse(
792 : char const*& it,
793 : char const* const end
794 : ) const noexcept
795 : {
796 161 : pattern u;
797 :
798 : // optional scheme
799 : {
800 161 : auto it0 = it;
801 161 : auto rv = grammar::parse(
802 : it, end,
803 161 : grammar::tuple_rule(
804 : scheme_template_rule,
805 161 : grammar::squelch(
806 161 : grammar::delim_rule(':'))));
807 161 : if(rv)
808 72 : u.scheme = *rv;
809 : else
810 89 : it = it0;
811 : }
812 :
813 : // hier_part (authority + path)
814 : // if there are less than 2 chars left,
815 : // we are parsing the path
816 161 : if (it == end)
817 : {
818 : // this is over, so we can consider
819 : // that a "path-empty"
820 4 : return u;
821 : }
822 157 : if(end - it == 1)
823 : {
824 : // only one char left
825 : // it can be a single separator "/",
826 : // representing an empty absolute path,
827 : // or a single-char segment
828 5 : if(*it == '/')
829 : {
830 : // path-absolute
831 2 : u.path = {it, 1};
832 2 : ++it;
833 2 : return u;
834 : }
835 : // this can be a:
836 : // - path-noscheme if there's no scheme, or
837 : // - path-rootless with a single char, or
838 : // - path-empty (and consume nothing)
839 4 : if (!u.scheme.empty() ||
840 1 : *it != ':')
841 : {
842 : // path-rootless with a single char
843 : // this needs to be a segment because
844 : // the authority needs two slashes
845 : // "//"
846 : // path-noscheme also matches here
847 : // because we already validated the
848 : // first char
849 3 : auto rv = grammar::parse(
850 : it, end, urls::detail::segment_rule);
851 3 : if(! rv)
852 1 : return rv.error();
853 2 : u.path = *rv;
854 : }
855 2 : return u;
856 : }
857 :
858 : // authority
859 152 : if( it[0] == '/' &&
860 74 : it[1] == '/')
861 : {
862 : // "//" always indicates authority
863 61 : it += 2;
864 61 : auto rv = grammar::parse(
865 : it, end,
866 : authority_template_rule);
867 : // authority is allowed to be empty
868 61 : BOOST_ASSERT(rv);
869 61 : u.has_authority = true;
870 61 : u.has_user = rv->has_user;
871 61 : u.user = rv->user;
872 61 : u.has_pass = rv->has_pass;
873 61 : u.pass = rv->pass;
874 61 : u.host = rv->host;
875 61 : u.has_port = rv->has_port;
876 61 : u.port = rv->port;
877 : }
878 :
879 : // the authority requires an absolute path
880 : // or an empty path
881 152 : if (it == end ||
882 125 : (u.has_authority &&
883 34 : (*it != '/' &&
884 8 : *it != '?' &&
885 2 : *it != '#')))
886 : {
887 : // path-empty
888 29 : return u;
889 : }
890 :
891 : // path-abempty
892 : // consume the whole path at once because
893 : // we're going to count number of segments
894 : // later after the replacements happen
895 : static constexpr auto segment_fmt_rule =
896 : pct_encoded_fmt_string_rule(path_chars);
897 123 : auto rp = grammar::parse(
898 : it, end, segment_fmt_rule);
899 : // path-abempty is allowed to be empty
900 123 : BOOST_ASSERT(rp);
901 123 : u.path = *rp;
902 :
903 : // [ "?" query ]
904 : {
905 : static constexpr auto query_fmt_rule =
906 : pct_encoded_fmt_string_rule(query_chars);
907 123 : auto rv = grammar::parse(
908 : it, end,
909 123 : grammar::tuple_rule(
910 123 : grammar::squelch(
911 123 : grammar::delim_rule('?')),
912 : query_fmt_rule));
913 : // query is allowed to be empty but
914 : // delim rule is not
915 123 : if (rv)
916 : {
917 13 : u.has_query = true;
918 13 : u.query = *rv;
919 : }
920 : }
921 :
922 : // [ "#" fragment ]
923 : {
924 : static constexpr auto frag_fmt_rule =
925 : pct_encoded_fmt_string_rule(fragment_chars);
926 123 : auto rv = grammar::parse(
927 : it, end,
928 123 : grammar::tuple_rule(
929 123 : grammar::squelch(
930 123 : grammar::delim_rule('#')),
931 : frag_fmt_rule));
932 : // frag is allowed to be empty but
933 : // delim rule is not
934 123 : if (rv)
935 : {
936 7 : u.has_frag = true;
937 7 : u.frag = *rv;
938 : }
939 : }
940 :
941 123 : return u;
942 : }
943 : };
944 :
945 : constexpr pattern_rule_t pattern_rule{};
946 :
947 : system::result<pattern>
948 161 : parse_pattern(
949 : core::string_view s)
950 : {
951 161 : return grammar::parse(
952 161 : s, pattern_rule);
953 : }
954 :
955 : } // detail
956 : } // urls
957 : } // boost
|