LCOV - code coverage report
Current view: top level - src/server - any_router.cpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 91.6 % 238 218 20
Test Date: 2026-06-13 19:44:58 Functions: 94.1 % 17 16 1

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

Generated by: LCOV version 2.3