GCC Code Coverage Report


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