libs/url/src/detail/pattern.cpp

99.7% Lines (380/381) 100.0% Functions (10/10) 89.6% Branches (190/212)
libs/url/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 "../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
1/1
✓ Branch 1 taken 148 times.
148 measure_context mctx(args);
52
2/2
✓ Branch 1 taken 61 times.
✓ Branch 2 taken 87 times.
148 if (!scheme.empty())
53 {
54 61 pctx = {scheme, pctx.next_arg_id()};
55
1/1
✓ Branch 2 taken 61 times.
61 n.scheme = pct_vmeasure(
56 grammar::alpha_chars, pctx, mctx);
57 61 mctx.advance_to(0);
58 }
59
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 95 times.
148 if (has_authority)
60 {
61
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 45 times.
53 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 52 times.
53 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 52 pctx = {host, pctx.next_arg_id()};
86
1/1
✓ Branch 1 taken 52 times.
52 n.host = pct_vmeasure(
87 host_chars, pctx, mctx);
88 52 mctx.advance_to(0);
89 }
90
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 36 times.
53 if (has_port)
91 {
92 17 pctx = {port, pctx.next_arg_id()};
93
1/1
✓ Branch 2 taken 17 times.
17 n.port = pct_vmeasure(
94 grammar::digit_chars, pctx, mctx);
95 17 mctx.advance_to(0);
96 }
97 }
98
2/2
✓ Branch 1 taken 110 times.
✓ Branch 2 taken 38 times.
148 if (!path.empty())
99 {
100 110 pctx = {path, pctx.next_arg_id()};
101
1/1
✓ Branch 1 taken 108 times.
110 n.path = pct_vmeasure(
102 path_chars, pctx, mctx);
103 108 mctx.advance_to(0);
104 }
105
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 133 times.
146 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 139 times.
146 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 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
1/1
✓ Branch 1 taken 145 times.
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
2/2
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 85 times.
145 if (!scheme.empty())
143 {
144 120 auto dest = u.resize_impl(
145 parts::id_scheme,
146
1/1
✓ Branch 1 taken 60 times.
60 n.scheme + 1, op);
147 60 pctx = {scheme, pctx.next_arg_id()};
148 60 fctx.advance_to(dest);
149
1/1
✓ Branch 2 taken 60 times.
60 const char* dest1 = pct_vformat(
150 grammar::alpha_chars, pctx, fctx);
151 60 dest[n.scheme] = ':';
152 // validate
153
2/2
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 59 times.
60 if (!grammar::parse({dest, dest1}, scheme_rule()))
154 {
155 1 throw_invalid_argument();
156 }
157 }
158
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 93 times.
144 if (has_authority)
159 {
160
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 43 times.
51 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 51 times.
51 auto dest = u.set_host_impl(
187 n.host, op);
188
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 50 times.
51 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 50 pctx = {host, pctx.next_arg_id()};
205 50 fctx.advance_to(dest);
206 char const* dest1 =
207
1/1
✓ Branch 1 taken 50 times.
50 pct_vformat(host_chars, pctx, fctx);
208 50 u.impl_.decoded_[parts::id_host] =
209 50 detail::to_size_type(
210
1/1
✓ Branch 1 taken 50 times.
100 pct_string_view(dest, dest1 - dest)
211 ->decoded_size());
212 }
213 51 auto uh = u.encoded_host();
214
1/1
✓ Branch 4 taken 51 times.
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
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 34 times.
51 if (has_port)
221 {
222
1/1
✓ Branch 1 taken 17 times.
17 dest = u.set_port_impl(n.port, op);
223 17 pctx = {port, pctx.next_arg_id()};
224 17 fctx.advance_to(dest);
225
1/1
✓ Branch 2 taken 17 times.
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
1/1
✓ Branch 1 taken 17 times.
17 pct_string_view(dest, dest1 - dest)
230 17 ->decoded_size() + 1);
231 17 core::string_view up = {dest - 1, dest1};
232
1/1
✓ Branch 3 taken 17 times.
17 auto p = grammar::parse(up, detail::port_part_rule).value();
233
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 if (p.has_port)
234 17 u.impl_.port_number_ = p.port_number;
235 }
236 }
237
2/2
✓ Branch 1 taken 108 times.
✓ Branch 2 taken 36 times.
144 if (!path.empty())
238 {
239
1/1
✓ Branch 1 taken 108 times.
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
1/1
✓ Branch 1 taken 108 times.
108 auto dest1 = pct_vformat(
245 path_chars, pctx, fctx);
246
1/1
✓ Branch 1 taken 108 times.
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
1/2
✓ Branch 1 taken 108 times.
✗ Branch 2 not taken.
108 if (!npath.empty())
251 {
252 216 u.impl_.nseg_ = detail::to_size_type(
253
1/1
✓ Branch 1 taken 108 times.
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
4/4
✓ Branch 2 taken 79 times.
✓ Branch 3 taken 29 times.
✓ Branch 4 taken 79 times.
✓ Branch 5 taken 29 times.
187 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 20 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 86 times.
196 if (!u.has_authority() &&
296
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 106 times.
196 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 131 times.
144 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 137 times.
144 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 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
3/4
boost::urls::detail::pct_encoded_fmt_string_rule_t<boost::urls::grammar::implementation_defined::charset_ref<boost::urls::grammar::lut_chars> >::parse(char const*&, char const*) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
boost::urls::detail::pct_encoded_fmt_string_rule_t<boost::urls::grammar::lut_chars>::parse(char const*&, char const*) const:
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 244 times.
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
2/4
boost::urls::detail::pct_encoded_fmt_string_rule_t<boost::urls::grammar::implementation_defined::charset_ref<boost::urls::grammar::lut_chars> >::parse(char const*&, char const*) const:
✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
boost::urls::detail::pct_encoded_fmt_string_rule_t<boost::urls::grammar::lut_chars>::parse(char const*&, char const*) const:
✓ Branch 1 taken 480 times.
✗ Branch 2 not taken.
520 while (rv)
387 {
388 520 auto it0 = it;
389 // consume some with replacement id
390 // rule
391
4/4
boost::urls::detail::pct_encoded_fmt_string_rule_t<boost::urls::grammar::implementation_defined::charset_ref<boost::urls::grammar::lut_chars> >::parse(char const*&, char const*) const:
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 20 times.
boost::urls::detail::pct_encoded_fmt_string_rule_t<boost::urls::grammar::lut_chars>::parse(char const*&, char const*) const:
✓ Branch 2 taken 244 times.
✓ Branch 3 taken 236 times.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
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
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 while (rv)
472 {
473 32 auto it0 = it;
474 // consume some with replacement id
475
2/2
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 15 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 16 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
54 BOOST_ASSERT(rv);
549 54 t.user = *rv;
550
551 // ':'
552
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 17 times.
54 if( it == end ||
553
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 20 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 54 times.
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
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 2 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
55 BOOST_ASSERT(rv);
661
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 46 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
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
2/2
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 18 times.
55 if (!rv)
694 {
695 37 it = it0;
696 }
697 else
698 {
699 18 u.has_port = true;
700
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 2 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 154 times.
155 if(it == end)
724 {
725 // scheme can't be empty
726 1 BOOST_URL_RETURN_EC(
727 grammar::error::mismatch);
728 }
729
4/4
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 134 times.
284 if(!grammar::alpha_chars(*it) &&
730
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 110 times.
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
2/2
✓ Branch 1 taken 110 times.
✓ Branch 2 taken 24 times.
134 if (!grammar::alpha_chars(*it))
739 {
740
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 108 times.
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
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 52 times.
135 while (it != end)
765 {
766 83 auto it0 = it;
767
2/2
✓ Branch 2 taken 80 times.
✓ Branch 3 taken 3 times.
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
2/2
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 89 times.
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
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 151 times.
155 if (it == end)
816 {
817 // this is over, so we can consider
818 // that a "path-empty"
819 4 return u;
820 }
821
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 146 times.
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
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
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
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() ||
839
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
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
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 if(! rv)
851 1 return rv.error();
852 2 u.path = *rv;
853 }
854 2 return u;
855 }
856
857 // authority
858
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 78 times.
146 if( it[0] == '/' &&
859
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 13 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
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
2/2
✓ Branch 0 taken 119 times.
✓ Branch 1 taken 27 times.
146 if (it == end ||
881
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 91 times.
119 (u.has_authority &&
882
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 20 times.
28 (*it != '/' &&
883
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 *it != '?' &&
884
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 117 times.
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
2/2
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 104 times.
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
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 110 times.
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
957