GCC Code Coverage Report


Directory: ./
File: libs/http_proto/src/server/basic_router.cpp
Date: 2025-12-01 15:47:27
Exec Total Coverage
Lines: 375 383 97.9%
Functions: 34 34 100.0%
Branches: 238 272 87.5%

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 248 pct_decode(
97 urls::pct_string_view s)
98 {
99 248 std::string result;
100 248 core::string_view sv(s);
101
1/1
✓ Branch 2 taken 248 times.
248 result.reserve(s.size());
102 248 auto it = sv.data();
103 248 auto const end = it + sv.size();
104 for(;;)
105 {
106
2/2
✓ Branch 0 taken 248 times.
✓ Branch 1 taken 523 times.
771 if(it == end)
107 248 break;
108
2/2
✓ Branch 0 taken 521 times.
✓ Branch 1 taken 2 times.
523 if(*it != '%')
109 {
110
1/1
✓ Branch 1 taken 521 times.
521 result.push_back(*it++);
111 521 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 523 }
134 496 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 173 pct_decode_path(
146 urls::pct_string_view s)
147 {
148 173 std::string result;
149 173 core::string_view sv(s);
150
1/1
✓ Branch 2 taken 173 times.
173 result.reserve(s.size());
151 173 auto it = sv.data();
152 173 auto const end = it + sv.size();
153 for(;;)
154 {
155
2/2
✓ Branch 0 taken 173 times.
✓ Branch 1 taken 428 times.
601 if(it == end)
156 173 break;
157
2/2
✓ Branch 0 taken 424 times.
✓ Branch 1 taken 4 times.
428 if(*it != '%')
158 {
159
1/1
✓ Branch 1 taken 424 times.
424 result.push_back(*it++);
160 424 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 428 }
190 346 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 basic_request::
203 match_result
204 {
205 246 void adjust_path(
206 basic_request& req,
207 std::size_t n)
208 {
209 246 n_ = n;
210
2/2
✓ Branch 0 taken 165 times.
✓ Branch 1 taken 81 times.
246 if(n_ == 0)
211 165 return;
212 81 req.base_path = {
213 req.base_path.data(),
214 81 req.base_path.size() + n_ };
215
2/2
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 54 times.
81 if(n_ < req.path.size())
216 {
217 27 req.path.remove_prefix(n_);
218 }
219 else
220 {
221 // append a soft slash
222 54 req.path = { req.decoded_path_.data() +
223 54 req.decoded_path_.size() - 1, 1};
224
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 54 times.
54 BOOST_ASSERT(req.path == "/");
225 }
226 }
227
228 115 void restore_path(
229 basic_request& req)
230 {
231 256 if( n_ > 0 &&
232
6/6
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 89 times.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 96 times.
140 req.addedSlash_ &&
233 25 req.path.data() ==
234 25 req.decoded_path_.data() +
235
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 6 times.
25 req.decoded_path_.size() - 1)
236 {
237 // remove soft slash
238 19 req.path = {
239 19 req.base_path.data() +
240 19 req.base_path.size(), 0 };
241 }
242 115 req.base_path.remove_suffix(n_);
243 345 req.path = {
244 115 req.path.data() - n_,
245 115 req.path.size() + n_ };
246 115 }
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 248 matcher(
262 core::string_view pat,
263 bool end_)
264 248 : end(end_)
265
1/1
✓ Branch 2 taken 248 times.
248 , decoded_pat_(
266 [&pat]
267 {
268
2/2
✓ Branch 1 taken 248 times.
✓ Branch 4 taken 248 times.
248 auto s = pct_decode(pat);
269 248 if( s.size() > 1
270
6/6
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 132 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 110 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 242 times.
248 && s.back() == '/')
271 6 s.pop_back();
272 248 return s;
273
1/1
✓ Branch 1 taken 248 times.
496 }())
274 248 , slash_(pat == "/")
275 {
276
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 132 times.
248 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 248 }
280
281 /** Return true if req.path is a match
282 */
283 281 bool operator()(
284 basic_request& req,
285 match_result& mr) const
286 {
287
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 281 times.
281 BOOST_ASSERT(! req.path.empty());
288
2/2
✓ Branch 0 taken 165 times.
✓ Branch 1 taken 116 times.
446 if( slash_ && (
289
3/4
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 111 times.
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
219 ! end ||
290
2/2
✓ Branch 2 taken 165 times.
✓ Branch 3 taken 116 times.
335 req.path == "/"))
291 {
292 // params = {};
293 165 mr.adjust_path(req, 0);
294 165 return true;
295 }
296 116 auto it = req.path.data();
297 116 auto pit = pv_.segs.begin();
298 116 auto const end_ = it + req.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(! req.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 - req.path.data();
334 81 mr.adjust_path(req, 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 250 explicit entry(
359 handler_ptr h) noexcept
360 250 : handler(std::move(h))
361 250 , all(true)
362 {
363 250 }
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 basic_request const& req) 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 req.verb_ == verb;
395
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
15 if(req.verb_ != http_proto::method::unknown)
396 1 return false;
397 14 return req.verb_str_ == verb_str;
398 }
399 };
400
401 matcher match;
402 std::vector<entry> entries;
403
404 // middleware layer
405 186 layer(
406 core::string_view pat,
407 handler_list handlers)
408 186 : match(pat, false)
409 {
410
1/1
✓ Branch 1 taken 186 times.
186 entries.reserve(handlers.n);
411
2/2
✓ Branch 0 taken 236 times.
✓ Branch 1 taken 186 times.
422 for(std::size_t i = 0; i < handlers.n; ++i)
412
1/1
✓ Branch 2 taken 236 times.
236 entries.emplace_back(std::move(handlers.p[i]));
413 186 }
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 137 explicit impl(
440 opt_flags opt_) noexcept
441 137 : opt(opt_)
442 {
443 137 }
444 };
445
446 //------------------------------------------------
447
448 153 any_router::
449 137 ~any_router()
450 {
451
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 137 times.
153 if(! impl_)
452 16 return;
453
2/2
✓ Branch 1 taken 135 times.
✓ Branch 2 taken 2 times.
137 if(--impl_->refs == 0)
454
1/2
✓ Branch 0 taken 135 times.
✗ Branch 1 not taken.
135 delete impl_;
455 153 }
456
457 137 any_router::
458 any_router(
459 137 opt_flags opt)
460 137 : impl_(new impl(opt))
461 {
462 137 }
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 186 any_router::
535 add_impl(
536 core::string_view pattern,
537 handler_list const& handlers)
538 {
539
2/2
✓ Branch 1 taken 106 times.
✓ Branch 2 taken 80 times.
186 if( pattern.empty())
540 106 pattern = "/";
541 186 impl_->layers.emplace_back(
542 186 pattern, std::move(handlers));
543 186 }
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 basic_request& req, basic_response& res,
592 route_result ec) const ->
593 route_result
594 {
595
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 BOOST_ASSERT(res.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 req.base_path = { req.decoded_path_.data(), 0 };
609 4 req.path = req.decoded_path_;
610
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if(req.addedSlash_)
611 1 req.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(res.resume_ == res.pos_);
615 4 res.pos_ = 0;
616 4 res.ec_ = ec;
617 4 return do_dispatch(req, res);
618 }
619
620 // top-level dispatch that gets called first
621 route_result
622 173 any_router::
623 dispatch_impl(
624 http_proto::method verb,
625 core::string_view verb_str,
626 urls::url_view const& url,
627 basic_request& req,
628 basic_response& res) const
629 {
630 // VFALCO we could reuse the string storage by not clearing them
631 // set req.case_sensitive, req.strict to default of false
632 173 req = {};
633
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 140 times.
173 if(verb == http_proto::method::unknown)
634 {
635
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
33 BOOST_ASSERT(! verb_str.empty());
636 33 verb = http_proto::string_to_method(verb_str);
637
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 12 times.
33 if(verb == http_proto::method::unknown)
638
1/1
✓ Branch 1 taken 21 times.
21 req.verb_str_ = verb_str;
639 }
640 else
641 {
642
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 140 times.
140 BOOST_ASSERT(verb_str.empty());
643 }
644 173 req.verb_ = verb;
645 // VFALCO use reusing-StringToken
646 req.decoded_path_ =
647
1/1
✓ Branch 2 taken 173 times.
173 pct_decode_path(url.encoded_path());
648
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 173 times.
173 BOOST_ASSERT(! req.decoded_path_.empty());
649 173 req.base_path = { req.decoded_path_.data(), 0 };
650 173 req.path = req.decoded_path_;
651
2/2
✓ Branch 1 taken 55 times.
✓ Branch 2 taken 118 times.
173 if(req.decoded_path_.back() != '/')
652 {
653 55 req.decoded_path_.push_back('/');
654 55 req.addedSlash_ = true;
655 }
656
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 173 times.
173 BOOST_ASSERT(req.case_sensitive == false);
657
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 173 times.
173 BOOST_ASSERT(req.strict == false);
658
659 173 res = {};
660
661 // we cannot do anything after do_dispatch returns,
662 // other than return the route_result, or else we
663 // could race with the detached operation trying to resume.
664 173 return do_dispatch(req, res);
665 }
666
667 // recursive dispatch
668 route_result
669 193 any_router::
670 dispatch_impl(
671 basic_request& req,
672 basic_response& res) const
673 {
674 // options are recursive and need to be restored on
675 // exception or when returning to a calling router.
676 struct option_saver
677 {
678 193 option_saver(
679 basic_request& req) noexcept
680 193 : req_(&req)
681 193 , case_sensitive_(req.case_sensitive)
682 193 , strict_(req.strict)
683 {
684 193 }
685
686 193 ~option_saver()
687 179 {
688
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 179 times.
193 if(! req_)
689 14 return;
690 179 req_->case_sensitive = case_sensitive_;
691 179 req_->strict = strict_;
692 193 };
693
694 14 void cancel() noexcept
695 {
696 14 req_ = nullptr;
697 14 }
698
699 private:
700 basic_request* req_;
701 bool case_sensitive_;
702 bool strict_;
703 };
704
705 193 option_saver restore_options(req);
706
707 // inherit or apply options
708
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 189 times.
193 if((impl_->opt & 2) != 0)
709 4 req.case_sensitive = true;
710
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 187 times.
189 else if((impl_->opt & 4) != 0)
711 2 req.case_sensitive = false;
712
713
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 193 times.
193 if((impl_->opt & 8) != 0)
714 req.strict = true;
715
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 193 times.
193 else if((impl_->opt & 16) != 0)
716 req.strict = false;
717
718 // nested routers count as 1 call
719 //++res.pos_;
720
721 193 match_result mr;
722
2/2
✓ Branch 5 taken 285 times.
✓ Branch 6 taken 62 times.
347 for(auto const& i : impl_->layers)
723 {
724
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 276 times.
285 if(res.resume_ > 0)
725 {
726 9 auto const n = i.count(); // handlers in layer
727
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if(res.pos_ + n < res.resume_)
728 {
729 3 res.pos_ += n; // skip layer
730 3 continue;
731 }
732 // repeat match to recreate the stack
733
1/1
✓ Branch 1 taken 6 times.
6 bool is_match = i.match(req, mr);
734
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 BOOST_ASSERT(is_match);
735 (void)is_match;
736 }
737 else
738 {
739
6/6
✓ Branch 0 taken 87 times.
✓ Branch 1 taken 189 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 86 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 275 times.
276 if(i.match.end && res.ec_.failed())
740 {
741 // routes can't have error handlers
742 1 res.pos_ += i.count(); // skip layer
743 1 continue;
744 }
745
3/3
✓ Branch 1 taken 275 times.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 240 times.
275 if(! i.match(req, mr))
746 {
747 // not a match
748 35 res.pos_ += i.count(); // skip layer
749 35 continue;
750 }
751 }
752 246 for(auto it = i.entries.begin();
753
2/2
✓ Branch 2 taken 317 times.
✓ Branch 3 taken 109 times.
426 it != i.entries.end(); ++it)
754 {
755 317 auto const& e(*it);
756
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 309 times.
317 if(res.resume_)
757 {
758 8 auto const n = e.handler->count();
759
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if(res.pos_ + n < res.resume_)
760 {
761 2 res.pos_ += n; // skip entry
762 180 continue;
763 }
764
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 BOOST_ASSERT(e.match_method(req));
765 }
766
2/2
✓ Branch 0 taken 101 times.
✓ Branch 1 taken 208 times.
309 else if(i.match.end)
767 {
768 // check verb for match
769
2/2
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 50 times.
101 if(! e.match_method(req))
770 {
771 51 res.pos_ += e.handler->count(); // skip entry
772 51 continue;
773 }
774 }
775
776 264 route_result rv;
777 // increment before invoke
778 264 ++res.pos_;
779
2/2
✓ Branch 0 taken 260 times.
✓ Branch 1 taken 4 times.
264 if(res.pos_ != res.resume_)
780 {
781 // call the handler
782
1/1
✓ Branch 2 taken 260 times.
260 rv = e.handler->invoke(req, res);
783 // res.pos_ can be incremented further
784 // inside the above call to invoke.
785
2/2
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 246 times.
260 if(rv == route::detach)
786 {
787 // It is essential that we return immediately, without
788 // doing anything after route::detach is returned,
789 // otherwise we could race with the detached operation
790 // attempting to call resume().
791 14 restore_options.cancel();
792 127 return rv;
793 }
794 }
795 else
796 {
797 // a subrouter never detaches on its own
798
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 BOOST_ASSERT(e.handler->count() == 1);
799 // can't detach on resume
800
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
4 if(res.ec_ == route::detach)
801 1 detail::throw_invalid_argument();
802 // do resume
803 3 res.resume_ = 0;
804 3 rv = res.ec_;
805 3 res.ec_ = {};
806 }
807
2/2
✓ Branch 2 taken 137 times.
✓ Branch 3 taken 1 times.
387 if( rv == route::send ||
808
4/4
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 111 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 136 times.
387 rv == route::complete ||
809
2/2
✓ Branch 2 taken 113 times.
✓ Branch 3 taken 136 times.
386 rv == route::close)
810 113 return rv;
811
2/2
✓ Branch 2 taken 98 times.
✓ Branch 3 taken 38 times.
136 if(rv == route::next)
812 98 continue; // next entry
813
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 36 times.
38 if(rv == route::next_route)
814 {
815 // middleware can't return next_route
816
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if(! i.match.end)
817 1 detail::throw_invalid_argument();
818
2/2
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 1 times.
6 while(++it != i.entries.end())
819 5 res.pos_ += it->handler->count();
820 6 break; // skip remaining entries
821 }
822 // we must handle all route enums
823
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
36 BOOST_ASSERT(! is_route_result(rv));
824
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 34 times.
36 if(! rv.failed())
825 {
826 // handler must return non-successful error_code
827 2 detail::throw_invalid_argument();
828 }
829 // error handling mode
830 34 res.ec_ = rv;
831
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 5 times.
34 if(! i.match.end)
832 29 continue; // next entry
833 // routes don't have error handlers
834
2/2
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 5 times.
11 while(++it != i.entries.end())
835 6 res.pos_ += it->handler->count();
836 5 break; // skip remaining entries
837 }
838
839 115 mr.restore_path(req);
840 }
841
842 62 return route::next;
843 193 }
844
845 route_result
846 177 any_router::
847 do_dispatch(
848 basic_request& req,
849 basic_response& res) const
850 {
851
1/1
✓ Branch 1 taken 173 times.
177 auto rv = dispatch_impl(req, res);
852
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 173 times.
173 BOOST_ASSERT(is_route_result(rv));
853
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 173 times.
173 BOOST_ASSERT(rv != route::next_route);
854
2/2
✓ Branch 2 taken 119 times.
✓ Branch 3 taken 54 times.
173 if(rv != route::next)
855 {
856 // when rv == route::detach we must return immediately,
857 // without attempting to perform any additional operations.
858 119 return rv;
859 }
860
2/2
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 6 times.
54 if(! res.ec_.failed())
861 {
862 // unhandled route
863 48 return route::next;
864 }
865 // error condition
866 6 return res.ec_;
867 }
868
869 //} // detail
870
871 } // http_proto
872 } // boost
873