LCOV - code coverage report
Current view: top level - libs/beast2/src/server/detail/any_router.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 52.4 % 147 77
Test Date: 2025-11-13 15:50:43 Functions: 55.6 % 18 10

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/beast2
       8              : //
       9              : 
      10              : #include "src/server/route_rule.hpp"
      11              : #include <boost/beast2/server/basic_router.hpp>
      12              : #include <boost/beast2/server/route_handler.hpp>
      13              : #include <boost/beast2/server/detail/any_router.hpp>
      14              : #include <boost/beast2/error.hpp>
      15              : #include <boost/beast2/detail/except.hpp>
      16              : #include <boost/url/grammar/hexdig_chars.hpp>
      17              : #include <boost/assert.hpp>
      18              : #include <atomic>
      19              : #include <string>
      20              : #include <vector>
      21              : 
      22              : namespace boost {
      23              : namespace beast2 {
      24              : namespace detail {
      25              : 
      26            9 : any_router::any_handler::~any_handler() = default;
      27              : 
      28              : //------------------------------------------------
      29              : 
      30              : namespace {
      31              : 
      32              : // decode all percent escapes
      33              : std::string
      34            9 : pct_decode(
      35              :     urls::pct_string_view s)
      36              : {
      37           18 :     return core::string_view(s); // for now
      38              : }
      39              : 
      40              : // decode all percent escapes except slashes '/' and '\'
      41              : std::string
      42            7 : pct_decode_path(
      43              :     urls::pct_string_view s)
      44              : {
      45            7 :     std::string result;
      46            7 :     core::string_view sv(s);
      47            7 :     result.reserve(s.size());
      48            7 :     auto it = sv.data();
      49            7 :     auto const end = it + sv.size();
      50              :     for(;;)
      51              :     {
      52           23 :         if(it == end)
      53           14 :             return result;
      54           16 :         if(*it != '%')
      55              :         {
      56           16 :             result.push_back(*it++);
      57           16 :             continue;
      58              :         }
      59            0 :         if(++it == end)
      60            0 :             break;
      61            0 :         auto d0 = urls::grammar::hexdig_value(*it++);
      62            0 :         if( d0 < 0 ||
      63              :             it == end)
      64              :             break;
      65            0 :         auto d1 = urls::grammar::hexdig_value(*it++);
      66            0 :         if(d1 < 0)
      67            0 :             break;
      68            0 :         char c = d0 * 4 + d1;
      69            0 :         if( c != '/' &&
      70              :             c != '\\')
      71              :         {
      72            0 :             result.push_back(c);
      73            0 :             continue;
      74              :         }
      75            0 :         result.append(it - 3, 3);
      76           16 :     }
      77            0 :     detail::throw_invalid_argument(
      78              :         "bad percent encoding");
      79            0 : }
      80              : 
      81              : struct match_result
      82              : {
      83              :     std::size_t n = 0; // chars moved from path to base_path
      84              : };
      85              : 
      86              : } // (anon)
      87              : 
      88              : //------------------------------------------------
      89              : 
      90              : struct any_router::layer
      91              : {
      92              :     std::string pat_;            // prefix to match
      93              :     handler_ptr handler_;
      94              :     bool end_;                   // if an exact match is required
      95              :     //bool ignore_case = false;
      96              :     //bool strict = false;        // trailing slashes are significant
      97              :     // route r;
      98              :     path_rule_t::value_type pv_;
      99              : 
     100            9 :     layer(
     101              :         core::string_view pat,
     102              :         handler_ptr handler,
     103              :         bool end)
     104            9 :         : pat_(pct_decode(pat))
     105            9 :         , handler_(std::move(handler))
     106            9 :         , end_(end)
     107           18 :         , pv_(grammar::parse(
     108            9 :             pat, path_rule).value())
     109              :     {
     110            9 :     }
     111              : 
     112              :     /** Return true if path matches this layer
     113              : 
     114              :         Caller is responsible for selectively percent
     115              :         decoding the path. Slashes should not be decoded.
     116              :     */
     117              :     bool
     118            8 :     match(
     119              :         core::string_view path,
     120              :         match_result& mr,
     121              :         req_info const& ri) const
     122              :     {
     123            8 :         BOOST_ASSERT(! path.empty());
     124            8 :         auto it = path.data();
     125            8 :         auto const end = it + path.size();
     126            8 :         auto pit = pv_.segs.begin();
     127            8 :         auto const pend = pv_.segs.end();
     128           15 :         while(it != end && pit != pend)
     129              :         {
     130           16 :             if(! core::string_view(
     131            8 :                     it, end).starts_with(pit->prefix))
     132            1 :                 return false;
     133            7 :             it += pit->prefix.size();
     134            7 :             ++pit;
     135              :         }
     136            7 :         if( end_ && (
     137            0 :             it != end ||
     138            0 :             pit != pend))
     139            0 :             return false;
     140           14 :         if( ! end_ &&
     141            7 :             pit != pend)
     142            0 :             return false;
     143            7 :         mr.n = it - path.data();
     144           21 :         ri.base_path = {
     145            7 :             ri.base_path.data(),
     146            7 :             ri.base_path.size() + mr.n };
     147            7 :         ri.path.remove_prefix(mr.n);
     148            7 :         return true;
     149              :     }
     150              : };
     151              : 
     152              : struct any_router::impl
     153              : {
     154              :     std::atomic<std::size_t> refs{1};
     155              :     std::vector<layer> layers;
     156              :     req_info(*get_req_info)(void*);
     157              : };
     158              : 
     159              : //------------------------------------------------
     160              : 
     161            7 : any_router::
     162            7 : ~any_router()
     163              : {
     164            7 :     if(! impl_)
     165            0 :         return;
     166            7 :     if(--impl_->refs == 0)
     167            7 :         delete impl_;
     168            7 : }
     169              : 
     170            0 : any_router::
     171            0 : any_router(any_router&& other) noexcept
     172            0 :     :impl_(other.impl_)
     173              : {
     174            0 :     other.impl_ = nullptr;
     175            0 : }
     176              : 
     177            0 : any_router::
     178            0 : any_router(any_router const& other) noexcept
     179              : {
     180            0 :     impl_ = other.impl_;
     181            0 :     ++impl_->refs;
     182            0 : }
     183              : 
     184              : any_router&
     185            0 : any_router::
     186              : operator=(any_router&& other) noexcept
     187              : {
     188            0 :     auto p = impl_;
     189            0 :     impl_ = other.impl_;
     190            0 :     other.impl_ = nullptr;
     191            0 :     if(p && --p->refs == 0)
     192            0 :         delete p;
     193            0 :     return *this;
     194              : }
     195              : 
     196              : any_router&
     197            0 : any_router::
     198              : operator=(any_router const& other) noexcept
     199              : {
     200            0 :     auto p = impl_;
     201            0 :     impl_ = other.impl_;
     202            0 :     ++impl_->refs;
     203            0 :     if(p && --p->refs == 0)
     204            0 :         delete p;
     205            0 :     return *this;
     206              : }
     207              : 
     208            7 : any_router::
     209              : any_router(
     210            7 :     req_info(*get_req_info)(void*))
     211            7 :     : impl_(new impl)
     212              : {
     213            7 :     impl_->get_req_info = get_req_info;
     214            7 : }
     215              : 
     216              : std::size_t
     217            0 : any_router::
     218              : count() const noexcept
     219              : {
     220            0 :     std::size_t n = 1;
     221            0 :     for(auto const& e : impl_->layers)
     222            0 :         n += e.handler_->count();
     223            0 :     return n;
     224              : }
     225              : 
     226              : // top-level dispatch gets called first
     227              : route_result
     228            7 : any_router::
     229              : dispatch_impl(
     230              :     http_proto::method verb,
     231              :     urls::url_view const& url,
     232              :     void* req, void*res, route_state& st) const
     233              : {
     234            7 :     st = {};
     235            7 :     st.verb = verb;
     236            7 :     st.verb_str = {};
     237              :     // VFALCO use reusing-StringToken
     238              :     st.decoded_path =
     239            7 :         pct_decode_path(url.encoded_path());
     240              : 
     241            7 :     auto ri = impl_->get_req_info(req);
     242            7 :     ri.base_path = { st.decoded_path.data(), 0 };
     243            7 :     ri.path = st.decoded_path;
     244              : 
     245           14 :     return dispatch_impl(req, res, st);
     246              : }
     247              : 
     248              : // recursive dispatch
     249              : route_result
     250            7 : any_router::
     251              : dispatch_impl(
     252              :     void* req, void* res, route_state& st) const
     253              : {
     254            7 :     auto ri = impl_->get_req_info(req);
     255              :     //system::error_code ec;
     256              : 
     257            8 :     for(auto const& e : impl_->layers)
     258              :     {
     259            8 :         match_result mr;
     260            8 :         if(e.match(ri.path, mr, ri))
     261              :         {
     262            7 :             auto rv = e.handler_->invoke(req, res, st, nullptr);
     263            7 :             if( rv.failed() ||
     264            0 :                 rv == route::send ||
     265            7 :                 rv == route::done ||
     266            7 :                 rv == route::close
     267              :                 // || rv == route::detach // VFALCO this would be best
     268              :                 )
     269            7 :                 return rv;
     270            0 :             if(rv == route::detach)
     271              :             {
     272              :                 // do detach
     273            0 :                 return rv;
     274              :             }
     275              :             // must be a non-success error code
     276            0 :             if(&rv.category() != &detail::route_cat)
     277            0 :                 detail::throw_invalid_argument();
     278              : 
     279              :         }
     280              :     }
     281              : 
     282            0 :     return route::next;
     283              : 
     284              : #if 0
     285              :     route_result rv;
     286              :     system::error_code ec;
     287              :     auto ri = impl_->get_req_info(req);
     288              :     std::string path_str = std::string(ri.path->buffer());
     289              : 
     290              :     auto it = impl_->layers.begin();
     291              :     auto const end = impl_->layers.end();
     292              : 
     293              :     if(st.resume != 0)
     294              :     {
     295              :         // st.resume is how many we have to skip
     296              :         while(it != end)
     297              :         {
     298              :             auto const n = it->handler->count();
     299              :             if(st.pos + n <= st.resume)
     300              :             {
     301              :                 // skip route and children
     302              :                 st.pos += n;
     303              :                 ++it;
     304              :                 continue;
     305              :             }
     306              : 
     307              :             ++st.pos;
     308              : 
     309              :             if(st.pos == st.resume)
     310              :             {
     311              :                 st.resume = 0;
     312              :                 return st.ec;
     313              :             }
     314              :             break;
     315              :         }
     316              :     }
     317              : 
     318              :     for(;it != end;++it)
     319              :     {
     320              :         // 1-based index of last route we checked
     321              :         ++st.pos;
     322              : 
     323              :         if( it->method != http_proto::method::unknown &&
     324              :             ri.method != it->method)
     325              :             continue;
     326              :         if(! it->exact_prefix.empty())
     327              :         {
     328              :             if(it->prefix)
     329              :             {
     330              :                 // only the prefix has to match
     331              :                 if(! core::string_view(*ri.suffix_path).starts_with(
     332              :                     core::string_view(it->exact_prefix)))
     333              :                     continue;
     334              :             }
     335              :             else
     336              :             {
     337              :                 // full match
     338              :                 if( core::string_view(*ri.suffix_path) !=
     339              :                     core::string_view(it->exact_prefix))
     340              :                     continue;
     341              :             }
     342              :         }
     343              : 
     344              :         auto const base_path0 = *ri.base_path;
     345              :         auto const suffix_path0 = *ri.suffix_path;
     346              :         ri.base_path->append(
     347              :             ri.suffix_path->data(), it->exact_prefix.size());
     348              :         ri.suffix_path->erase(0, it->exact_prefix.size());
     349              :         // invoke the handler
     350              :         rv = it->handler->invoke(req, res, st, nullptr);
     351              :         *ri.base_path = base_path0;
     352              :         *ri.suffix_path = suffix_path0;
     353              : 
     354              :         if(rv == route::next)
     355              :             continue;
     356              :         if(&rv.category() != &detail::route_cat)
     357              :         {
     358              :             // must indicate failure
     359              :             if(! ec.failed())
     360              :                 detail::throw_invalid_argument();
     361              :             ec = rv;
     362              :             goto do_error;
     363              :         }
     364              :         if(rv == route::detach)
     365              :         {
     366              :             // VFALCO this statement is broken, because a foreign
     367              :             // thread could be resuming and race with st.resume
     368              :             if( st.resume == 0)
     369              :                 st.resume = st.pos;
     370              :             return rv;
     371              :         }
     372              :         BOOST_ASSERT(
     373              :             rv == route::send ||
     374              :             rv == route::close |
     375              :             rv == route::done);
     376              :         return rv;
     377              :     }
     378              :     return route::next;
     379              : 
     380              : do_error:
     381              :     while(++it != end)
     382              :     {
     383              :         // invoke error handlers
     384              :         rv = it->handler->invoke(req, res, st, &ec);
     385              :         if(&ec.category() == &detail::route_cat)
     386              :         {
     387              :             // can't change ec to a route result
     388              :             detail::throw_invalid_argument();
     389              :         }
     390              :         if(! ec.failed())
     391              :         {
     392              :             // can't change ec to success
     393              :             detail::throw_invalid_argument();
     394              :         }
     395              :         if(rv == route::next)
     396              :             continue;
     397              :         if( rv == route::send ||
     398              :             rv == route::close)
     399              :             return rv;
     400              :         if(rv == route::done)
     401              :         {
     402              :             // can't complete the response in handler
     403              :             detail::throw_invalid_argument();
     404              :         }
     405              :         if(rv == route::detach)
     406              :         {
     407              :             // can't detach in error handler
     408              :             detail::throw_invalid_argument();
     409              :         }
     410              :         // can't get here
     411              :         detail::throw_logic_error();
     412              :     }
     413              :     return ec;
     414              : #endif
     415              : }
     416              : 
     417              : auto
     418            0 : any_router::
     419              : resume(
     420              :     void* req, void* res, route_state& st,
     421              :     route_result const& ec) const ->
     422              :         route_result
     423              : {
     424            0 :     BOOST_ASSERT(st.resume > 0);
     425            0 :     if( ec == route::send ||
     426            0 :         ec == route::close ||
     427            0 :         ec == route::done)
     428            0 :         return ec;
     429            0 :     if(ec == route::detach)
     430              :     {
     431              :         // can't detach on resume
     432            0 :         detail::throw_invalid_argument();
     433              :     }
     434            0 :     if(&ec.category() != &detail::route_cat)
     435              :     {
     436              :         // must indicate failure
     437            0 :         if(! ec.failed())
     438            0 :             detail::throw_invalid_argument();
     439              :     }
     440            0 :     st.ec = ec;
     441            0 :     st.pos = 0;
     442            0 :     return dispatch_impl(req, res, st);
     443              : }
     444              : 
     445              : void
     446            9 : any_router::
     447              : append(
     448              :     bool end,
     449              :     http_proto::method method,
     450              :     core::string_view pat,
     451              :     handler_ptr h)
     452              : {
     453              :     // delete the last layer if it is empty, to handle the case where
     454              :     // the user calls route() without actually adding anything after.
     455              :     /*
     456              :     if( ! impl_->layers.empty() &&
     457              :         ! impl_->layers.back().handler)
     458              :     {
     459              :         --impl_->size;
     460              :         impl_->layers.pop_back();
     461              :     }*/
     462              :     (void)method;
     463            9 :     impl_->layers.emplace_back(
     464            9 :         pat, std::move(h), end);
     465            9 : }
     466              : 
     467              : void
     468            0 : any_router::
     469              : append(
     470              :     core::string_view,
     471              :     any_router&)
     472              : {
     473            0 : }
     474              : 
     475              : } // detail
     476              : } // beast2
     477              : } // boost
        

Generated by: LCOV version 2.1