LCOV - code coverage report
Current view: top level - libs/url/example/router/detail/impl/router.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 367 367
Test Date: 2024-08-19 20:08:54 Functions: 97.2 % 36 35

            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          654 :     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          332 :     auto rv = grammar::parse(
     239              :         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          654 : }
     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              :     {
     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              :     {
     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          113 :         grammar::parse(path, detail::path_template_rule);
     482          113 :     if (!segsr)
     483              :     {
     484            1 :         delete v;
     485            1 :         segsr.value();
     486              :     }
     487          112 :     auto segs = *segsr;
     488          112 :     auto it = segs.begin();
     489          112 :     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            5 :         }
     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          298 :             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          298 :         }
     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          116 : }
     587              : 
     588              : node const*
     589          161 : 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           95 : ~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 2.1