LCOV - code coverage report
Current view: top level - src/server/detail - router_base.cpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 92.0 % 238 219
Test Date: 2026-02-09 01:37:05 Functions: 94.1 % 17 16

            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/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          300 :     if(methods == ~0ULL)
      39           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         9520 :     for(auto const& [m, name] : known)
      78              :     {
      79         9240 :         if(methods & (1ULL << static_cast<unsigned>(m)))
      80              :         {
      81          274 :             if(!result.empty())
      82           46 :                 result += ", ";
      83          274 :             result += name;
      84              :         }
      85              :     }
      86          288 :     for(auto const& v : custom)
      87              :     {
      88            8 :         if(!result.empty())
      89            2 :             result += ", ";
      90            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          598 :     if(child & 2)
     105            4 :         result = (result & ~6) | 2;
     106          594 :     else if(child & 4)
     107            4 :         result = (result & ~6) | 4;
     108              : 
     109              :     // strict: bits 3-4 (8=true, 16=false)
     110          598 :     if(child & 8)
     111            1 :         result = (result & ~24) | 8;
     112          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          127 :     auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0);
     127          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          111 :     if(!m.end_)
     142            0 :         return;
     143              : 
     144              :     // Per-matcher collection
     145          111 :     if(e.all)
     146            9 :         m.allowed_methods_ = ~0ULL;
     147          102 :     else if(e.verb != http::method::unknown)
     148           99 :         m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
     149            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          111 :         m.allowed_methods_, m.custom_verbs_);
     155              : 
     156              :     // Global collection (for OPTIONS *)
     157          111 :     if(e.all)
     158            9 :         global_methods_ = ~0ULL;
     159          102 :     else if(e.verb != http::method::unknown)
     160           99 :         global_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
     161            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          330 :     global_custom_verbs_.erase(
     171          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          165 :         global_methods_, global_custom_verbs_);
     175          165 : }
     176              : 
     177              : void
     178          481 : router_base::impl::
     179              : finalize_pending()
     180              : {
     181          481 :     if(pending_route_ == SIZE_MAX)
     182          378 :         return;
     183          103 :     auto& m = matchers[pending_route_];
     184          103 :     if(entries.size() == m.first_entry_)
     185              :     {
     186              :         // empty route, remove it
     187            0 :         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          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          107 :     if(pattern.empty())
     411           76 :         pattern = "/";
     412              : 
     413          107 :     auto const matcher_idx = impl_->matchers.size();
     414          107 :     impl_->matchers.emplace_back(pattern, false);
     415          107 :     auto& m = impl_->matchers.back();
     416          107 :     if(m.error())
     417            0 :         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          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           55 :     if(!sub.impl_)
     441            0 :         return;
     442              : 
     443           55 :     sub.impl_->finalize_pending();
     444              : 
     445           55 :     if(pattern.empty())
     446            0 :         pattern = "/";
     447              : 
     448              :     // Create parent matcher for the mount point
     449           55 :     auto const parent_matcher_idx = impl_->matchers.size();
     450           55 :     impl_->matchers.emplace_back(pattern, false);
     451           55 :     auto& parent_m = impl_->matchers.back();
     452           55 :     if(parent_m.error())
     453            0 :         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          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           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          332 :     for(auto& sm : sub.impl_->matchers)
     482              :     {
     483          278 :         auto d = sm.depth_;
     484          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          278 :         impl_->matchers.push_back(std::move(sm));
     491              :     }
     492              : 
     493              :     // Inline sub's entries
     494          114 :     for(auto& se : sub.impl_->entries)
     495              :     {
     496           60 :         se.matcher_idx += matcher_offset;
     497           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           54 :     for(auto& v : sub.impl_->global_custom_verbs_)
     507            0 :         impl_->global_custom_verbs_.push_back(std::move(v));
     508           54 :     impl_->rebuild_global_allow_header();
     509              : 
     510              :     // Move options handler if sub has one and parent doesn't
     511           54 :     if(sub.impl_->options_handler_ && !impl_->options_handler_)
     512            0 :         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          116 :     if(pattern.empty())
     525            0 :         throw_invalid_argument();
     526              : 
     527          116 :     auto const idx = impl_->matchers.size();
     528          116 :     impl_->matchers.emplace_back(pattern, true);
     529          116 :     auto& m = impl_->matchers.back();
     530          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           99 :     if(verb == http::method::unknown)
     549            0 :         throw_invalid_argument();
     550              : 
     551           99 :     auto& m = impl_->matchers[idx];
     552          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           12 :     if(verb.empty())
     571              :     {
     572              :         // all methods
     573           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            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            0 : router_base::
     595              : finalize_pending()
     596              : {
     597            0 :     if(impl_)
     598            0 :         impl_->finalize_pending();
     599            0 : }
     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          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          247 :     if(verb == http::method::options &&
     629          247 :        url.encoded_path() == "*")
     630              :     {
     631            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          199 :     pv.decoded_path_ = pct_decode_path(url.encoded_path());
     647          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          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            6 :     if(verb.empty())
     671            1 :         throw_invalid_argument();
     672              : 
     673            5 :     impl_->ensure_finalized();
     674              : 
     675            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            5 :     if(is_options && url.encoded_path() == "*")
     680              :     {
     681            0 :         if(impl_->options_handler_)
     682              :         {
     683            0 :             return impl_->options_handler_->invoke(
     684            0 :                 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            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            5 :     pv.decoded_path_ = pct_decode_path(url.encoded_path());
     700            5 :     if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
     701              :     {
     702            0 :         pv.decoded_path_.push_back('/');
     703            0 :         pv.addedSlash_ = true;
     704              :     }
     705              :     else
     706              :     {
     707            5 :         pv.addedSlash_ = false;
     708              :     }
     709            5 :     p.base_path = { pv.decoded_path_.data(), 0 };
     710            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
        

Generated by: LCOV version 2.3