GCC Code Coverage Report


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