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