GCC Code Coverage Report


Directory: ./
File: libs/http_proto/src/server/basic_router.cpp
Date: 2025-12-04 09:48:34
Exec Total Coverage
Lines: 399 408 97.8%
Functions: 34 34 100.0%
Branches: 252 285 88.4%

Line Branch Exec Source
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/cppalliance/http_proto
8 //
9
10 #include "src/server/route_rule.hpp"
11 #include <boost/http_proto/server/basic_router.hpp>
12 #include <boost/http_proto/server/route_handler.hpp>
13 #include <boost/http_proto/error.hpp>
14 #include <boost/http_proto/detail/except.hpp>
15 #include <boost/url/grammar/ci_string.hpp>
16 #include <boost/url/grammar/hexdig_chars.hpp>
17 #include <boost/assert.hpp>
18 #include <atomic>
19 #include <string>
20 #include <vector>
21
22 namespace boost {
23 namespace http_proto {
24
25 //namespace detail {
26
27 // VFALCO Temporarily here until Boost.URL merges the fix
28 static
29 bool
30 96 ci_is_equal(
31 core::string_view s0,
32 core::string_view s1) noexcept
33 {
34 96 auto n = s0.size();
35
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
96 if(s1.size() != n)
36 return false;
37 96 auto p1 = s0.data();
38 96 auto p2 = s1.data();
39 char a, b;
40 // fast loop
41
2/2
✓ Branch 0 taken 313 times.
✓ Branch 1 taken 74 times.
387 while(n--)
42 {
43 313 a = *p1++;
44 313 b = *p2++;
45
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 291 times.
313 if(a != b)
46 22 goto slow;
47 }
48 74 return true;
49 do
50 {
51 3 a = *p1++;
52 3 b = *p2++;
53 25 slow:
54
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 6 times.
50 if( grammar::to_lower(a) !=
55 25 grammar::to_lower(b))
56 19 return false;
57 }
58
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 while(n--);
59 3 return true;
60 }
61
62
63 //------------------------------------------------
64 /*
65
66 pattern target path(use) path(get)
67 -------------------------------------------------
68 / / /
69 / /api /api
70 /api /api / /api
71 /api /api/ / /api/
72 /api /api/ / no-match strict
73 /api /api/v0 /v0 no-match
74 /api/ /api / /api
75 /api/ /api / no-match strict
76 /api/ /api/ / /api/
77 /api/ /api/v0 /v0 no-match
78
79 */
80
81 //------------------------------------------------
82
83 /*
84 static
85 void
86 make_lower(std::string& s)
87 {
88 for(auto& c : s)
89 c = grammar::to_lower(c);
90 }
91 */
92
93 // decode all percent escapes
94 static
95 std::string
96 265 pct_decode(
97 urls::pct_string_view s)
98 {
99 265 std::string result;
100 265 core::string_view sv(s);
101
1/1
✓ Branch 2 taken 265 times.
265 result.reserve(s.size());
102 265 auto it = sv.data();
103 265 auto const end = it + sv.size();
104 for(;;)
105 {
106
2/2
✓ Branch 0 taken 265 times.
✓ Branch 1 taken 540 times.
805 if(it == end)
107 265 break;
108
2/2
✓ Branch 0 taken 538 times.
✓ Branch 1 taken 2 times.
540 if(*it != '%')
109 {
110
1/1
✓ Branch 1 taken 538 times.
538 result.push_back(*it++);
111 538 continue;
112 }
113 2 ++it;
114 #if 0
115 // pct_string_view can never have invalid pct-encodings
116 if(it == end)
117 goto invalid;
118 #endif
119 2 auto d0 = urls::grammar::hexdig_value(*it++);
120 #if 0
121 // pct_string_view can never have invalid pct-encodings
122 if( d0 < 0 ||
123 it == end)
124 goto invalid;
125 #endif
126 2 auto d1 = urls::grammar::hexdig_value(*it++);
127 #if 0
128 // pct_string_view can never have invalid pct-encodings
129 if(d1 < 0)
130 goto invalid;
131 #endif
132
1/1
✓ Branch 1 taken 2 times.
2 result.push_back(d0 * 16 + d1);
133 540 }
134 530 return result;
135 #if 0
136 invalid:
137 // can't get here, as received a pct_string_view
138 detail::throw_invalid_argument();
139 #endif
140 }
141
142 // decode all percent escapes except slashes '/' and '\'
143 static
144 std::string
145 180 pct_decode_path(
146 urls::pct_string_view s)
147 {
148 180 std::string result;
149 180 core::string_view sv(s);
150
1/1
✓ Branch 2 taken 180 times.
180 result.reserve(s.size());
151 180 auto it = sv.data();
152 180 auto const end = it + sv.size();
153 for(;;)
154 {
155
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 435 times.
615 if(it == end)
156 180 break;
157
2/2
✓ Branch 0 taken 431 times.
✓ Branch 1 taken 4 times.
435 if(*it != '%')
158 {
159
1/1
✓ Branch 1 taken 431 times.
431 result.push_back(*it++);
160 431 continue;
161 }
162 4 ++it;
163 #if 0
164 // pct_string_view can never have invalid pct-encodings
165 if(it == end)
166 goto invalid;
167 #endif
168 4 auto d0 = urls::grammar::hexdig_value(*it++);
169 #if 0
170 // pct_string_view can never have invalid pct-encodings
171 if( d0 < 0 ||
172 it == end)
173 goto invalid;
174 #endif
175 4 auto d1 = urls::grammar::hexdig_value(*it++);
176 #if 0
177 // pct_string_view can never have invalid pct-encodings
178 if(d1 < 0)
179 goto invalid;
180 #endif
181 4 char c = d0 * 16 + d1;
182
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
4 if( c != '/' &&
183 c != '\\')
184 {
185
1/1
✓ Branch 1 taken 2 times.
2 result.push_back(c);
186 2 continue;
187 }
188
1/1
✓ Branch 1 taken 2 times.
2 result.append(it - 3, 3);
189 435 }
190 360 return result;
191 #if 0
192 invalid:
193 // can't get here, as received a pct_string_view
194 detail::throw_invalid_argument();
195 #endif
196 }
197
198 //------------------------------------------------
199
200 //} // detail
201
202 struct route_params_base::
203 match_result
204 {
205 263 void adjust_path(
206 route_params_base& p,
207 std::size_t n)
208 {
209 263 n_ = n;
210
2/2
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 81 times.
263 if(n_ == 0)
211 182 return;
212 81 p.base_path = {
213 p.base_path.data(),
214 81 p.base_path.size() + n_ };
215
2/2
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 54 times.
81 if(n_ < p.path.size())
216 {
217 27 p.path.remove_prefix(n_);
218 }
219 else
220 {
221 // append a soft slash
222 54 p.path = { p.decoded_path_.data() +
223 54 p.decoded_path_.size() - 1, 1};
224
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 54 times.
54 BOOST_ASSERT(p.path == "/");
225 }
226 }
227
228 130 void restore_path(
229 route_params_base& p)
230 {
231 286 if( n_ > 0 &&
232
6/6
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 104 times.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 111 times.
155 p.addedSlash_ &&
233 25 p.path.data() ==
234 25 p.decoded_path_.data() +
235
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 6 times.
25 p.decoded_path_.size() - 1)
236 {
237 // remove soft slash
238 19 p.path = {
239 19 p.base_path.data() +
240 19 p.base_path.size(), 0 };
241 }
242 130 p.base_path.remove_suffix(n_);
243 390 p.path = {
244 130 p.path.data() - n_,
245 130 p.path.size() + n_ };
246 130 }
247
248 private:
249 std::size_t n_ = 0; // chars moved from path to base_path
250 };
251
252 //------------------------------------------------
253
254 //namespace detail {
255
256 // Matches a path against a pattern
257 struct any_router::matcher
258 {
259 bool const end; // false for middleware
260
261 265 matcher(
262 core::string_view pat,
263 bool end_)
264 265 : end(end_)
265
1/1
✓ Branch 2 taken 265 times.
265 , decoded_pat_(
266 [&pat]
267 {
268
2/2
✓ Branch 1 taken 265 times.
✓ Branch 4 taken 265 times.
265 auto s = pct_decode(pat);
269 265 if( s.size() > 1
270
6/6
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 149 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 110 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 259 times.
265 && s.back() == '/')
271 6 s.pop_back();
272 265 return s;
273
1/1
✓ Branch 1 taken 265 times.
530 }())
274 265 , slash_(pat == "/")
275 {
276
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 149 times.
265 if(! slash_)
277
1/1
✓ Branch 2 taken 116 times.
116 pv_ = grammar::parse(
278
1/1
✓ Branch 2 taken 116 times.
116 decoded_pat_, path_rule).value();
279 265 }
280
281 /** Return true if p.path is a match
282 */
283 298 bool operator()(
284 route_params_base& p,
285 match_result& mr) const
286 {
287
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 298 times.
298 BOOST_ASSERT(! p.path.empty());
288
2/2
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 116 times.
480 if( slash_ && (
289
3/4
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 128 times.
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
236 ! end ||
290
2/2
✓ Branch 2 taken 182 times.
✓ Branch 3 taken 116 times.
352 p.path == "/"))
291 {
292 // params = {};
293 182 mr.adjust_path(p, 0);
294 182 return true;
295 }
296 116 auto it = p.path.data();
297 116 auto pit = pv_.segs.begin();
298 116 auto const end_ = it + p.path.size();
299 116 auto const pend = pv_.segs.end();
300
6/6
✓ Branch 0 taken 143 times.
✓ Branch 1 taken 54 times.
✓ Branch 3 taken 116 times.
✓ Branch 4 taken 27 times.
✓ Branch 5 taken 116 times.
✓ Branch 6 taken 81 times.
197 while(it != end_ && pit != pend)
301 {
302 // prefix has to match
303 116 auto s = core::string_view(it, end_);
304
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 6 times.
116 if(! p.case_sensitive)
305 {
306
2/2
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 96 times.
110 if(pit->prefix.size() > s.size())
307 35 return false;
308
1/1
✓ Branch 3 taken 96 times.
96 s = s.substr(0, pit->prefix.size());
309 //if(! grammar::ci_is_equal(s, pit->prefix))
310
2/2
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 77 times.
96 if(! ci_is_equal(s, pit->prefix))
311 19 return false;
312 }
313 else
314 {
315
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
6 if(! s.starts_with(pit->prefix))
316 2 return false;
317 }
318 81 it += pit->prefix.size();
319 81 ++pit;
320 }
321
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 60 times.
81 if(end)
322 {
323 // require full match
324
3/6
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 21 times.
42 if( it != end_ ||
325 21 pit != pend)
326 return false;
327 }
328
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
60 else if(pit != pend)
329 {
330 return false;
331 }
332 // number of matching characters
333 81 auto const n = it - p.path.data();
334 81 mr.adjust_path(p, n);
335 81 return true;
336 }
337
338 private:
339 stable_string decoded_pat_;
340 path_rule_t::value_type pv_;
341 bool slash_;
342 };
343
344 //------------------------------------------------
345
346 struct any_router::layer
347 {
348 struct entry
349 {
350 handler_ptr handler;
351
352 // only for end routes
353 http_proto::method verb =
354 http_proto::method::unknown;
355 std::string verb_str;
356 bool all;
357
358 268 explicit entry(
359 handler_ptr h) noexcept
360 268 : handler(std::move(h))
361 268 , all(true)
362 {
363 268 }
364
365 70 entry(
366 http_proto::method verb_,
367 handler_ptr h) noexcept
368 70 : handler(std::move(h))
369 70 , verb(verb_)
370 70 , all(false)
371 {
372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 BOOST_ASSERT(verb !=
373 http_proto::method::unknown);
374 70 }
375
376 9 entry(
377 core::string_view verb_str_,
378 handler_ptr h) noexcept
379 9 : handler(std::move(h))
380 9 , verb(http_proto::string_to_method(verb_str_))
381 9 , all(false)
382 {
383
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9 if(verb != http_proto::method::unknown)
384 2 return;
385 7 verb_str = verb_str_;
386 }
387
388 107 bool match_method(
389 route_params_base const& p) const noexcept
390 {
391
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 95 times.
107 if(all)
392 12 return true;
393
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 15 times.
95 if(verb != http_proto::method::unknown)
394 80 return p.verb_ == verb;
395
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
15 if(p.verb_ != http_proto::method::unknown)
396 1 return false;
397 14 return p.verb_str_ == verb_str;
398 }
399 };
400
401 matcher match;
402 std::vector<entry> entries;
403
404 // middleware layer
405 203 layer(
406 core::string_view pat,
407 handler_list handlers)
408 203 : match(pat, false)
409 {
410
1/1
✓ Branch 1 taken 203 times.
203 entries.reserve(handlers.n);
411
2/2
✓ Branch 0 taken 254 times.
✓ Branch 1 taken 203 times.
457 for(std::size_t i = 0; i < handlers.n; ++i)
412
1/1
✓ Branch 2 taken 254 times.
254 entries.emplace_back(std::move(handlers.p[i]));
413 203 }
414
415 // route layer
416 62 explicit layer(
417 core::string_view pat)
418 62 : match(pat, true)
419 {
420 62 }
421
422 45 std::size_t count() const noexcept
423 {
424 45 std::size_t n = 0;
425
2/2
✓ Branch 4 taken 51 times.
✓ Branch 5 taken 45 times.
96 for(auto const& e : entries)
426 51 n += e.handler->count();
427 45 return n;
428 }
429 };
430
431 //------------------------------------------------
432
433 struct any_router::impl
434 {
435 std::atomic<std::size_t> refs{1};
436 std::vector<layer> layers;
437 opt_flags opt;
438
439 144 explicit impl(
440 opt_flags opt_) noexcept
441 144 : opt(opt_)
442 {
443 144 }
444 };
445
446 //------------------------------------------------
447
448 160 any_router::
449 144 ~any_router()
450 {
451
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 144 times.
160 if(! impl_)
452 16 return;
453
2/2
✓ Branch 1 taken 142 times.
✓ Branch 2 taken 2 times.
144 if(--impl_->refs == 0)
454
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 delete impl_;
455 160 }
456
457 144 any_router::
458 any_router(
459 144 opt_flags opt)
460 144 : impl_(new impl(opt))
461 {
462 144 }
463
464 15 any_router::
465 15 any_router(any_router&& other) noexcept
466 15 :impl_(other.impl_)
467 {
468 15 other.impl_ = nullptr;
469 15 }
470
471 1 any_router::
472 1 any_router(any_router const& other) noexcept
473 {
474 1 impl_ = other.impl_;
475 1 ++impl_->refs;
476 1 }
477
478 any_router&
479 1 any_router::
480 operator=(any_router&& other) noexcept
481 {
482 1 auto p = impl_;
483 1 impl_ = other.impl_;
484 1 other.impl_ = nullptr;
485
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 if(p && --p->refs == 0)
486
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete p;
487 1 return *this;
488 }
489
490 any_router&
491 1 any_router::
492 operator=(any_router const& other) noexcept
493 {
494 1 auto p = impl_;
495 1 impl_ = other.impl_;
496 1 ++impl_->refs;
497
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 if(p && --p->refs == 0)
498
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete p;
499 1 return *this;
500 }
501
502 //------------------------------------------------
503
504 std::size_t
505 4 any_router::
506 count() const noexcept
507 {
508 4 std::size_t n = 0;
509
2/2
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 4 times.
8 for(auto const& i : impl_->layers)
510
2/2
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 4 times.
20 for(auto const& e : i.entries)
511 16 n += e.handler->count();
512 4 return n;
513 }
514
515 auto
516 63 any_router::
517 new_layer(
518 core::string_view pattern) -> layer&
519 {
520 // the pattern must not be empty
521
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 62 times.
63 if(pattern.empty())
522 1 detail::throw_invalid_argument();
523 // delete the last route if it is empty,
524 // this happens if they call route() without
525 // adding anything
526
6/6
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 61 times.
92 if(! impl_->layers.empty() &&
527 30 impl_->layers.back().entries.empty())
528 1 impl_->layers.pop_back();
529 62 impl_->layers.emplace_back(pattern);
530 62 return impl_->layers.back();
531 };
532
533 void
534 203 any_router::
535 add_impl(
536 core::string_view pattern,
537 handler_list const& handlers)
538 {
539
2/2
✓ Branch 1 taken 123 times.
✓ Branch 2 taken 80 times.
203 if( pattern.empty())
540 123 pattern = "/";
541 203 impl_->layers.emplace_back(
542 203 pattern, std::move(handlers));
543 203 }
544
545 void
546 68 any_router::
547 add_impl(
548 layer& e,
549 http_proto::method verb,
550 handler_list const& handlers)
551 {
552 // cannot be unknown
553
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 67 times.
68 if(verb == http_proto::method::unknown)
554 1 detail::throw_invalid_argument();
555
556 67 e.entries.reserve(e.entries.size() + handlers.n);
557
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 67 times.
137 for(std::size_t i = 0; i < handlers.n; ++i)
558 70 e.entries.emplace_back(verb,
559 70 std::move(handlers.p[i]));
560 67 }
561
562 void
563 23 any_router::
564 add_impl(
565 layer& e,
566 core::string_view verb_str,
567 handler_list const& handlers)
568 {
569 23 e.entries.reserve(e.entries.size() + handlers.n);
570
571
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 9 times.
23 if(verb_str.empty())
572 {
573 // all
574
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
28 for(std::size_t i = 0; i < handlers.n; ++i)
575 14 e.entries.emplace_back(
576 14 std::move(handlers.p[i]));
577 14 return;
578 }
579
580 // possibly custom string
581
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 9 times.
18 for(std::size_t i = 0; i < handlers.n; ++i)
582 9 e.entries.emplace_back(verb_str,
583 9 std::move(handlers.p[i]));
584 }
585
586 //------------------------------------------------
587
588 auto
589 9 any_router::
590 resume_impl(
591 route_params_base& p,
592 route_result ec) const ->
593 route_result
594 {
595
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 BOOST_ASSERT(p.resume_ > 0);
596
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
17 if( ec == route::send ||
597
4/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 6 times.
17 ec == route::close ||
598
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 6 times.
16 ec == route::complete)
599 3 return ec;
600
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
6 if(! is_route_result(ec))
601 {
602 // must indicate failure
603
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if(! ec.failed())
604 2 detail::throw_invalid_argument();
605 }
606
607 // restore base_path and path
608 4 p.base_path = { p.decoded_path_.data(), 0 };
609 4 p.path = p.decoded_path_;
610
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if(p.addedSlash_)
611 1 p.path.remove_suffix(1);
612
613 // resume_ was set in the handler's wrapper
614
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 BOOST_ASSERT(p.resume_ == p.pos_);
615 4 p.pos_ = 0;
616 4 p.ec_ = ec;
617 4 return do_dispatch(p);
618 }
619
620 // top-level dispatch that gets called first
621 route_result
622 180 any_router::
623 dispatch_impl(
624 http_proto::method verb,
625 core::string_view verb_str,
626 urls::url_view const& url,
627 route_params_base& p) const
628 {
629 180 p.verb_str_.clear();
630 180 p.decoded_path_.clear();
631 180 p.ec_.clear();
632 180 p.ep_ = nullptr;
633 180 p.pos_ = 0;
634 180 p.resume_ = 0;
635 180 p.addedSlash_ = false;
636 180 p.case_sensitive = false;
637 180 p.strict = false;
638
639
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 147 times.
180 if(verb == http_proto::method::unknown)
640 {
641
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
33 BOOST_ASSERT(! verb_str.empty());
642
1/1
✓ Branch 1 taken 33 times.
33 verb = http_proto::string_to_method(verb_str);
643
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 12 times.
33 if(verb == http_proto::method::unknown)
644
1/1
✓ Branch 1 taken 21 times.
21 p.verb_str_ = verb_str;
645 }
646 else
647 {
648
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 147 times.
147 BOOST_ASSERT(verb_str.empty());
649 }
650 180 p.verb_ = verb;
651
652 // VFALCO use reusing-StringToken
653 p.decoded_path_ =
654
1/1
✓ Branch 2 taken 180 times.
180 pct_decode_path(url.encoded_path());
655
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 180 times.
180 BOOST_ASSERT(! p.decoded_path_.empty());
656 180 p.base_path = { p.decoded_path_.data(), 0 };
657 180 p.path = p.decoded_path_;
658
2/2
✓ Branch 1 taken 55 times.
✓ Branch 2 taken 125 times.
180 if(p.decoded_path_.back() != '/')
659 {
660
1/1
✓ Branch 1 taken 55 times.
55 p.decoded_path_.push_back('/');
661 55 p.addedSlash_ = true;
662 }
663
664 // we cannot do anything after do_dispatch returns,
665 // other than return the route_result, or else we
666 // could race with the detached operation trying to resume.
667
1/1
✓ Branch 1 taken 177 times.
180 auto rv = do_dispatch(p);
668
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 165 times.
177 if(rv == route::detach)
669 12 return rv;
670
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 161 times.
165 if(p.ep_)
671 {
672 4 p.ep_ = nullptr;
673 4 return error::unhandled_exception;
674 }
675
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 155 times.
161 if( p.ec_.failed())
676 6 p.ec_ = {};
677 161 return rv;
678 }
679
680 // recursive dispatch
681 route_result
682 200 any_router::
683 dispatch_impl(
684 route_params_base& p) const
685 {
686 // options are recursive and need to be restored on
687 // exception or when returning to a calling router.
688 struct option_saver
689 {
690 200 option_saver(
691 route_params_base& p) noexcept
692 200 : p_(&p)
693 200 , case_sensitive_(p.case_sensitive)
694 200 , strict_(p.strict)
695 {
696 200 }
697
698 200 ~option_saver()
699 186 {
700
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 186 times.
200 if(! p_)
701 14 return;
702 186 p_->case_sensitive = case_sensitive_;
703 186 p_->strict = strict_;
704 200 };
705
706 14 void cancel() noexcept
707 {
708 14 p_ = nullptr;
709 14 }
710
711 private:
712 route_params_base* p_;
713 bool case_sensitive_;
714 bool strict_;
715 };
716
717 200 option_saver restore_options(p);
718
719 // inherit or apply options
720
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 196 times.
200 if((impl_->opt & 2) != 0)
721 4 p.case_sensitive = true;
722
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 194 times.
196 else if((impl_->opt & 4) != 0)
723 2 p.case_sensitive = false;
724
725
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 200 times.
200 if((impl_->opt & 8) != 0)
726 p.strict = true;
727
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 200 times.
200 else if((impl_->opt & 16) != 0)
728 p.strict = false;
729
730 200 match_result mr;
731
2/2
✓ Branch 5 taken 302 times.
✓ Branch 6 taken 67 times.
369 for(auto const& i : impl_->layers)
732 {
733
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 293 times.
302 if(p.resume_ > 0)
734 {
735 9 auto const n = i.count(); // handlers in layer
736
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if(p.pos_ + n < p.resume_)
737 {
738 3 p.pos_ += n; // skip layer
739 3 continue;
740 }
741 // repeat match to recreate the stack
742
1/1
✓ Branch 1 taken 6 times.
6 bool is_match = i.match(p, mr);
743
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 BOOST_ASSERT(is_match);
744 (void)is_match;
745 }
746 else
747 {
748
6/6
✓ Branch 0 taken 87 times.
✓ Branch 1 taken 206 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 86 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 292 times.
293 if(i.match.end && p.ec_.failed())
749 {
750 // routes can't have error handlers
751 1 p.pos_ += i.count(); // skip layer
752 1 continue;
753 }
754
3/3
✓ Branch 1 taken 292 times.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 257 times.
292 if(! i.match(p, mr))
755 {
756 // not a match
757 35 p.pos_ += i.count(); // skip layer
758 35 continue;
759 }
760 }
761 263 for(auto it = i.entries.begin();
762
2/2
✓ Branch 2 taken 335 times.
✓ Branch 3 taken 124 times.
459 it != i.entries.end(); ++it)
763 {
764 335 auto const& e(*it);
765
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 327 times.
335 if(p.resume_)
766 {
767 8 auto const n = e.handler->count();
768
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if(p.pos_ + n < p.resume_)
769 {
770 2 p.pos_ += n; // skip entry
771 196 continue;
772 }
773
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 BOOST_ASSERT(e.match_method(p));
774 }
775
2/2
✓ Branch 0 taken 101 times.
✓ Branch 1 taken 226 times.
327 else if(i.match.end)
776 {
777 // check verb for match
778
2/2
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 50 times.
101 if(! e.match_method(p))
779 {
780 51 p.pos_ += e.handler->count(); // skip entry
781 51 continue;
782 }
783 }
784
785 282 route_result rv;
786 // increment before invoke
787 282 ++p.pos_;
788
2/2
✓ Branch 0 taken 278 times.
✓ Branch 1 taken 4 times.
282 if(p.pos_ != p.resume_)
789 {
790 // call the handler
791 #ifdef BOOST_NO_EXCEPTIONS
792 rv = e.handler->invoke(p);
793 #else
794 try
795 {
796
1/1
✓ Branch 2 taken 272 times.
278 rv = e.handler->invoke(p);
797
2/2
✓ Branch 1 taken 57 times.
✓ Branch 2 taken 215 times.
272 if(p.ec_.failed())
798 57 p.ep_ = {}; // transition to error mode
799 }
800 6 catch(...)
801 {
802
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if(p.ec_.failed())
803 p.ec_ = {}; // transition to except mode
804 6 p.ep_ = std::current_exception();
805 6 rv = route::next;
806 6 }
807 #endif
808
809 // p.pos_ can be incremented further
810 // inside the above call to invoke.
811
2/2
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 264 times.
278 if(rv == route::detach)
812 {
813 // It is essential that we return immediately, without
814 // doing anything after route::detach is returned,
815 // otherwise we could race with the detached operation
816 // attempting to call resume().
817 14 restore_options.cancel();
818 129 return rv;
819 }
820 }
821 else
822 {
823 // a subrouter never detaches on its own
824
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 BOOST_ASSERT(e.handler->count() == 1);
825 // can't detach on resume
826
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
4 if(p.ec_ == route::detach)
827 1 detail::throw_invalid_argument();
828 // do resume
829 3 p.resume_ = 0;
830 3 rv = p.ec_;
831 3 p.ec_ = {};
832 }
833
2/2
✓ Branch 2 taken 153 times.
✓ Branch 3 taken 1 times.
421 if( rv == route::send ||
834
4/4
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 113 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 152 times.
421 rv == route::complete ||
835
2/2
✓ Branch 2 taken 115 times.
✓ Branch 3 taken 152 times.
420 rv == route::close)
836 {
837
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 96 times.
115 if( p.ec_.failed())
838 19 p.ec_ = {};
839
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 113 times.
115 if( p.ep_)
840 2 p.ep_ = nullptr;
841 115 return rv;
842 }
843
2/2
✓ Branch 2 taken 114 times.
✓ Branch 3 taken 38 times.
152 if(rv == route::next)
844 114 continue; // next entry
845
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 36 times.
38 if(rv == route::next_route)
846 {
847 // middleware can't return next_route
848
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if(! i.match.end)
849 1 detail::throw_invalid_argument();
850
2/2
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 1 times.
6 while(++it != i.entries.end())
851 5 p.pos_ += it->handler->count();
852 6 break; // skip remaining entries
853 }
854 // we must handle all route enums
855
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
36 BOOST_ASSERT(! is_route_result(rv));
856
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 34 times.
36 if(! rv.failed())
857 {
858 // handler must return non-successful error_code
859 2 detail::throw_invalid_argument();
860 }
861 // error handling mode
862 34 p.ec_ = rv;
863
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 5 times.
34 if(! i.match.end)
864 29 continue; // next entry
865 // routes don't have error handlers
866
2/2
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 5 times.
11 while(++it != i.entries.end())
867 6 p.pos_ += it->handler->count();
868 5 break; // skip remaining entries
869 }
870
871 130 mr.restore_path(p);
872 }
873
874 67 return route::next;
875 200 }
876
877 route_result
878 184 any_router::
879 do_dispatch(
880 route_params_base& p) const
881 {
882
1/1
✓ Branch 1 taken 180 times.
184 auto rv = dispatch_impl(p);
883
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 180 times.
180 BOOST_ASSERT(is_route_result(rv));
884
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 180 times.
180 BOOST_ASSERT(rv != route::next_route);
885
2/2
✓ Branch 2 taken 121 times.
✓ Branch 3 taken 59 times.
180 if(rv != route::next)
886 {
887 // when rv == route::detach we must return immediately,
888 // without attempting to perform any additional operations.
889 121 return rv;
890 }
891
2/2
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 6 times.
59 if(! p.ec_.failed())
892 {
893 // unhandled route
894 53 return route::next;
895 }
896 // error condition
897 6 return p.ec_;
898 }
899
900 //} // detail
901
902 } // http_proto
903 } // boost
904