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 |