src/detail/pattern.cpp

100.0% Lines (364/364) 100.0% Functions (10/10) 90.3% Branches (186/206)
src/detail/pattern.cpp
Line Branch Hits 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
1/1
✓ Branch 1 taken 154 times.
154 measure_context mctx(args);
52
2/2
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 87 times.
154 if (!scheme.empty())
53 {
54 67 pctx = {scheme, pctx.next_arg_id()};
55
1/1
✓ Branch 2 taken 67 times.
67 n.scheme = pct_vmeasure(
56 grammar::alpha_chars, pctx, mctx);
57 67 mctx.advance_to(0);
58 }
59
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 95 times.
154 if (has_authority)
60 {
61
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 51 times.
59 if (has_user)
62 {
63 8 pctx = {user, pctx.next_arg_id()};
64
1/1
✓ Branch 1 taken 8 times.
8 n.user = pct_vmeasure(
65 user_chars, pctx, mctx);
66 8 mctx.advance_to(0);
67
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (has_pass)
68 {
69 6 pctx = {pass, pctx.next_arg_id()};
70
1/1
✓ Branch 1 taken 6 times.
6 n.pass = pct_vmeasure(
71 password_chars, pctx, mctx);
72 6 mctx.advance_to(0);
73 }
74 }
75
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 58 times.
59 if (host.starts_with('['))
76 {
77
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 BOOST_ASSERT(host.ends_with(']'));
78
1/1
✓ Branch 2 taken 1 time.
1 pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
79
1/1
✓ Branch 1 taken 1 time.
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
1/1
✓ Branch 1 taken 58 times.
58 n.host = pct_vmeasure(
87 host_chars, pctx, mctx);
88 58 mctx.advance_to(0);
89 }
90
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 38 times.
59 if (has_port)
91 {
92 21 pctx = {port, pctx.next_arg_id()};
93
1/1
✓ Branch 2 taken 21 times.
21 n.port = pct_vmeasure(
94 grammar::digit_chars, pctx, mctx);
95 21 mctx.advance_to(0);
96 }
97 }
98
2/2
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 38 times.
154 if (!path.empty())
99 {
100 116 pctx = {path, pctx.next_arg_id()};
101
1/1
✓ Branch 1 taken 114 times.
116 n.path = pct_vmeasure(
102 path_chars, pctx, mctx);
103 114 mctx.advance_to(0);
104 }
105
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 139 times.
152 if (has_query)
106 {
107 13 pctx = {query, pctx.next_arg_id()};
108
1/1
✓ Branch 1 taken 13 times.
13 n.query = pct_vmeasure(
109 query_chars, pctx, mctx);
110 13 mctx.advance_to(0);
111 }
112
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 145 times.
152 if (has_frag)
113 {
114 7 pctx = {frag, pctx.next_arg_id()};
115
1/1
✓ Branch 1 taken 7 times.
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
1/1
✓ Branch 1 taken 151 times.
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
2/2
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 85 times.
151 if (!scheme.empty())
143 {
144 132 auto dest = u.resize_impl(
145 parts::id_scheme,
146
1/1
✓ Branch 1 taken 66 times.
66 n.scheme + 1, op);
147 66 pctx = {scheme, pctx.next_arg_id()};
148 66 fctx.advance_to(dest);
149
1/1
✓ Branch 2 taken 66 times.
66 const char* dest1 = pct_vformat(
150 grammar::alpha_chars, pctx, fctx);
151 66 dest[n.scheme] = ':';
152 // validate
153
2/2
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 65 times.
66 if (!grammar::parse({dest, dest1}, scheme_rule()))
154 {
155 1 throw_invalid_argument();
156 }
157 }
158
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 93 times.
150 if (has_authority)
159 {
160
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 49 times.
57 if (has_user)
161 {
162
1/1
✓ Branch 1 taken 8 times.
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
1/1
✓ Branch 1 taken 8 times.
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
1/1
✓ Branch 1 taken 8 times.
8 pct_string_view(dest, dest1 - dest)
171 ->decoded_size());
172
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (has_pass)
173 {
174
1/1
✓ Branch 1 taken 6 times.
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
1/1
✓ Branch 1 taken 6 times.
6 dest1 = pct_vformat(
179 password_chars, pctx, fctx);
180 6 u.impl_.decoded_[parts::id_pass] =
181 6 detail::to_size_type(
182
1/1
✓ Branch 2 taken 6 times.
12 pct_string_view({destp, dest1})
183 6 ->decoded_size() + 1);
184 }
185 }
186
1/1
✓ Branch 1 taken 57 times.
57 auto dest = u.set_host_impl(
187 n.host, op);
188
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 56 times.
57 if (host.starts_with('['))
189 {
190
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 BOOST_ASSERT(host.ends_with(']'));
191
1/1
✓ Branch 2 taken 1 time.
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/1
✓ Branch 1 taken 1 time.
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
1/1
✓ Branch 1 taken 1 time.
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
1/1
✓ Branch 1 taken 56 times.
56 pct_vformat(host_chars, pctx, fctx);
208 56 u.impl_.decoded_[parts::id_host] =
209 56 detail::to_size_type(
210
1/1
✓ Branch 1 taken 56 times.
112 pct_string_view(dest, dest1 - dest)
211 ->decoded_size());
212 }
213 57 auto uh = u.encoded_host();
214
1/1
✓ Branch 4 taken 57 times.
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
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 36 times.
57 if (has_port)
221 {
222
1/1
✓ Branch 1 taken 21 times.
21 dest = u.set_port_impl(n.port, op);
223 21 pctx = {port, pctx.next_arg_id()};
224 21 fctx.advance_to(dest);
225
1/1
✓ Branch 2 taken 21 times.
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
1/1
✓ Branch 1 taken 21 times.
21 pct_string_view(dest, dest1 - dest)
230 21 ->decoded_size() + 1);
231 21 core::string_view up = {dest - 1, dest1};
232
1/1
✓ Branch 3 taken 21 times.
21 auto p = grammar::parse(up, detail::port_part_rule).value();
233
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 if (p.has_port)
234 21 u.impl_.port_number_ = p.port_number;
235 }
236 }
237
2/2
✓ Branch 1 taken 114 times.
✓ Branch 2 taken 36 times.
150 if (!path.empty())
238 {
239
1/1
✓ Branch 1 taken 114 times.
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
1/1
✓ Branch 1 taken 114 times.
114 auto dest1 = pct_vformat(
245 path_chars, pctx, fctx);
246
1/1
✓ Branch 1 taken 114 times.
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
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
114 if (!npath.empty())
251 {
252 228 u.impl_.nseg_ = detail::to_size_type(
253
1/1
✓ Branch 1 taken 114 times.
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
4/4
✓ Branch 2 taken 79 times.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 79 times.
✓ Branch 5 taken 35 times.
193 if (u.scheme().empty() &&
263
1/2
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
79 !u.has_authority())
264 {
265 79 auto fseg = u.encoded_segments().front();
266
1/1
✓ Branch 2 taken 79 times.
79 std::size_t nc = std::count(
267 79 fseg.begin(), fseg.end(), ':');
268
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 74 times.
79 if (nc)
269 {
270 5 std::size_t diff = nc * 2;
271
1/1
✓ Branch 1 taken 5 times.
5 u.reserve(n_total + diff);
272 10 dest = u.resize_impl(
273 parts::id_path,
274
1/1
✓ Branch 1 taken 5 times.
5 n.path + diff, op);
275 5 char* dest0 = dest + diff;
276 5 std::memmove(dest0, dest, n.path);
277
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 5 times.
35 while (dest0 != dest)
278 {
279
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 9 times.
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
4/4
✓ Branch 1 taken 88 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 86 times.
202 if (!u.has_authority() &&
296
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 112 times.
202 u.encoded_path().starts_with("//"))
297 {
298
1/1
✓ Branch 1 taken 2 times.
2 u.reserve(n_total + 2);
299 4 dest = u.resize_impl(
300 parts::id_path,
301
1/1
✓ Branch 1 taken 2 times.
2 n.path + 2, op);
302 2 std::memmove(dest + 2, dest, n.path);
303 2 *dest++ = '/';
304 2 *dest = '.';
305 }
306 }
307
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 137 times.
150 if (has_query)
308 {
309 26 auto dest = u.resize_impl(
310 parts::id_query,
311
1/1
✓ Branch 1 taken 13 times.
13 n.query + 1, op);
312 13 *dest++ = '?';
313 13 pctx = {query, pctx.next_arg_id()};
314 13 fctx.advance_to(dest);
315
1/1
✓ Branch 1 taken 13 times.
13 auto dest1 = pct_vformat(
316 query_chars, pctx, fctx);
317
1/1
✓ Branch 1 taken 13 times.
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
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 if (!nquery.empty())
322 {
323 26 u.impl_.nparam_ = detail::to_size_type(
324
1/1
✓ Branch 2 taken 13 times.
13 std::count(
325 nquery.begin(),
326 26 nquery.end(), '&') + 1);
327 }
328 }
329
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 143 times.
150 if (has_frag)
330 {
331 14 auto dest = u.resize_impl(
332 parts::id_frag,
333
1/1
✓ Branch 1 taken 7 times.
7 n.frag + 1, op);
334 7 *dest++ = '#';
335 7 pctx = {frag, pctx.next_arg_id()};
336 7 fctx.advance_to(dest);
337
1/1
✓ Branch 1 taken 7 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 286 times.
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
1/2
✓ Branch 1 taken 562 times.
✗ Branch 2 not taken.
562 while (rv)
387 {
388 562 auto it0 = it;
389 // consume some with replacement id
390 // rule
391
2/2
✓ Branch 2 taken 286 times.
✓ Branch 3 taken 276 times.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
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
1/2
✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
40 while (rv)
472 {
473 40 auto it0 = it;
474 // consume some with replacement id
475
2/2
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 19 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 20 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
60 BOOST_ASSERT(rv);
549 60 t.user = *rv;
550
551 // ':'
552
2/2
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 17 times.
60 if( it == end ||
553
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 24 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 60 times.
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
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 2 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 58 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
61 BOOST_ASSERT(rv);
662
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 52 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
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
2/2
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 22 times.
61 if (!rv)
695 {
696 39 it = it0;
697 }
698 else
699 {
700 22 u.has_port = true;
701
2/2
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 2 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 160 times.
161 if(it == end)
725 {
726 // scheme can't be empty
727 1 BOOST_URL_RETURN_EC(
728 grammar::error::mismatch);
729 }
730
4/4
✓ Branch 1 taken 134 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 140 times.
294 if(!grammar::alpha_chars(*it) &&
731
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 114 times.
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
2/2
✓ Branch 1 taken 114 times.
✓ Branch 2 taken 26 times.
140 if (!grammar::alpha_chars(*it))
740 {
741
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 112 times.
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
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 52 times.
141 while (it != end)
766 {
767 89 auto it0 = it;
768
2/2
✓ Branch 2 taken 86 times.
✓ Branch 3 taken 3 times.
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
2/2
✓ Branch 1 taken 72 times.
✓ Branch 2 taken 89 times.
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
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 157 times.
161 if (it == end)
817 {
818 // this is over, so we can consider
819 // that a "path-empty"
820 4 return u;
821 }
822
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 152 times.
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
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
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
3/4
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
4 if (!u.scheme.empty() ||
840
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
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
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 if(! rv)
852 1 return rv.error();
853 2 u.path = *rv;
854 }
855 2 return u;
856 }
857
858 // authority
859
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 78 times.
152 if( it[0] == '/' &&
860
2/2
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 13 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
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
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 27 times.
152 if (it == end ||
882
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 91 times.
125 (u.has_authority &&
883
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 26 times.
34 (*it != '/' &&
884
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 *it != '?' &&
885
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 123 times.
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
2/2
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 110 times.
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
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 116 times.
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
958