libs/http/src/server/detail/router_base.cpp

92.0% Lines (219/238) 94.1% Functions (16/17) 80.0% Branches (120/150)
libs/http/src/server/detail/router_base.cpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/http
8 //
9
10 #include "src/server/detail/router_base.hpp"
11 #include <boost/http/server/detail/router_base.hpp>
12 #include <boost/http/detail/except.hpp>
13 #include <boost/http/error.hpp>
14 #include <boost/http/field.hpp>
15 #include <boost/http/status.hpp>
16 #include <boost/url/grammar/ci_string.hpp>
17 #include <boost/url/grammar/hexdig_chars.hpp>
18 #include "src/server/detail/pct_decode.hpp"
19
20 #include <algorithm>
21
22 namespace boost {
23 namespace http {
24 namespace detail {
25
26 //------------------------------------------------
27 //
28 // impl helpers
29 //
30 //------------------------------------------------
31
32 std::string
33 300 router_base::impl::
34 build_allow_header(
35 std::uint64_t methods,
36 std::vector<std::string> const& custom)
37 {
38
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 280 times.
300 if(methods == ~0ULL)
39
1/1
✓ Branch 1 taken 20 times.
40 return "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT";
40
41 280 std::string result;
42 static constexpr std::pair<http::method, char const*> known[] = {
43 {http::method::acl, "ACL"},
44 {http::method::bind, "BIND"},
45 {http::method::checkout, "CHECKOUT"},
46 {http::method::connect, "CONNECT"},
47 {http::method::copy, "COPY"},
48 {http::method::delete_, "DELETE"},
49 {http::method::get, "GET"},
50 {http::method::head, "HEAD"},
51 {http::method::link, "LINK"},
52 {http::method::lock, "LOCK"},
53 {http::method::merge, "MERGE"},
54 {http::method::mkactivity, "MKACTIVITY"},
55 {http::method::mkcalendar, "MKCALENDAR"},
56 {http::method::mkcol, "MKCOL"},
57 {http::method::move, "MOVE"},
58 {http::method::msearch, "M-SEARCH"},
59 {http::method::notify, "NOTIFY"},
60 {http::method::options, "OPTIONS"},
61 {http::method::patch, "PATCH"},
62 {http::method::post, "POST"},
63 {http::method::propfind, "PROPFIND"},
64 {http::method::proppatch, "PROPPATCH"},
65 {http::method::purge, "PURGE"},
66 {http::method::put, "PUT"},
67 {http::method::rebind, "REBIND"},
68 {http::method::report, "REPORT"},
69 {http::method::search, "SEARCH"},
70 {http::method::subscribe, "SUBSCRIBE"},
71 {http::method::trace, "TRACE"},
72 {http::method::unbind, "UNBIND"},
73 {http::method::unlink, "UNLINK"},
74 {http::method::unlock, "UNLOCK"},
75 {http::method::unsubscribe, "UNSUBSCRIBE"},
76 };
77
2/2
✓ Branch 2 taken 9240 times.
✓ Branch 3 taken 280 times.
9520 for(auto const& [m, name] : known)
78 {
79
2/2
✓ Branch 0 taken 274 times.
✓ Branch 1 taken 8966 times.
9240 if(methods & (1ULL << static_cast<unsigned>(m)))
80 {
81
2/2
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 228 times.
274 if(!result.empty())
82
1/1
✓ Branch 1 taken 46 times.
46 result += ", ";
83
1/1
✓ Branch 1 taken 274 times.
274 result += name;
84 }
85 }
86
2/2
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 280 times.
288 for(auto const& v : custom)
87 {
88
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 6 times.
8 if(!result.empty())
89
1/1
✓ Branch 1 taken 2 times.
2 result += ", ";
90
1/1
✓ Branch 1 taken 8 times.
8 result += v;
91 }
92 280 return result;
93 280 }
94
95 router_base::opt_flags
96 598 router_base::impl::
97 compute_effective_opts(
98 opt_flags parent,
99 opt_flags child)
100 {
101 598 opt_flags result = parent;
102
103 // case_sensitive: bits 1-2 (2=true, 4=false)
104
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 594 times.
598 if(child & 2)
105 4 result = (result & ~6) | 2;
106
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 590 times.
594 else if(child & 4)
107 4 result = (result & ~6) | 4;
108
109 // strict: bits 3-4 (8=true, 16=false)
110
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 597 times.
598 if(child & 8)
111 1 result = (result & ~24) | 8;
112
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 596 times.
597 else if(child & 16)
113 1 result = (result & ~24) | 16;
114
115 598 return result;
116 }
117
118 void
119 127 router_base::impl::
120 restore_path(
121 route_params& p,
122 std::size_t base_len)
123 {
124 127 auto& pv = *route_params_access{p};
125 127 p.base_path = { pv.decoded_path_.data(), base_len };
126
2/2
✓ Branch 1 taken 87 times.
✓ Branch 2 taken 40 times.
127 auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0);
127
2/2
✓ Branch 0 taken 126 times.
✓ Branch 1 taken 1 time.
127 if(base_len < path_len)
128 126 p.path = { pv.decoded_path_.data() + base_len,
129 path_len - base_len };
130 else
131 2 p.path = { pv.decoded_path_.data() +
132 1 pv.decoded_path_.size() - 1, 1 }; // soft slash
133 127 }
134
135 void
136 111 router_base::impl::
137 update_allow_for_entry(
138 matcher& m,
139 entry const& e)
140 {
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 111 times.
111 if(!m.end_)
142 return;
143
144 // Per-matcher collection
145
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 102 times.
111 if(e.all)
146 9 m.allowed_methods_ = ~0ULL;
147
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 3 times.
102 else if(e.verb != http::method::unknown)
148 99 m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
149
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 else if(!e.verb_str.empty())
150 3 m.custom_verbs_.push_back(e.verb_str);
151
152 // Rebuild per-matcher Allow header eagerly
153 111 m.allow_header_ = build_allow_header(
154
1/1
✓ Branch 1 taken 111 times.
111 m.allowed_methods_, m.custom_verbs_);
155
156 // Global collection (for OPTIONS *)
157
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 102 times.
111 if(e.all)
158 9 global_methods_ = ~0ULL;
159
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 3 times.
102 else if(e.verb != http::method::unknown)
160 99 global_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
161
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 else if(!e.verb_str.empty())
162 3 global_custom_verbs_.push_back(e.verb_str);
163 }
164
165 void
166 165 router_base::impl::
167 rebuild_global_allow_header()
168 {
169 165 std::sort(global_custom_verbs_.begin(), global_custom_verbs_.end());
170
1/1
✓ Branch 3 taken 165 times.
330 global_custom_verbs_.erase(
171
1/1
✓ Branch 3 taken 165 times.
165 std::unique(global_custom_verbs_.begin(), global_custom_verbs_.end()),
172 165 global_custom_verbs_.end());
173 165 global_allow_header_ = build_allow_header(
174
1/1
✓ Branch 1 taken 165 times.
165 global_methods_, global_custom_verbs_);
175 165 }
176
177 void
178 481 router_base::impl::
179 finalize_pending()
180 {
181
2/2
✓ Branch 0 taken 378 times.
✓ Branch 1 taken 103 times.
481 if(pending_route_ == SIZE_MAX)
182 378 return;
183 103 auto& m = matchers[pending_route_];
184
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 103 times.
103 if(entries.size() == m.first_entry_)
185 {
186 // empty route, remove it
187 matchers.pop_back();
188 }
189 else
190 {
191 103 m.skip_ = entries.size();
192 }
193 103 pending_route_ = SIZE_MAX;
194 }
195
196 //------------------------------------------------
197 //
198 // dispatch
199 //
200 //------------------------------------------------
201
202 route_task
203
1/1
✓ Branch 1 taken 204 times.
204 router_base::impl::
204 dispatch_loop(route_params& p, bool is_options) const
205 {
206 auto& pv = *route_params_access{p};
207
208 std::size_t last_matched = SIZE_MAX;
209 std::uint32_t current_depth = 0;
210
211 std::uint64_t matched_methods = 0;
212 std::vector<std::string> matched_custom_verbs;
213
214 std::size_t path_stack[router_base::max_path_depth];
215 path_stack[0] = 0;
216
217 std::size_t matched_at_depth[router_base::max_path_depth];
218 for(std::size_t d = 0; d < router_base::max_path_depth; ++d)
219 matched_at_depth[d] = SIZE_MAX;
220
221 for(std::size_t i = 0; i < entries.size(); )
222 {
223 auto const& e = entries[i];
224 auto const& m = matchers[e.matcher_idx];
225 auto const target_depth = m.depth_;
226
227 bool ancestors_ok = true;
228
229 std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1;
230
231 for(std::size_t check_idx = start_idx;
232 check_idx <= e.matcher_idx && ancestors_ok;
233 ++check_idx)
234 {
235 auto const& cm = matchers[check_idx];
236
237 bool is_needed_ancestor = (cm.depth_ < target_depth) &&
238 (matched_at_depth[cm.depth_] == SIZE_MAX);
239 bool is_self = (check_idx == e.matcher_idx);
240
241 if(!is_needed_ancestor && !is_self)
242 continue;
243
244 if(cm.depth_ <= current_depth && current_depth > 0)
245 {
246 restore_path(p, path_stack[cm.depth_]);
247 }
248
249 if(cm.end_ && pv.kind_ != router_base::is_plain)
250 {
251 i = cm.skip_;
252 ancestors_ok = false;
253 break;
254 }
255
256 pv.case_sensitive = (cm.effective_opts_ & 2) != 0;
257 pv.strict = (cm.effective_opts_ & 8) != 0;
258
259 if(cm.depth_ < router_base::max_path_depth)
260 path_stack[cm.depth_] = p.base_path.size();
261
262 match_result mr;
263 if(!cm(p, mr))
264 {
265 for(std::size_t d = cm.depth_; d < router_base::max_path_depth; ++d)
266 matched_at_depth[d] = SIZE_MAX;
267 i = cm.skip_;
268 ancestors_ok = false;
269 break;
270 }
271
272 if(!mr.params_.empty())
273 {
274 for(auto& param : mr.params_)
275 p.params.push_back(std::move(param));
276 }
277
278 if(cm.depth_ < router_base::max_path_depth)
279 matched_at_depth[cm.depth_] = check_idx;
280
281 last_matched = check_idx;
282 current_depth = cm.depth_ + 1;
283
284 if(current_depth < router_base::max_path_depth)
285 path_stack[current_depth] = p.base_path.size();
286 }
287
288 if(!ancestors_ok)
289 continue;
290
291 // Collect methods from matching end-route matchers
292 if(m.end_)
293 {
294 matched_methods |= m.allowed_methods_;
295 for(auto const& v : m.custom_verbs_)
296 matched_custom_verbs.push_back(v);
297 }
298
299 if(m.end_ && !e.match_method(
300 const_cast<route_params&>(p)))
301 {
302 ++i;
303 continue;
304 }
305
306 if(e.h->kind != pv.kind_)
307 {
308 ++i;
309 continue;
310 }
311
312 //--------------------------------------------------
313 // Invoke handler
314 //--------------------------------------------------
315
316 route_result rv;
317 try
318 {
319 rv = co_await e.h->invoke(
320 const_cast<route_params&>(p));
321 }
322 catch(...)
323 {
324 pv.ep_ = std::current_exception();
325 pv.kind_ = router_base::is_exception;
326 ++i;
327 continue;
328 }
329
330 if(rv.what() == route_what::next)
331 {
332 ++i;
333 continue;
334 }
335
336 if(rv.what() == route_what::next_route)
337 {
338 if(!m.end_)
339 co_return route_error(error::invalid_route_result);
340 i = m.skip_;
341 continue;
342 }
343
344 if(rv.what() == route_what::done ||
345 rv.what() == route_what::close)
346 {
347 co_return rv;
348 }
349
350 // Error - transition to error mode
351 pv.ec_ = rv.error();
352 pv.kind_ = router_base::is_error;
353
354 if(m.end_)
355 {
356 i = m.skip_;
357 continue;
358 }
359
360 ++i;
361 }
362
363 if(pv.kind_ == router_base::is_exception)
364 co_return route_error(error::unhandled_exception);
365 if(pv.kind_ == router_base::is_error)
366 co_return route_error(pv.ec_);
367
368 // OPTIONS fallback
369 if(is_options && matched_methods != 0 && options_handler_)
370 {
371 std::string allow = build_allow_header(matched_methods, matched_custom_verbs);
372 co_return co_await options_handler_->invoke(p, allow);
373 }
374
375 // 405 fallback: path matched but method didn't
376 if(!is_options &&
377 (matched_methods != 0 || !matched_custom_verbs.empty()))
378 {
379 std::string allow = build_allow_header(matched_methods, matched_custom_verbs);
380 p.res.set(field::allow, allow);
381 p.res.set_status(status::method_not_allowed);
382 (void)(co_await p.send());
383 co_return route_done;
384 }
385
386 co_return route_next;
387 408 }
388
389 //------------------------------------------------
390 //
391 // router_base
392 //
393 //------------------------------------------------
394
395 222 router_base::
396 router_base(
397 222 opt_flags opt)
398 222 : impl_(std::make_shared<impl>(opt))
399 {
400 222 }
401
402 void
403 107 router_base::
404 add_middleware(
405 std::string_view pattern,
406 handlers hn)
407 {
408 107 impl_->finalize_pending();
409
410
2/2
✓ Branch 1 taken 76 times.
✓ Branch 2 taken 31 times.
107 if(pattern.empty())
411 76 pattern = "/";
412
413 107 auto const matcher_idx = impl_->matchers.size();
414
1/1
✓ Branch 2 taken 107 times.
107 impl_->matchers.emplace_back(pattern, false);
415 107 auto& m = impl_->matchers.back();
416
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 107 times.
107 if(m.error())
417 throw_invalid_argument();
418 107 m.first_entry_ = impl_->entries.size();
419 107 m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
420 107 m.own_opts_ = impl_->opt_;
421 107 m.depth_ = 0;
422
423
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 107 times.
223 for(std::size_t i = 0; i < hn.n; ++i)
424 {
425 116 impl_->entries.emplace_back(std::move(hn.p[i]));
426 116 impl_->entries.back().matcher_idx = matcher_idx;
427 }
428
429 107 m.skip_ = impl_->entries.size();
430 107 }
431
432 void
433 55 router_base::
434 inline_router(
435 std::string_view pattern,
436 router_base&& sub)
437 {
438 55 impl_->finalize_pending();
439
440
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
55 if(!sub.impl_)
441 return;
442
443 55 sub.impl_->finalize_pending();
444
445
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
55 if(pattern.empty())
446 pattern = "/";
447
448 // Create parent matcher for the mount point
449 55 auto const parent_matcher_idx = impl_->matchers.size();
450
1/1
✓ Branch 2 taken 55 times.
55 impl_->matchers.emplace_back(pattern, false);
451 55 auto& parent_m = impl_->matchers.back();
452
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 55 times.
55 if(parent_m.error())
453 throw_invalid_argument();
454 55 parent_m.first_entry_ = impl_->entries.size();
455
456 55 auto parent_eff = impl::compute_effective_opts(0, impl_->opt_);
457 55 parent_m.effective_opts_ = parent_eff;
458 55 parent_m.own_opts_ = impl_->opt_;
459 55 parent_m.depth_ = 0;
460
461 // Check nesting depth
462 55 std::size_t max_sub_depth = 0;
463
2/2
✓ Branch 5 taken 294 times.
✓ Branch 6 taken 55 times.
349 for(auto const& sm : sub.impl_->matchers)
464 588 max_sub_depth = (std::max)(max_sub_depth,
465 294 static_cast<std::size_t>(sm.depth_));
466
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 54 times.
55 if(max_sub_depth + 1 >= max_path_depth)
467 1 throw_length_error(
468 "router nesting depth exceeds max_path_depth");
469
470 // Compute offsets for re-indexing
471 54 auto const matcher_offset = impl_->matchers.size();
472 54 auto const entry_offset = impl_->entries.size();
473
474 // Recompute effective_opts for inlined matchers using depth stack
475 54 auto sub_root_eff = impl::compute_effective_opts(
476 54 parent_eff, sub.impl_->opt_);
477 opt_flags eff_stack[max_path_depth];
478 54 eff_stack[0] = sub_root_eff;
479
480 // Inline sub's matchers
481
2/2
✓ Branch 6 taken 278 times.
✓ Branch 7 taken 54 times.
332 for(auto& sm : sub.impl_->matchers)
482 {
483 278 auto d = sm.depth_;
484
2/2
✓ Branch 0 taken 219 times.
✓ Branch 1 taken 59 times.
278 opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff;
485 278 eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_);
486 278 sm.effective_opts_ = eff_stack[d];
487 278 sm.depth_ += 1; // increase by 1 (parent is at depth 0)
488 278 sm.first_entry_ += entry_offset;
489 278 sm.skip_ += entry_offset;
490
1/1
✓ Branch 3 taken 278 times.
278 impl_->matchers.push_back(std::move(sm));
491 }
492
493 // Inline sub's entries
494
2/2
✓ Branch 6 taken 60 times.
✓ Branch 7 taken 54 times.
114 for(auto& se : sub.impl_->entries)
495 {
496 60 se.matcher_idx += matcher_offset;
497
1/1
✓ Branch 3 taken 60 times.
60 impl_->entries.push_back(std::move(se));
498 }
499
500 // Set parent matcher's skip
501 // Need to re-fetch since vector may have reallocated
502 54 impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size();
503
504 // Merge global methods
505 54 impl_->global_methods_ |= sub.impl_->global_methods_;
506
1/2
✗ Branch 6 not taken.
✓ Branch 7 taken 54 times.
54 for(auto& v : sub.impl_->global_custom_verbs_)
507 impl_->global_custom_verbs_.push_back(std::move(v));
508
1/1
✓ Branch 2 taken 54 times.
54 impl_->rebuild_global_allow_header();
509
510 // Move options handler if sub has one and parent doesn't
511
3/6
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 54 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 54 times.
54 if(sub.impl_->options_handler_ && !impl_->options_handler_)
512 impl_->options_handler_ = std::move(sub.impl_->options_handler_);
513
514 54 sub.impl_.reset();
515 }
516
517 std::size_t
518 116 router_base::
519 new_route(
520 std::string_view pattern)
521 {
522 116 impl_->finalize_pending();
523
524
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
116 if(pattern.empty())
525 throw_invalid_argument();
526
527 116 auto const idx = impl_->matchers.size();
528
1/1
✓ Branch 2 taken 116 times.
116 impl_->matchers.emplace_back(pattern, true);
529 116 auto& m = impl_->matchers.back();
530
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 104 times.
116 if(m.error())
531 12 throw_invalid_argument();
532 104 m.first_entry_ = impl_->entries.size();
533 104 m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
534 104 m.own_opts_ = impl_->opt_;
535 104 m.depth_ = 0;
536
537 104 impl_->pending_route_ = idx;
538 104 return idx;
539 }
540
541 void
542 99 router_base::
543 add_to_route(
544 std::size_t idx,
545 http::method verb,
546 handlers hn)
547 {
548
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if(verb == http::method::unknown)
549 throw_invalid_argument();
550
551 99 auto& m = impl_->matchers[idx];
552
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 99 times.
198 for(std::size_t i = 0; i < hn.n; ++i)
553 {
554 99 impl_->entries.emplace_back(verb, std::move(hn.p[i]));
555 99 impl_->entries.back().matcher_idx = idx;
556 99 impl_->update_allow_for_entry(m, impl_->entries.back());
557 }
558 99 impl_->rebuild_global_allow_header();
559 99 }
560
561 void
562 12 router_base::
563 add_to_route(
564 std::size_t idx,
565 std::string_view verb,
566 handlers hn)
567 {
568 12 auto& m = impl_->matchers[idx];
569
570
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
12 if(verb.empty())
571 {
572 // all methods
573
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 9 times.
18 for(std::size_t i = 0; i < hn.n; ++i)
574 {
575 9 impl_->entries.emplace_back(std::move(hn.p[i]));
576 9 impl_->entries.back().matcher_idx = idx;
577 9 impl_->update_allow_for_entry(m, impl_->entries.back());
578 }
579 }
580 else
581 {
582 // specific method string
583
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for(std::size_t i = 0; i < hn.n; ++i)
584 {
585 3 impl_->entries.emplace_back(verb, std::move(hn.p[i]));
586 3 impl_->entries.back().matcher_idx = idx;
587 3 impl_->update_allow_for_entry(m, impl_->entries.back());
588 }
589 }
590 12 impl_->rebuild_global_allow_header();
591 12 }
592
593 void
594 router_base::
595 finalize_pending()
596 {
597 if(impl_)
598 impl_->finalize_pending();
599 }
600
601 void
602 224 router_base::
603 set_options_handler_impl(
604 options_handler_ptr p)
605 {
606 224 impl_->options_handler_ = std::move(p);
607 224 }
608
609 //------------------------------------------------
610 //
611 // dispatch
612 //
613 //------------------------------------------------
614
615 route_task
616 202 router_base::
617 dispatch(
618 http::method verb,
619 urls::url_view const& url,
620 route_params& p) const
621 {
622
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 201 times.
202 if(verb == http::method::unknown)
623 1 throw_invalid_argument();
624
625 201 impl_->ensure_finalized();
626
627 // Handle OPTIONS * before normal dispatch
628
4/4
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 155 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 44 times.
247 if(verb == http::method::options &&
629
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 199 times.
247 url.encoded_path() == "*")
630 {
631
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if(impl_->options_handler_)
632 {
633 2 return impl_->options_handler_->invoke(
634 2 p, impl_->global_allow_header_);
635 }
636 }
637
638 // Initialize params
639 199 auto& pv = *route_params_access{p};
640 199 pv.kind_ = is_plain;
641 199 pv.verb_ = verb;
642 199 pv.verb_str_.clear();
643 199 pv.ec_.clear();
644 199 pv.ep_ = nullptr;
645 199 p.params.clear();
646
1/1
✓ Branch 2 taken 199 times.
199 pv.decoded_path_ = pct_decode_path(url.encoded_path());
647
6/6
✓ Branch 1 taken 198 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 113 times.
✓ Branch 5 taken 85 times.
✓ Branch 6 taken 114 times.
✓ Branch 7 taken 85 times.
199 if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
648 {
649 114 pv.decoded_path_.push_back('/');
650 114 pv.addedSlash_ = true;
651 }
652 else
653 {
654 85 pv.addedSlash_ = false;
655 }
656 199 p.base_path = { pv.decoded_path_.data(), 0 };
657
4/4
✓ Branch 0 taken 114 times.
✓ Branch 1 taken 85 times.
✓ Branch 3 taken 113 times.
✓ Branch 4 taken 1 time.
199 auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
658 199 p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
659
660 199 return impl_->dispatch_loop(p, verb == http::method::options);
661 }
662
663 route_task
664 6 router_base::
665 dispatch(
666 std::string_view verb,
667 urls::url_view const& url,
668 route_params& p) const
669 {
670
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
6 if(verb.empty())
671 1 throw_invalid_argument();
672
673 5 impl_->ensure_finalized();
674
675
1/1
✓ Branch 2 taken 5 times.
5 auto const method = http::string_to_method(verb);
676 5 bool const is_options = (method == http::method::options);
677
678 // Handle OPTIONS * before normal dispatch
679
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
5 if(is_options && url.encoded_path() == "*")
680 {
681 if(impl_->options_handler_)
682 {
683 return impl_->options_handler_->invoke(
684 p, impl_->global_allow_header_);
685 }
686 }
687
688 // Initialize params
689 5 auto& pv = *route_params_access{p};
690 5 pv.kind_ = is_plain;
691 5 pv.verb_ = method;
692
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 if(pv.verb_ == http::method::unknown)
693 4 pv.verb_str_ = verb;
694 else
695 1 pv.verb_str_.clear();
696 5 pv.ec_.clear();
697 5 pv.ep_ = nullptr;
698 5 p.params.clear();
699
1/1
✓ Branch 2 taken 5 times.
5 pv.decoded_path_ = pct_decode_path(url.encoded_path());
700
3/6
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
5 if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
701 {
702 pv.decoded_path_.push_back('/');
703 pv.addedSlash_ = true;
704 }
705 else
706 {
707 5 pv.addedSlash_ = false;
708 }
709 5 p.base_path = { pv.decoded_path_.data(), 0 };
710
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
5 auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
711 5 p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
712
713 5 return impl_->dispatch_loop(p, is_options);
714 }
715
716 } // detail
717 } // http
718 } // boost
719