LCOV - code coverage report
Current view: top level - libs/url/example/router/detail/impl - router.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 366 366 100.0 %
Date: 2024-03-15 21:27:27 Functions: 35 36 97.2 %

          Line data    Source code
       1             : //
       2             : // Copyright (c) 2023 Alan de Freitas (alandefreitas@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/boostorg/url
       8             : //
       9             : 
      10             : #ifndef BOOST_URL_DETAIL_ROUTER_IPP
      11             : #define BOOST_URL_DETAIL_ROUTER_IPP
      12             : 
      13             : #include "../router.hpp"
      14             : #include <boost/url/decode_view.hpp>
      15             : #include <boost/url/grammar/alnum_chars.hpp>
      16             : #include <boost/url/grammar/alpha_chars.hpp>
      17             : #include <boost/url/grammar/lut_chars.hpp>
      18             : #include <boost/url/grammar/token_rule.hpp>
      19             : #include <boost/url/grammar/variant_rule.hpp>
      20             : #include <boost/url/rfc/detail/path_rules.hpp>
      21             : #include <boost/url/detail/replacement_field_rule.hpp>
      22             : #include <vector>
      23             : 
      24             : namespace boost {
      25             : namespace urls {
      26             : namespace detail {
      27             : 
      28             : // A path segment template
      29             : class segment_template
      30             : {
      31             :     enum class modifier : unsigned char
      32             :     {
      33             :         none,
      34             :         // {id?}
      35             :         optional,
      36             :         // {id*}
      37             :         star,
      38             :         // {id+}
      39             :         plus
      40             :     };
      41             : 
      42             :     std::string str_;
      43             :     bool is_literal_ = true;
      44             :     modifier modifier_ = modifier::none;
      45             : 
      46             :     friend struct segment_template_rule_t;
      47             : public:
      48        1271 :     segment_template() = default;
      49             : 
      50             :     bool
      51             :     match(pct_string_view seg) const;
      52             : 
      53             :     core::string_view
      54         326 :     string() const
      55             :     {
      56         326 :         return str_;
      57             :     }
      58             : 
      59             :     core::string_view
      60             :     id() const;
      61             : 
      62             :     bool
      63             :     empty() const
      64             :     {
      65             :         return str_.empty();
      66             :     }
      67             : 
      68             :     bool
      69         687 :     is_literal() const
      70             :     {
      71         687 :         return is_literal_;
      72             :     }
      73             : 
      74             :     bool
      75         163 :     has_modifier() const
      76             :     {
      77         326 :         return !is_literal() &&
      78         326 :                modifier_ != modifier::none;
      79             :     }
      80             : 
      81             :     bool
      82         101 :     is_optional() const
      83             :     {
      84         101 :         return modifier_ == modifier::optional;
      85             :     }
      86             : 
      87             :     bool
      88          51 :     is_star() const
      89             :     {
      90          51 :         return modifier_ == modifier::star;
      91             :     }
      92             : 
      93             :     bool
      94          41 :     is_plus() const
      95             :     {
      96          41 :         return modifier_ == modifier::plus;
      97             :     }
      98             : 
      99             :     friend
     100          83 :     bool operator==(
     101             :         segment_template const& a,
     102             :         segment_template const& b)
     103             :     {
     104          83 :         if (a.is_literal_ != b.is_literal_)
     105           8 :             return false;
     106          75 :         if (a.is_literal_)
     107          73 :             return a.str_ == b.str_;
     108           2 :         return a.modifier_ == b.modifier_;
     109             :     }
     110             : 
     111             :     // segments have precedence:
     112             :     //     - literal
     113             :     //     - unique
     114             :     //     - optional
     115             :     //     - plus
     116             :     //     - star
     117             :     friend
     118          18 :     bool operator<(
     119             :         segment_template const& a,
     120             :         segment_template const& b)
     121             :     {
     122          18 :         if (b.is_literal())
     123          15 :             return false;
     124           3 :         if (a.is_literal())
     125           1 :             return !b.is_literal();
     126           2 :         return a.modifier_ < b.modifier_;
     127             :     }
     128             : };
     129             : 
     130             : // A segment template is either a literal string
     131             : // or a replacement field (as in a format_string).
     132             : // Fields cannot contain format specs and might
     133             : // have one of the following modifiers:
     134             : // - ?: optional segment
     135             : // - *: zero or more segments
     136             : // - +: one or more segments
     137             : struct segment_template_rule_t
     138             : {
     139             :     using value_type = segment_template;
     140             : 
     141             :     system::result<value_type>
     142             :     parse(
     143             :         char const*& it,
     144             :         char const* end
     145             :     ) const noexcept;
     146             : };
     147             : 
     148             : constexpr auto segment_template_rule = segment_template_rule_t{};
     149             : 
     150             : constexpr auto path_template_rule =
     151             :     grammar::tuple_rule(
     152             :         grammar::squelch(
     153             :             grammar::optional_rule(
     154             :                 grammar::delim_rule('/'))),
     155             :         grammar::range_rule(
     156             :             segment_template_rule,
     157             :             grammar::tuple_rule(
     158             :                 grammar::squelch(grammar::delim_rule('/')),
     159             :                 segment_template_rule)));
     160             : 
     161             : bool
     162         311 : segment_template::
     163             : match(pct_string_view seg) const
     164             : {
     165         311 :     if (is_literal_)
     166         151 :         return *seg == str_;
     167             : 
     168             :     // other nodes match any string
     169         160 :     return true;
     170             : }
     171             : 
     172             : core::string_view
     173         168 : segment_template::
     174             : id() const
     175             : {
     176             :     // if (is_literal_) return {};
     177         168 :     BOOST_ASSERT(!is_literal());
     178         168 :     core::string_view r = {str_};
     179         168 :     r.remove_prefix(1);
     180         168 :     r.remove_suffix(1);
     181         303 :     if (r.ends_with('?') ||
     182         303 :         r.ends_with('+') ||
     183         118 :         r.ends_with('*'))
     184          72 :         r.remove_suffix(1);
     185         168 :     return r;
     186             : }
     187             : 
     188             : auto
     189         654 : segment_template_rule_t::
     190             : parse(
     191             :     char const*& it,
     192             :     char const* end) const noexcept
     193             :     -> system::result<value_type>
     194             : {
     195        1308 :     segment_template t;
     196         654 :     if (it != end &&
     197         646 :         *it == '{')
     198             :     {
     199             :         // replacement field
     200         323 :         auto it0 = it;
     201         323 :         ++it;
     202             :         auto send =
     203         323 :             grammar::find_if(
     204         323 :                 it, end, grammar::lut_chars('}'));
     205         323 :         if (send != end)
     206             :         {
     207         322 :             core::string_view s(it, send);
     208             :             static constexpr auto modifiers_cs =
     209             :                 grammar::lut_chars("?*+");
     210             :             static constexpr auto id_rule =
     211             :                 grammar::tuple_rule(
     212             :                     grammar::optional_rule(
     213             :                         arg_id_rule),
     214             :                     grammar::optional_rule(
     215             :                         grammar::delim_rule(modifiers_cs)));
     216         636 :             if (s.empty() ||
     217         636 :                 grammar::parse(s, id_rule))
     218             :             {
     219         322 :                 it = send + 1;
     220             : 
     221         322 :                 t.str_ = core::string_view(it0, send + 1);
     222         322 :                 t.is_literal_ = false;
     223         322 :                 if (s.ends_with('?'))
     224          48 :                     t.modifier_ =
     225             :                         segment_template::modifier::optional;
     226         274 :                 else if (s.ends_with('*'))
     227          48 :                     t.modifier_ =
     228             :                         segment_template::modifier::star;
     229         226 :                 else if (s.ends_with('+'))
     230          58 :                     t.modifier_ =
     231             :                         segment_template::modifier::plus;
     232         322 :                 return t;
     233             :             }
     234             :         }
     235           1 :         it = it0;
     236             :     }
     237             :     // literal segment
     238             :     auto rv = grammar::parse(
     239         332 :         it, end, urls::detail::segment_rule);
     240         332 :     BOOST_ASSERT(rv);
     241         332 :     rv->decode({}, urls::string_token::assign_to(t.str_));
     242         332 :     t.is_literal_ = true;
     243         332 :     return t;
     244             : }
     245             : 
     246             : // a small vector for child nodes...
     247             : // we shouldn't expect many children per node, and
     248             : // we don't want to allocate for that. But we also
     249             : // cannot cap the max number of child nodes because
     250             : // especially the root nodes can potentially an
     251             : // exponentially higher number of child nodes.
     252             : class child_idx_vector
     253             : {
     254             :     static constexpr std::size_t N = 5;
     255             :     std::size_t static_child_idx_[N]{};
     256             :     std::size_t* child_idx{nullptr};
     257             :     std::size_t size_{0};
     258             :     std::size_t cap_{0};
     259             : 
     260             : public:
     261        1176 :     ~child_idx_vector()
     262        1176 :     {
     263        1176 :         delete[] child_idx;
     264        1176 :     }
     265             : 
     266         393 :     child_idx_vector() = default;
     267             : 
     268         390 :     child_idx_vector(child_idx_vector const& other)
     269         390 :         : size_{other.size_}
     270         390 :         , cap_{other.cap_}
     271             :     {
     272         390 :         if (other.child_idx)
     273             :         {
     274           1 :             child_idx = new std::size_t[cap_];
     275           1 :             std::memcpy(child_idx, other.child_idx, size_ * sizeof(std::size_t));
     276           1 :             return;
     277             :         }
     278         389 :         std::memcpy(static_child_idx_, other.static_child_idx_, size_ * sizeof(std::size_t));
     279             :     }
     280             : 
     281         393 :     child_idx_vector(child_idx_vector&& other)
     282         393 :         : child_idx{other.child_idx}
     283         393 :         , size_{other.size_}
     284         393 :         , cap_{other.cap_}
     285             :     {
     286         393 :         std::memcpy(static_child_idx_, other.static_child_idx_, N);
     287         393 :         other.child_idx = nullptr;
     288         393 :     }
     289             : 
     290             :     bool
     291          47 :     empty() const
     292             :     {
     293          47 :         return size_ == 0;
     294             :     }
     295             : 
     296             :     std::size_t
     297         606 :     size() const
     298             :     {
     299         606 :         return size_;
     300             :     }
     301             : 
     302             :     std::size_t*
     303        1304 :     begin()
     304             :     {
     305        1304 :         if (child_idx)
     306          33 :             return child_idx;
     307        1271 :         return static_child_idx_;
     308             :     }
     309             : 
     310             :     std::size_t*
     311         642 :     end()
     312             :     {
     313         642 :         return begin() + size_;
     314             :     }
     315             : 
     316             :     std::size_t const*
     317         678 :     begin() const
     318             :     {
     319         678 :         if (child_idx)
     320           4 :             return child_idx;
     321         674 :         return static_child_idx_;
     322             :     }
     323             : 
     324             :     std::size_t const*
     325         339 :     end() const
     326             :     {
     327         339 :         return begin() + size_;
     328             :     }
     329             : 
     330             :     void
     331           5 :     erase(std::size_t* it)
     332             :     {
     333           5 :         BOOST_ASSERT(it - begin() >= 0);
     334           5 :         std::memmove(it - 1, it, end() - it);
     335           5 :         --size_;
     336           5 :     }
     337             : 
     338             :     void
     339         298 :     push_back(std::size_t v)
     340             :     {
     341         298 :         if (size_ == N && !child_idx)
     342             :         {
     343           1 :             child_idx = new std::size_t[N*2];
     344           1 :             cap_ = N*2;
     345           1 :             std::memcpy(child_idx, static_child_idx_, N * sizeof(std::size_t));
     346             :         }
     347         297 :         else if (child_idx && size_ == cap_)
     348             :         {
     349           1 :             auto* tmp = new std::size_t[cap_*2];
     350           1 :             std::memcpy(tmp, child_idx, cap_ * sizeof(std::size_t));
     351           1 :             delete[] child_idx;
     352           1 :             child_idx = tmp;
     353           1 :             cap_ = cap_*2;
     354             :         }
     355         298 :         begin()[size_++] = v;
     356         298 :     }
     357             : };
     358             : 
     359             : // A node in the resource tree
     360             : // Each segment in the resource tree might be
     361             : // associated with
     362             : struct node
     363             : {
     364             :     static constexpr std::size_t npos{std::size_t(-1)};
     365             : 
     366             :     // literal segment or replacement field
     367             :     detail::segment_template seg{};
     368             : 
     369             :     // A pointer to the resource
     370             :     router_base::any_resource const* resource{nullptr};
     371             : 
     372             :     // The complete match for the resource
     373             :     std::string path_template;
     374             : 
     375             :     // Index of the parent node in the
     376             :     // implementation pool of nodes
     377             :     std::size_t parent_idx{npos};
     378             : 
     379             :     // Index of child nodes in the pool
     380             :     detail::child_idx_vector child_idx;
     381             : };
     382             : 
     383             : class impl
     384             : {
     385             :     // Pool of nodes in the resource tree
     386             :     std::vector<node> nodes_;
     387             : 
     388             : public:
     389          95 :     impl()
     390          95 :     {
     391             :         // root node with no resource
     392          95 :         nodes_.push_back(node{});
     393          95 :     }
     394             : 
     395          95 :     ~impl()
     396          95 :     {
     397         483 :         for (auto &r: nodes_)
     398         388 :             delete r.resource;
     399          95 :     }
     400             : 
     401             :     // include a node for a resource
     402             :     void
     403             :     insert_impl(
     404             :         core::string_view path,
     405             :         router_base::any_resource const* v);
     406             : 
     407             :     // match a node and return the element
     408             :     router_base::any_resource const*
     409             :     find_impl(
     410             :         segments_encoded_view path,
     411             :         core::string_view*& matches,
     412             :         core::string_view*& ids) const;
     413             : 
     414             : private:
     415             :     // try to match from this root node
     416             :     node const*
     417             :     try_match(
     418             :         segments_encoded_view::const_iterator it,
     419             :         segments_encoded_view::const_iterator end,
     420             :         node const* root,
     421             :         int level,
     422             :         core::string_view*& matches,
     423             :         core::string_view*& ids) const;
     424             : 
     425             :     // check if a node has a resource when we
     426             :     // also consider optional paths through
     427             :     // the child nodes.
     428             :     static
     429             :     node const*
     430             :     find_optional_resource(
     431             :         const node* root,
     432             :         std::vector<node> const& ns,
     433             :         core::string_view*& matches,
     434             :         core::string_view*& ids);
     435             : };
     436             : 
     437             : node const*
     438          55 : impl::
     439             : find_optional_resource(
     440             :     const node* root,
     441             :     std::vector<node> const& ns,
     442             :     core::string_view*& matches,
     443             :     core::string_view*& ids)
     444             : {
     445          55 :     BOOST_ASSERT(root);
     446          55 :     if (root->resource)
     447          13 :         return root;
     448          42 :     BOOST_ASSERT(!root->child_idx.empty());
     449          69 :     for (auto i: root->child_idx)
     450             :     {
     451          42 :         auto& c = ns[i];
     452          76 :         if (!c.seg.is_optional() &&
     453          34 :             !c.seg.is_star())
     454          26 :             continue;
     455             :         // Child nodes are also
     456             :         // potentially optional.
     457          16 :         auto matches0 = matches;
     458          16 :         auto ids0 = ids;
     459          16 :         *matches++ = {};
     460          16 :         *ids++ = c.seg.id();
     461          16 :         auto n = find_optional_resource(
     462             :             &c, ns, matches, ids);
     463          16 :         if (n)
     464          15 :             return n;
     465           1 :         matches = matches0;
     466           1 :         ids = ids0;
     467             :     }
     468          27 :     return nullptr;
     469             : }
     470             : 
     471             : void
     472         113 : impl::
     473             : insert_impl(
     474             :     core::string_view path,
     475             :     router_base::any_resource const* v)
     476             : {
     477             :     // Parse dynamic route segments
     478         113 :     if (path.starts_with("/"))
     479           2 :         path.remove_prefix(1);
     480             :     auto segsr =
     481         226 :         grammar::parse(path, detail::path_template_rule);
     482         113 :     if (!segsr)
     483             :     {
     484           1 :         delete v;
     485           1 :         segsr.value();
     486             :     }
     487         224 :     auto segs = *segsr;
     488         224 :     auto it = segs.begin();
     489         113 :     auto end = segs.end();
     490             : 
     491             :     // Iterate existing nodes
     492         112 :     node* cur = &nodes_.front();
     493         112 :     int level = 0;
     494         438 :     while (it != end)
     495             :     {
     496         326 :         core::string_view seg = (*it).string();
     497         326 :         if (seg == ".")
     498             :         {
     499           2 :             ++it;
     500          10 :             continue;
     501             :         }
     502         324 :         if (seg == "..")
     503             :         {
     504             :             // discount unmatched leaf or
     505             :             // keep track of levels behind root
     506           7 :             if (cur == &nodes_.front())
     507             :             {
     508           2 :                 --level;
     509           2 :                 ++it;
     510           2 :                 continue;
     511             :             }
     512             :             // move to parent deleting current
     513             :             // if it carries no resource
     514           5 :             std::size_t p_idx = cur->parent_idx;
     515           5 :             if (cur == &nodes_.back() &&
     516          10 :                 !cur->resource &&
     517           5 :                 cur->child_idx.empty())
     518             :             {
     519           5 :                 node* p = &nodes_[p_idx];
     520           5 :                 std::size_t cur_idx = cur - nodes_.data();
     521           5 :                 p->child_idx.erase(
     522             :                     std::remove(
     523             :                         p->child_idx.begin(),
     524             :                         p->child_idx.end(),
     525             :                         cur_idx));
     526           5 :                 nodes_.pop_back();
     527             :             }
     528           5 :             cur = &nodes_[p_idx];
     529           5 :             ++it;
     530           5 :             continue;
     531             :         }
     532             :         // discount unmatched root parent
     533         317 :         if (level < 0)
     534             :         {
     535           1 :             ++level;
     536           1 :             ++it;
     537           1 :             continue;
     538             :         }
     539             :         // look for child
     540         316 :         auto cit = std::find_if(
     541             :             cur->child_idx.begin(),
     542             :             cur->child_idx.end(),
     543         166 :             [this, &it](std::size_t ci) -> bool
     544             :             {
     545          83 :                 return nodes_[ci].seg == *it;
     546             :             });
     547         316 :         if (cit != cur->child_idx.end())
     548             :         {
     549             :             // move to existing child
     550          18 :             cur = &nodes_[*cit];
     551             :         }
     552             :         else
     553             :         {
     554             :             // create child if it doesn't exist
     555         596 :             node child;
     556         298 :             child.seg = *it;
     557         298 :             std::size_t cur_id = cur - nodes_.data();
     558         298 :             child.parent_idx = cur_id;
     559         298 :             nodes_.push_back(std::move(child));
     560         298 :             nodes_[cur_id].child_idx.push_back(nodes_.size() - 1);
     561         298 :             if (nodes_[cur_id].child_idx.size() > 1)
     562             :             {
     563             :                 // keep nodes sorted
     564          18 :                 auto& cs = nodes_[cur_id].child_idx;
     565          18 :                 std::size_t n = cs.size() - 1;
     566          19 :                 while (n)
     567             :                 {
     568          18 :                     if (nodes_[cs.begin()[n]].seg < nodes_[cs.begin()[n - 1]].seg)
     569           1 :                         std::swap(cs.begin()[n], cs.begin()[n - 1]);
     570             :                     else
     571          17 :                         break;
     572           1 :                     --n;
     573             :                 }
     574             :             }
     575         298 :             cur = &nodes_.back();
     576             :         }
     577         316 :         ++it;
     578             :     }
     579         112 :     if (level != 0)
     580             :     {
     581           1 :         delete v;
     582           1 :         urls::detail::throw_invalid_argument();
     583             :     }
     584         111 :     cur->resource = v;
     585         111 :     cur->path_template = path;
     586         111 : }
     587             : 
     588             : node const*
     589         455 : impl::
     590             : try_match(
     591             :     segments_encoded_view::const_iterator it,
     592             :     segments_encoded_view::const_iterator end,
     593             :     node const* cur,
     594             :     int level,
     595             :     core::string_view*& matches,
     596             :     core::string_view*& ids) const
     597             : {
     598         455 :     while (it != end)
     599             :     {
     600         340 :         pct_string_view s = *it;
     601         340 :         if (*s == ".")
     602             :         {
     603             :             // ignore segment
     604           5 :             ++it;
     605          50 :             continue;
     606             :         }
     607         335 :         if (*s == "..")
     608             :         {
     609             : 
     610             :             // move back to the parent node
     611          44 :             ++it;
     612          76 :             if (level <= 0 &&
     613          32 :                 cur != &nodes_.front())
     614             :             {
     615          31 :                 if (!cur->seg.is_literal())
     616             :                 {
     617          22 :                     --matches;
     618          22 :                     --ids;
     619             :                 }
     620          31 :                 cur = &nodes_[cur->parent_idx];
     621             :             }
     622             :             else
     623             :                 // there's no parent, so we
     624             :                 // discount that from the implicit
     625             :                 // tree beyond terminals
     626          13 :                 --level;
     627          44 :             continue;
     628             :         }
     629             : 
     630             :         // we are in the implicit tree above the
     631             :         // root, so discount that as a level
     632         291 :         if (level < 0)
     633             :         {
     634           1 :             ++level;
     635           1 :             ++it;
     636           1 :             continue;
     637             :         }
     638             : 
     639             :         // calculate the lower bound on the
     640             :         // possible number of branches to
     641             :         // determine if we need to branch.
     642             :         // We branch when we might have more than
     643             :         // one child matching node at this level.
     644             :         // If so, we need to potentially branch
     645             :         // to find which path leads to a valid
     646             :         // resource. Otherwise, we can just
     647             :         // consume the node and input without
     648             :         // any recursive function calls.
     649         290 :         bool branch = false;
     650         290 :         if (cur->child_idx.size() > 1)
     651             :         {
     652           7 :             int branches_lb = 0;
     653          27 :             for (auto i: cur->child_idx)
     654             :             {
     655          25 :                 auto& c = nodes_[i];
     656          33 :                 if (c.seg.is_literal() ||
     657           8 :                     !c.seg.has_modifier())
     658             :                 {
     659             :                     // a literal path counts only
     660             :                     // if it matches
     661          22 :                     branches_lb += c.seg.match(s);
     662             :                 }
     663             :                 else
     664             :                 {
     665             :                     // everything not matching
     666             :                     // a single path counts as
     667             :                     // more than one path already
     668           3 :                     branches_lb = 2;
     669             :                 }
     670          25 :                 if (branches_lb > 1)
     671             :                 {
     672             :                     // already know we need to
     673             :                     // branch
     674           5 :                     branch = true;
     675           5 :                     break;
     676             :                 }
     677             :             }
     678             :         }
     679             : 
     680             :         // attempt to match each child node
     681         290 :         node const* r = nullptr;
     682         290 :         bool match_any = false;
     683         308 :         for (auto i: cur->child_idx)
     684             :         {
     685         289 :             auto& c = nodes_[i];
     686         289 :             if (c.seg.match(s))
     687             :             {
     688         278 :                 if (c.seg.is_literal())
     689             :                 {
     690             :                     // just continue from the
     691             :                     // next segment
     692         123 :                     if (branch)
     693             :                     {
     694           2 :                         r = try_match(
     695             :                             std::next(it), end,
     696             :                             &c, level,
     697             :                             matches, ids);
     698           2 :                         if (r)
     699           2 :                             break;
     700             :                     }
     701             :                     else
     702             :                     {
     703         121 :                         cur = &c;
     704         121 :                         match_any = true;
     705         121 :                         break;
     706             :                     }
     707             :                 }
     708         155 :                 else if (!c.seg.has_modifier())
     709             :                 {
     710             :                     // just continue from the
     711             :                     // next segment
     712          96 :                     if (branch)
     713             :                     {
     714           2 :                         auto matches0 = matches;
     715           2 :                         auto ids0 = ids;
     716           2 :                         *matches++ = *it;
     717           2 :                         *ids++ = c.seg.id();
     718           2 :                         r = try_match(
     719             :                             std::next(it), end, &c,
     720             :                             level, matches, ids);
     721           2 :                         if (r)
     722             :                         {
     723           1 :                             break;
     724             :                         }
     725             :                         else
     726             :                         {
     727             :                             // rewind
     728           1 :                             matches = matches0;
     729           1 :                             ids = ids0;
     730             :                         }
     731             :                     }
     732             :                     else
     733             :                     {
     734             :                         // only path possible
     735          94 :                         *matches++ = *it;
     736          94 :                         *ids++ = c.seg.id();
     737          94 :                         cur = &c;
     738          94 :                         match_any = true;
     739          94 :                         break;
     740             :                     }
     741             :                 }
     742          59 :                 else if (c.seg.is_optional())
     743             :                 {
     744             :                     // attempt to match by ignoring
     745             :                     // and not ignoring the segment.
     746             :                     // we first try the complete
     747             :                     // continuation consuming the
     748             :                     // input, which is the
     749             :                     // longest and most likely
     750             :                     // match
     751          18 :                     auto matches0 = matches;
     752          18 :                     auto ids0 = ids;
     753          18 :                     *matches++ = *it;
     754          18 :                     *ids++ = c.seg.id();
     755          18 :                     r = try_match(
     756             :                         std::next(it), end,
     757             :                         &c, level, matches, ids);
     758          18 :                     if (r)
     759          11 :                         break;
     760             :                     // rewind
     761           7 :                     matches = matches0;
     762           7 :                     ids = ids0;
     763             :                     // try complete continuation
     764             :                     // consuming no segment
     765           7 :                     *matches++ = {};
     766           7 :                     *ids++ = c.seg.id();
     767           7 :                     r = try_match(
     768             :                         it, end, &c,
     769             :                         level, matches, ids);
     770           7 :                     if (r)
     771           5 :                         break;
     772             :                     // rewind
     773           2 :                     matches = matches0;
     774           2 :                     ids = ids0;
     775             :                 }
     776             :                 else
     777             :                 {
     778             :                     // check if the next segments
     779             :                     // won't send us to a parent
     780             :                     // directory
     781          41 :                     auto first = it;
     782          41 :                     std::size_t ndotdot = 0;
     783          41 :                     std::size_t nnondot = 0;
     784          41 :                     auto it1 = it;
     785         114 :                     while (it1 != end)
     786             :                     {
     787          83 :                         if (*it1 == "..")
     788             :                         {
     789          17 :                             ++ndotdot;
     790          17 :                             if (ndotdot >= (nnondot + c.seg.is_star()))
     791          10 :                                 break;
     792             :                         }
     793          66 :                         else if (*it1 != ".")
     794             :                         {
     795          66 :                             ++nnondot;
     796             :                         }
     797          73 :                         ++it1;
     798             :                     }
     799          41 :                     if (it1 != end)
     800          37 :                         break;
     801             : 
     802             :                     // attempt to match many
     803             :                     // segments
     804          31 :                     auto matches0 = matches;
     805          31 :                     auto ids0 = ids;
     806          31 :                     *matches++ = *it;
     807          31 :                     *ids++ = c.seg.id();
     808             :                     // if this is a plus seg, we
     809             :                     // already consumed the first
     810             :                     // segment
     811          31 :                     if (c.seg.is_plus())
     812             :                     {
     813          17 :                         ++first;
     814             :                     }
     815             :                     // {*} is usually the last
     816             :                     // match in a path.
     817             :                     // try complete continuation
     818             :                     // match for every subrange
     819             :                     // from {last, last} to
     820             :                     // {first, last}.
     821             :                     // We also try {last, last}
     822             :                     // first because it is the
     823             :                     // longest match.
     824          31 :                     auto start = end;
     825          39 :                     while (start != first)
     826             :                     {
     827          25 :                         r = try_match(
     828             :                             start, end, &c,
     829             :                             level, matches, ids);
     830          25 :                         if (r)
     831             :                         {
     832          17 :                             core::string_view prev = *std::prev(start);
     833          34 :                             *matches0 = {
     834             :                                 matches0->data(),
     835          17 :                                 prev.data() + prev.size()};
     836          17 :                             break;
     837             :                         }
     838           8 :                         matches = matches0 + 1;
     839           8 :                         ids = ids0 + 1;
     840           8 :                         --start;
     841             :                     }
     842          31 :                     if (r)
     843             :                     {
     844          17 :                         break;
     845             :                     }
     846             :                     // start == first
     847          14 :                     matches = matches0 + 1;
     848          14 :                     ids = ids0 + 1;
     849          14 :                     r = try_match(
     850             :                         start, end, &c,
     851             :                         level, matches, ids);
     852          14 :                     if (r)
     853             :                     {
     854          10 :                         if (!c.seg.is_plus())
     855           2 :                             *matches0 = {};
     856          10 :                         break;
     857             :                     }
     858             :                 }
     859             :             }
     860             :         }
     861             :         // r represent we already found a terminal
     862             :         // node which is a match
     863         290 :         if (r)
     864          46 :             return r;
     865             :         // if we couldn't match anything, we go
     866             :         // one level up in the implicit tree
     867             :         // because the path might still have a
     868             :         // "..".
     869         244 :         if (!match_any)
     870          29 :             ++level;
     871         244 :         ++it;
     872             :     }
     873         115 :     if (level != 0)
     874             :     {
     875             :         // the path ended below or above an
     876             :         // existing node
     877          13 :         return nullptr;
     878             :     }
     879         102 :     if (!cur->resource)
     880             :     {
     881             :         // we consumed all the input and reached
     882             :         // a node with no resource, but it might
     883             :         // still have child optional segments
     884             :         // with resources we can reach without
     885             :         // consuming any input
     886          78 :         return find_optional_resource(
     887          39 :             cur, nodes_, matches, ids);
     888             :     }
     889          63 :     return cur;
     890             : }
     891             : 
     892             : router_base::any_resource const*
     893          93 : impl::
     894             : find_impl(
     895             :     segments_encoded_view path,
     896             :     core::string_view*& matches,
     897             :     core::string_view*& ids) const
     898             : {
     899             :     // parse_path is inconsistent for empty paths
     900          93 :     if (path.empty())
     901           4 :         path = segments_encoded_view("./");
     902             : 
     903             :     // Iterate nodes from the root
     904          93 :     node const*p = try_match(
     905             :         path.begin(), path.end(),
     906          93 :         &nodes_.front(), 0,
     907             :         matches, ids);
     908          93 :     if (p)
     909          76 :         return p->resource;
     910          17 :     return nullptr;
     911             : }
     912             : 
     913          95 : router_base::
     914          95 : router_base()
     915          95 :     : impl_(new impl{}) {}
     916             : 
     917          95 : router_base::
     918         190 : ~router_base()
     919             : {
     920          95 :     delete reinterpret_cast<impl*>(impl_);
     921          95 : }
     922             : 
     923             : void
     924         113 : router_base::
     925             : insert_impl(
     926             :     core::string_view s,
     927             :     any_resource const* v)
     928             : {
     929         113 :     reinterpret_cast<impl*>(impl_)
     930         113 :         ->insert_impl(s, v);
     931         111 : }
     932             : 
     933             : auto
     934          93 : router_base::
     935             : find_impl(
     936             :     segments_encoded_view path,
     937             :     core::string_view*& matches,
     938             :     core::string_view*& ids) const noexcept
     939             :     -> any_resource const*
     940             : {
     941          93 :     return reinterpret_cast<impl*>(impl_)
     942          93 :         ->find_impl(path, matches, ids);
     943             : }
     944             : 
     945             : } // detail
     946             : } // urls
     947             : } // boost
     948             : 
     949             : #endif

Generated by: LCOV version 1.15