LCOV - code coverage report
Current view: top level - libs/url/src/detail - pattern.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 356 356 100.0 %
Date: 2023-12-15 15:31:48 Functions: 10 10 100.0 %

          Line data    Source code
       1             : //
       2             : // Copyright (c) 2022 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_IMPL_PATTERN_IPP
      11             : #define BOOST_URL_DETAIL_IMPL_PATTERN_IPP
      12             : 
      13             : #include <boost/url/detail/config.hpp>
      14             : #include <boost/url/detail/pattern.hpp>
      15             : #include <boost/url/detail/pct_format.hpp>
      16             : #include <boost/url/detail/replacement_field_rule.hpp>
      17             : #include <boost/url/grammar/alpha_chars.hpp>
      18             : #include <boost/url/grammar/optional_rule.hpp>
      19             : #include <boost/url/grammar/token_rule.hpp>
      20             : #include <boost/url/rfc/detail/charsets.hpp>
      21             : #include <boost/url/rfc/detail/host_rule.hpp>
      22             : #include <boost/url/rfc/detail/path_rules.hpp>
      23             : #include <boost/url/rfc/detail/port_rule.hpp>
      24             : #include <boost/url/rfc/detail/scheme_rule.hpp>
      25             : 
      26             : namespace boost {
      27             : namespace urls {
      28             : namespace detail {
      29             : 
      30             : static constexpr auto lhost_chars = host_chars + ':';
      31             : 
      32             : void
      33         140 : pattern::
      34             : apply(
      35             :     url_base& u,
      36             :     format_args const& args) const
      37             : {
      38             :     // measure total
      39             :     struct sizes
      40             :     {
      41             :         std::size_t scheme = 0;
      42             :         std::size_t user = 0;
      43             :         std::size_t pass = 0;
      44             :         std::size_t host = 0;
      45             :         std::size_t port = 0;
      46             :         std::size_t path = 0;
      47             :         std::size_t query = 0;
      48             :         std::size_t frag = 0;
      49             :     };
      50         140 :     sizes n;
      51             : 
      52         140 :     format_parse_context pctx(nullptr, nullptr, 0);
      53         140 :     measure_context mctx(args);
      54         140 :     if (!scheme.empty())
      55             :     {
      56          54 :         pctx = {scheme, pctx.next_arg_id()};
      57          54 :         n.scheme = pct_vmeasure(
      58             :             grammar::alpha_chars, pctx, mctx);
      59          54 :         mctx.advance_to(0);
      60             :     }
      61         140 :     if (has_authority)
      62             :     {
      63          47 :         if (has_user)
      64             :         {
      65           8 :             pctx = {user, pctx.next_arg_id()};
      66           8 :             n.user = pct_vmeasure(
      67             :                 user_chars, pctx, mctx);
      68           8 :             mctx.advance_to(0);
      69           8 :             if (has_pass)
      70             :             {
      71           6 :                 pctx = {pass, pctx.next_arg_id()};
      72           6 :                 n.pass = pct_vmeasure(
      73             :                     password_chars, pctx, mctx);
      74           6 :                 mctx.advance_to(0);
      75             :             }
      76             :         }
      77          47 :         if (host.starts_with('['))
      78             :         {
      79           1 :             BOOST_ASSERT(host.ends_with(']'));
      80           1 :             pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
      81           1 :             n.host = pct_vmeasure(
      82           1 :                 lhost_chars, pctx, mctx) + 2;
      83           1 :             mctx.advance_to(0);
      84             :         }
      85             :         else
      86             :         {
      87          46 :             pctx = {host, pctx.next_arg_id()};
      88          46 :             n.host = pct_vmeasure(
      89             :                 host_chars, pctx, mctx);
      90          46 :             mctx.advance_to(0);
      91             :         }
      92          47 :         if (has_port)
      93             :         {
      94          13 :             pctx = {port, pctx.next_arg_id()};
      95          13 :             n.port = pct_vmeasure(
      96             :                 grammar::digit_chars, pctx, mctx);
      97          13 :             mctx.advance_to(0);
      98             :         }
      99             :     }
     100         140 :     if (!path.empty())
     101             :     {
     102         102 :         pctx = {path, pctx.next_arg_id()};
     103         102 :         n.path = pct_vmeasure(
     104             :             path_chars, pctx, mctx);
     105         100 :         mctx.advance_to(0);
     106             :     }
     107         138 :     if (has_query)
     108             :     {
     109          13 :         pctx = {query, pctx.next_arg_id()};
     110          13 :         n.query = pct_vmeasure(
     111             :             query_chars, pctx, mctx);
     112          13 :         mctx.advance_to(0);
     113             :     }
     114         138 :     if (has_frag)
     115             :     {
     116           7 :         pctx = {frag, pctx.next_arg_id()};
     117           7 :         n.frag = pct_vmeasure(
     118             :             fragment_chars, pctx, mctx);
     119           7 :         mctx.advance_to(0);
     120             :     }
     121         138 :     std::size_t const n_total =
     122         138 :         n.scheme +
     123         138 :         (n.scheme != 0) * 1 + // ":"
     124         138 :         has_authority * 2 +   // "//"
     125         138 :         n.user +
     126         138 :         has_pass * 1 +        // ":"
     127         138 :         n.pass +
     128         138 :         has_user * 1 +        // "@"
     129         138 :         n.host +
     130         138 :         has_port * 1 +        // ":"
     131         138 :         n.port +
     132         138 :         n.path +
     133         138 :         has_query * 1 +       // "?"
     134         138 :         n.query +
     135         138 :         has_frag * 1 +        // "#"
     136         138 :         n.frag;
     137         138 :     u.reserve(n_total);
     138             : 
     139             :     // Apply
     140         137 :     pctx = {nullptr, nullptr, 0};
     141         137 :     format_context fctx(nullptr, args);
     142         274 :     url_base::op_t op(u);
     143             :     using parts = parts_base;
     144         137 :     if (!scheme.empty())
     145             :     {
     146         106 :         auto dest = u.resize_impl(
     147             :             parts::id_scheme,
     148          53 :             n.scheme + 1, op);
     149          53 :         pctx = {scheme, pctx.next_arg_id()};
     150          53 :         fctx.advance_to(dest);
     151          53 :         const char* dest1 = pct_vformat(
     152             :             grammar::alpha_chars, pctx, fctx);
     153          53 :         dest[n.scheme] = ':';
     154             :         // validate
     155          53 :         if (!grammar::parse({dest, dest1}, scheme_rule()))
     156             :         {
     157           1 :             throw_invalid_argument();
     158             :         }
     159             :     }
     160         136 :     if (has_authority)
     161             :     {
     162          45 :         if (has_user)
     163             :         {
     164           8 :             auto dest = u.set_user_impl(
     165             :                 n.user, op);
     166           8 :             pctx = {user, pctx.next_arg_id()};
     167           8 :             fctx.advance_to(dest);
     168           8 :             char const* dest1 = pct_vformat(
     169             :                 user_chars, pctx, fctx);
     170           8 :             u.impl_.decoded_[parts::id_user] =
     171           8 :                 pct_string_view(dest, dest1 - dest)
     172           8 :                     ->decoded_size();
     173           8 :             if (has_pass)
     174             :             {
     175           6 :                 char* destp = u.set_password_impl(
     176             :                     n.pass, op);
     177           6 :                 pctx = {pass, pctx.next_arg_id()};
     178           6 :                 fctx.advance_to(destp);
     179           6 :                 dest1 = pct_vformat(
     180             :                     password_chars, pctx, fctx);
     181           6 :                 u.impl_.decoded_[parts::id_pass] =
     182           6 :                     pct_string_view({destp, dest1})
     183           6 :                         ->decoded_size() + 1;
     184             :             }
     185             :         }
     186          45 :         auto dest = u.set_host_impl(
     187             :             n.host, op);
     188          45 :         if (host.starts_with('['))
     189             :         {
     190           1 :             BOOST_ASSERT(host.ends_with(']'));
     191           1 :             pctx = {host.substr(1, host.size() - 2), pctx.next_arg_id()};
     192           1 :             *dest++ = '[';
     193           1 :             fctx.advance_to(dest);
     194             :             char* dest1 =
     195           1 :                 pct_vformat(lhost_chars, pctx, fctx);
     196           1 :             *dest1++ = ']';
     197           1 :             u.impl_.decoded_[parts::id_host] =
     198           2 :                 pct_string_view(dest - 1, dest1 - dest)
     199           1 :                     ->decoded_size();
     200             :         }
     201             :         else
     202             :         {
     203          44 :             pctx = {host, pctx.next_arg_id()};
     204          44 :             fctx.advance_to(dest);
     205             :             char const* dest1 =
     206          44 :                 pct_vformat(host_chars, pctx, fctx);
     207          44 :             u.impl_.decoded_[parts::id_host] =
     208          88 :                 pct_string_view(dest, dest1 - dest)
     209          44 :                     ->decoded_size();
     210             :         }
     211          45 :         auto uh = u.encoded_host();
     212          45 :         auto h = grammar::parse(uh, host_rule).value();
     213          45 :         std::memcpy(
     214          45 :             u.impl_.ip_addr_,
     215             :             h.addr,
     216             :             sizeof(u.impl_.ip_addr_));
     217          45 :         u.impl_.host_type_ = h.host_type;
     218          45 :         if (has_port)
     219             :         {
     220          13 :             dest = u.set_port_impl(n.port, op);
     221          13 :             pctx = {port, pctx.next_arg_id()};
     222          13 :             fctx.advance_to(dest);
     223          13 :             char const* dest1 = pct_vformat(
     224             :                 grammar::digit_chars, pctx, fctx);
     225          13 :             u.impl_.decoded_[parts::id_port] =
     226          13 :                 pct_string_view(dest, dest1 - dest)
     227          13 :                     ->decoded_size() + 1;
     228          13 :             core::string_view up = {dest - 1, dest1};
     229          13 :             auto p = grammar::parse(up, detail::port_part_rule).value();
     230          13 :             if (p.has_port)
     231          13 :                 u.impl_.port_number_ = p.port_number;
     232             :         }
     233             :     }
     234         136 :     if (!path.empty())
     235             :     {
     236         100 :         auto dest = u.resize_impl(
     237             :             parts::id_path,
     238             :             n.path, op);
     239         100 :         pctx = {path, pctx.next_arg_id()};
     240         100 :         fctx.advance_to(dest);
     241         100 :         auto dest1 = pct_vformat(
     242             :             path_chars, pctx, fctx);
     243         100 :         pct_string_view npath(dest, dest1 - dest);
     244         100 :         u.impl_.decoded_[parts::id_path] +=
     245         100 :             npath.decoded_size();
     246         100 :         if (!npath.empty())
     247             :         {
     248         100 :             u.impl_.nseg_ = std::count(
     249         100 :                 npath.begin() + 1,
     250         200 :                 npath.end(), '/') + 1;
     251             :         }
     252             :         // handle edge cases
     253             :         // 1) path is first component and the
     254             :         // first segment contains an unencoded ':'
     255             :         // This is impossible because the template
     256             :         // "{}" would be a host.
     257         178 :         if (u.scheme().empty() &&
     258          78 :             !u.has_authority())
     259             :         {
     260          78 :             auto fseg = u.encoded_segments().front();
     261          78 :             std::size_t nc = std::count(
     262          78 :                 fseg.begin(), fseg.end(), ':');
     263          78 :             if (nc)
     264             :             {
     265           4 :                 std::size_t diff = nc * 2;
     266           4 :                 u.reserve(n_total + diff);
     267           8 :                 dest = u.resize_impl(
     268             :                     parts::id_path,
     269           4 :                     n.path + diff, op);
     270           4 :                 char* dest0 = dest + diff;
     271           4 :                 std::memmove(dest0, dest, n.path);
     272          27 :                 while (dest0 != dest)
     273             :                 {
     274          23 :                     if (*dest0 != ':')
     275             :                     {
     276          15 :                         *dest++ = *dest0++;
     277             :                     }
     278             :                     else
     279             :                     {
     280           8 :                         *dest++ = '%';
     281           8 :                         *dest++ = '3';
     282           8 :                         *dest++ = 'A';
     283           8 :                         dest0++;
     284             :                     }
     285             :                 }
     286             :             }
     287             :         }
     288             :         // 2) url has no authority and path
     289             :         // starts with "//"
     290         186 :         if (!u.has_authority() &&
     291         186 :             u.encoded_path().starts_with("//"))
     292             :         {
     293           2 :             u.reserve(n_total + 2);
     294           4 :             dest = u.resize_impl(
     295             :                 parts::id_path,
     296           2 :                 n.path + 2, op);
     297           2 :             std::memmove(dest + 2, dest, n.path);
     298           2 :             *dest++ = '/';
     299           2 :             *dest = '.';
     300             :         }
     301             :     }
     302         136 :     if (has_query)
     303             :     {
     304          26 :         auto dest = u.resize_impl(
     305             :             parts::id_query,
     306          13 :             n.query + 1, op);
     307          13 :         *dest++ = '?';
     308          13 :         pctx = {query, pctx.next_arg_id()};
     309          13 :         fctx.advance_to(dest);
     310          13 :         auto dest1 = pct_vformat(
     311             :             query_chars, pctx, fctx);
     312          13 :         pct_string_view nquery(dest, dest1 - dest);
     313          13 :         u.impl_.decoded_[parts::id_query] +=
     314          13 :             nquery.decoded_size() + 1;
     315          13 :         if (!nquery.empty())
     316             :         {
     317          13 :             u.impl_.nparam_ = std::count(
     318             :                 nquery.begin(),
     319          26 :                 nquery.end(), '&') + 1;
     320             :         }
     321             :     }
     322         136 :     if (has_frag)
     323             :     {
     324          14 :         auto dest = u.resize_impl(
     325             :             parts::id_frag,
     326           7 :             n.frag + 1, op);
     327           7 :         *dest++ = '#';
     328           7 :         pctx = {frag, pctx.next_arg_id()};
     329           7 :         fctx.advance_to(dest);
     330           7 :         auto dest1 = pct_vformat(
     331             :             fragment_chars, pctx, fctx);
     332           7 :         u.impl_.decoded_[parts::id_frag] +=
     333           7 :             make_pct_string_view(
     334           7 :                 core::string_view(dest, dest1 - dest))
     335           7 :                 ->decoded_size() + 1;
     336             :     }
     337         136 : }
     338             : 
     339             : // This rule represents a pct-encoded string
     340             : // that contains an arbitrary number of
     341             : // replacement ids in it
     342             : template<class CharSet>
     343             : struct pct_encoded_fmt_string_rule_t
     344             : {
     345             :     using value_type = pct_string_view;
     346             : 
     347             :     constexpr
     348             :     pct_encoded_fmt_string_rule_t(
     349             :         CharSet const& cs) noexcept
     350             :         : cs_(cs)
     351             :     {
     352             :     }
     353             : 
     354             :     template<class CharSet_>
     355             :     friend
     356             :     constexpr
     357             :     auto
     358             :     pct_encoded_fmt_string_rule(
     359             :         CharSet_ const& cs) noexcept ->
     360             :     pct_encoded_fmt_string_rule_t<CharSet_>;
     361             : 
     362             :     system::result<value_type>
     363         241 :     parse(
     364             :         char const*& it,
     365             :         char const* end) const noexcept
     366             :     {
     367         241 :         auto const start = it;
     368         241 :         if(it == end)
     369             :         {
     370             :             // this might be empty
     371           1 :             return {};
     372             :         }
     373             : 
     374             :         // consume some with literal rule
     375             :         // this might be an empty literal
     376         240 :         auto literal_rule = pct_encoded_rule(cs_);
     377         240 :         auto rv = literal_rule.parse(it, end);
     378         470 :         while (rv)
     379             :         {
     380         470 :             auto it0 = it;
     381             :             // consume some with replacement id
     382             :             // rule
     383         470 :             if (!replacement_field_rule.parse(it, end))
     384             :             {
     385         240 :                 it = it0;
     386         240 :                 break;
     387             :             }
     388         230 :             rv = literal_rule.parse(it, end);
     389             :         }
     390             : 
     391         240 :         return core::string_view(start, it - start);
     392             :     }
     393             : 
     394             : private:
     395             :     CharSet cs_;
     396             : };
     397             : 
     398             : template<class CharSet>
     399             : constexpr
     400             : auto
     401             : pct_encoded_fmt_string_rule(
     402             :     CharSet const& cs) noexcept ->
     403             :     pct_encoded_fmt_string_rule_t<CharSet>
     404             : {
     405             :     // If an error occurs here it means that
     406             :     // the value of your type does not meet
     407             :     // the requirements. Please check the
     408             :     // documentation!
     409             :     static_assert(
     410             :         grammar::is_charset<CharSet>::value,
     411             :         "CharSet requirements not met");
     412             : 
     413             :     return pct_encoded_fmt_string_rule_t<CharSet>(cs);
     414             : }
     415             : 
     416             : // This rule represents a regular string with
     417             : // only chars from the specified charset and
     418             : // an arbitrary number of replacement ids in it
     419             : template<class CharSet>
     420             : struct fmt_token_rule_t
     421             : {
     422             :     using value_type = pct_string_view;
     423             : 
     424             :     constexpr
     425             :     fmt_token_rule_t(
     426             :         CharSet const& cs) noexcept
     427             :         : cs_(cs)
     428             :     {
     429             :     }
     430             : 
     431             :     template<class CharSet_>
     432             :     friend
     433             :     constexpr
     434             :     auto
     435             :     fmt_token_rule(
     436             :         CharSet_ const& cs) noexcept ->
     437             :     fmt_token_rule_t<CharSet_>;
     438             : 
     439             :     system::result<value_type>
     440          13 :     parse(
     441             :         char const*& it,
     442             :         char const* end) const noexcept
     443             :     {
     444          13 :         auto const start = it;
     445          13 :         BOOST_ASSERT(it != end);
     446             :         /*
     447             :         // This should never happen because
     448             :         // all tokens are optional and will
     449             :         // already return `none`:
     450             :         if(it == end)
     451             :         {
     452             :             BOOST_URL_RETURN_EC(
     453             :                 grammar::error::need_more);
     454             :         }
     455             :         */
     456             : 
     457             :         // consume some with literal rule
     458             :         // this might be an empty literal
     459             :         auto partial_token_rule =
     460          13 :             grammar::optional_rule(
     461          13 :                 grammar::token_rule(cs_));
     462          26 :         auto rv = partial_token_rule.parse(it, end);
     463          24 :         while (rv)
     464             :         {
     465          24 :             auto it0 = it;
     466             :             // consume some with replacement id
     467          24 :             if (!replacement_field_rule.parse(it, end))
     468             :             {
     469             :                 // no replacement and no more cs
     470             :                 // before: nothing else to consume
     471          13 :                 it = it0;
     472          13 :                 break;
     473             :             }
     474             :             // after {...}, consume any more chars
     475             :             // in the charset
     476          11 :             rv = partial_token_rule.parse(it, end);
     477             :         }
     478             : 
     479          13 :         if(it == start)
     480             :         {
     481             :             // it != end but we consumed nothing
     482           1 :             BOOST_URL_RETURN_EC(
     483             :                 grammar::error::need_more);
     484             :         }
     485             : 
     486          12 :         return core::string_view(start, it - start);
     487             :     }
     488             : 
     489             : private:
     490             :     CharSet cs_;
     491             : };
     492             : 
     493             : template<class CharSet>
     494             : constexpr
     495             : auto
     496             : fmt_token_rule(
     497             :     CharSet const& cs) noexcept ->
     498             :     fmt_token_rule_t<CharSet>
     499             : {
     500             :     // If an error occurs here it means that
     501             :     // the value of your type does not meet
     502             :     // the requirements. Please check the
     503             :     // documentation!
     504             :     static_assert(
     505             :         grammar::is_charset<CharSet>::value,
     506             :         "CharSet requirements not met");
     507             : 
     508             :     return fmt_token_rule_t<CharSet>(cs);
     509             : }
     510             : 
     511             : struct userinfo_template_rule_t
     512             : {
     513             :     struct value_type
     514             :     {
     515             :         core::string_view user;
     516             :         core::string_view password;
     517             :         bool has_password = false;
     518             :     };
     519             : 
     520             :     auto
     521          48 :     parse(
     522             :         char const*& it,
     523             :         char const* end
     524             :             ) const noexcept ->
     525             :         system::result<value_type>
     526             :     {
     527             :         static constexpr auto uchars =
     528             :             unreserved_chars +
     529             :             sub_delim_chars;
     530             :         static constexpr auto pwchars =
     531             :             uchars + ':';
     532             : 
     533          48 :         value_type t;
     534             : 
     535             :         // user
     536             :         static constexpr auto user_fmt_rule =
     537             :             pct_encoded_fmt_string_rule(uchars);
     538             :         auto rv = grammar::parse(
     539          48 :             it, end, user_fmt_rule);
     540          48 :         BOOST_ASSERT(rv);
     541          48 :         t.user = *rv;
     542             : 
     543             :         // ':'
     544          48 :         if( it == end ||
     545          31 :             *it != ':')
     546             :         {
     547          32 :             t.has_password = false;
     548          32 :             t.password = {};
     549          32 :             return t;
     550             :         }
     551          16 :         ++it;
     552             : 
     553             :         // pass
     554             :         static constexpr auto pass_fmt_rule =
     555             :             pct_encoded_fmt_string_rule(grammar::ref(pwchars));
     556             :         rv = grammar::parse(
     557          16 :             it, end, pass_fmt_rule);
     558          16 :         BOOST_ASSERT(rv);
     559          16 :         t.has_password = true;
     560          16 :         t.password = *rv;
     561             : 
     562          16 :         return t;
     563             :     }
     564             : };
     565             : 
     566             : constexpr userinfo_template_rule_t userinfo_template_rule{};
     567             : 
     568             : struct host_template_rule_t
     569             : {
     570             :     using value_type = core::string_view;
     571             : 
     572             :     auto
     573          49 :     parse(
     574             :         char const*& it,
     575             :         char const* end
     576             :             ) const noexcept ->
     577             :         system::result<value_type>
     578             :     {
     579          49 :         if(it == end)
     580             :         {
     581             :             // empty host
     582           1 :             return {};
     583             :         }
     584             : 
     585             :         // the host type will be ultimately
     586             :         // validated when applying the replacement
     587             :         // strings. Any chars allowed in hosts
     588             :         // are allowed here.
     589          48 :         if (*it != '[')
     590             :         {
     591             :             // IPv4address and reg-name have the
     592             :             // same char sets.
     593          46 :             constexpr auto any_host_template_rule =
     594             :                 pct_encoded_fmt_string_rule(host_chars);
     595             :             auto rv = grammar::parse(
     596          46 :                 it, end, any_host_template_rule);
     597             :             // any_host_template_rule can always
     598             :             // be empty, so it's never invalid
     599          46 :             BOOST_ASSERT(rv);
     600          46 :             return detail::to_sv(*rv);
     601             :         }
     602             :         // IP-literals need to be enclosed in
     603             :         // "[]" if using ':' in the template
     604             :         // string, because the ':' would be
     605             :         // ambiguous with the port in fmt string.
     606             :         // The "[]:" can be used in replacement
     607             :         // strings without the "[]" though.
     608           2 :         constexpr auto ip_literal_template_rule =
     609             :             pct_encoded_fmt_string_rule(lhost_chars);
     610           2 :         auto it0 = it;
     611             :         auto rv = grammar::parse(
     612             :             it, end,
     613           2 :             grammar::optional_rule(
     614           2 :                 grammar::tuple_rule(
     615           2 :                     grammar::squelch(
     616           2 :                         grammar::delim_rule('[')),
     617             :                     ip_literal_template_rule,
     618           2 :                     grammar::squelch(
     619           4 :                         grammar::delim_rule(']')))));
     620             :         // ip_literal_template_rule can always
     621             :         // be empty, so it's never invalid, but
     622             :         // the rule might fail to match the
     623             :         // closing "]"
     624           2 :         BOOST_ASSERT(rv);
     625           2 :         return core::string_view{it0, it};
     626             :     }
     627             : };
     628             : 
     629             : constexpr host_template_rule_t host_template_rule{};
     630             : 
     631             : struct authority_template_rule_t
     632             : {
     633             :     using value_type = pattern;
     634             : 
     635             :     system::result<value_type>
     636          49 :     parse(
     637             :         char const*& it,
     638             :         char const* end
     639             :     ) const noexcept
     640             :     {
     641          49 :         pattern u;
     642             : 
     643             :         // [ userinfo "@" ]
     644             :         {
     645             :             auto rv = grammar::parse(
     646             :                 it, end,
     647          49 :                 grammar::optional_rule(
     648          49 :                     grammar::tuple_rule(
     649             :                         userinfo_template_rule,
     650          49 :                         grammar::squelch(
     651          98 :                             grammar::delim_rule('@')))));
     652          49 :             BOOST_ASSERT(rv);
     653          49 :             if(rv->has_value())
     654             :             {
     655           9 :                 auto& r = **rv;
     656           9 :                 u.has_user = true;
     657           9 :                 u.user = r.user;
     658           9 :                 u.has_pass = r.has_password;
     659           9 :                 u.pass = r.password;
     660             :             }
     661             :         }
     662             : 
     663             :         // host
     664             :         {
     665             :             auto rv = grammar::parse(
     666             :                 it, end,
     667          49 :                 host_template_rule);
     668             :             // host is allowed to be empty
     669          49 :             BOOST_ASSERT(rv);
     670          49 :             u.host = *rv;
     671             :         }
     672             : 
     673             :         // [ ":" port ]
     674             :         {
     675             :             constexpr auto port_template_rule =
     676             :                 grammar::optional_rule(
     677             :                     fmt_token_rule(grammar::digit_chars));
     678          49 :             auto it0 = it;
     679             :             auto rv = grammar::parse(
     680             :                 it, end,
     681          49 :                 grammar::tuple_rule(
     682          49 :                     grammar::squelch(
     683          49 :                         grammar::delim_rule(':')),
     684          98 :                     port_template_rule));
     685          49 :             if (!rv)
     686             :             {
     687          35 :                 it = it0;
     688             :             }
     689             :             else
     690             :             {
     691          14 :                 u.has_port = true;
     692          14 :                 if (rv->has_value())
     693             :                 {
     694          12 :                     u.port = **rv;
     695             :                 }
     696             :             }
     697             :         }
     698             : 
     699          49 :         return u;
     700             :     }
     701             : };
     702             : 
     703             : constexpr authority_template_rule_t authority_template_rule{};
     704             : 
     705             : struct scheme_template_rule_t
     706             : {
     707             :     using value_type = core::string_view;
     708             : 
     709             :     system::result<value_type>
     710         147 :     parse(
     711             :         char const*& it,
     712             :         char const* end) const noexcept
     713             :     {
     714         147 :         auto const start = it;
     715         147 :         if(it == end)
     716             :         {
     717             :             // scheme can't be empty
     718           1 :             BOOST_URL_RETURN_EC(
     719             :                 grammar::error::mismatch);
     720             :         }
     721         270 :         if(!grammar::alpha_chars(*it) &&
     722         124 :             *it != '{')
     723             :         {
     724             :             // expected alpha
     725          20 :             BOOST_URL_RETURN_EC(
     726             :                 grammar::error::mismatch);
     727             :         }
     728             : 
     729             :         // it starts with replacement id or alpha char
     730         126 :         if (!grammar::alpha_chars(*it))
     731             :         {
     732         104 :             if (!replacement_field_rule.parse(it, end))
     733             :             {
     734             :                 // replacement_field_rule is invalid
     735           2 :                 BOOST_URL_RETURN_EC(
     736             :                     grammar::error::mismatch);
     737             :             }
     738             :         }
     739             :         else
     740             :         {
     741             :             // skip first
     742          22 :             ++it;
     743             :         }
     744             : 
     745             :         static
     746             :         constexpr
     747             :         grammar::lut_chars scheme_chars(
     748             :             "0123456789" "+-."
     749             :             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     750             :             "abcdefghijklmnopqrstuvwxyz");
     751             : 
     752             :         // non-scheme chars might be a new
     753             :         // replacement-id or just an invalid char
     754         124 :         it = grammar::find_if_not(
     755             :             it, end, scheme_chars);
     756         126 :         while (it != end)
     757             :         {
     758          75 :             auto it0 = it;
     759          75 :             if (!replacement_field_rule.parse(it, end))
     760             :             {
     761          73 :                 it = it0;
     762          73 :                 break;
     763             :             }
     764           2 :             it = grammar::find_if_not(
     765             :                 it, end, scheme_chars);
     766             :         }
     767         124 :         return core::string_view(start, it - start);
     768             :     }
     769             : };
     770             : 
     771             : constexpr scheme_template_rule_t scheme_template_rule{};
     772             : 
     773             : // This rule should consider all url types at the
     774             : // same time according to the format string
     775             : // - relative urls with no scheme/authority
     776             : // - absolute urls have no fragment
     777             : struct pattern_rule_t
     778             : {
     779             :     using value_type = pattern;
     780             : 
     781             :     system::result<value_type>
     782         147 :     parse(
     783             :         char const*& it,
     784             :         char const* const end
     785             :     ) const noexcept
     786             :     {
     787         147 :         pattern u;
     788             : 
     789             :         // optional scheme
     790             :         {
     791         147 :             auto it0 = it;
     792             :             auto rv = grammar::parse(
     793             :                 it, end,
     794         147 :                 grammar::tuple_rule(
     795             :                     scheme_template_rule,
     796         147 :                     grammar::squelch(
     797         147 :                         grammar::delim_rule(':'))));
     798         147 :             if(rv)
     799          59 :                 u.scheme = *rv;
     800             :             else
     801          88 :                 it = it0;
     802             :         }
     803             : 
     804             :         // hier_part (authority + path)
     805             :         // if there are less than 2 chars left,
     806             :         // we are parsing the path
     807         147 :         if (it == end)
     808             :         {
     809             :             // this is over, so we can consider
     810             :             // that a "path-empty"
     811           4 :             return u;
     812             :         }
     813         143 :         if(end - it == 1)
     814             :         {
     815             :             // only one char left
     816             :             // it can be a single separator "/",
     817             :             // representing an empty absolute path,
     818             :             // or a single-char segment
     819           5 :             if(*it == '/')
     820             :             {
     821             :                 // path-absolute
     822           2 :                 u.path = {it, 1};
     823           2 :                 ++it;
     824           2 :                 return u;
     825             :             }
     826             :             // this can be a:
     827             :             // - path-noscheme if there's no scheme, or
     828             :             // - path-rootless with a single char, or
     829             :             // - path-empty (and consume nothing)
     830           4 :             if (!u.scheme.empty() ||
     831           1 :                 *it != ':')
     832             :             {
     833             :                 // path-rootless with a single char
     834             :                 // this needs to be a segment because
     835             :                 // the authority needs two slashes
     836             :                 // "//"
     837             :                 // path-noscheme also matches here
     838             :                 // because we already validated the
     839             :                 // first char
     840             :                 auto rv = grammar::parse(
     841           3 :                     it, end, urls::detail::segment_rule);
     842           3 :                 if(! rv)
     843           1 :                     return rv.error();
     844           2 :                 u.path = *rv;
     845             :             }
     846           2 :             return u;
     847             :         }
     848             : 
     849             :         // authority
     850         138 :         if( it[0] == '/' &&
     851          62 :             it[1] == '/')
     852             :         {
     853             :             // "//" always indicates authority
     854          49 :             it += 2;
     855             :             auto rv = grammar::parse(
     856             :                 it, end,
     857          49 :                 authority_template_rule);
     858             :             // authority is allowed to be empty
     859          49 :             BOOST_ASSERT(rv);
     860          49 :             u.has_authority = true;
     861          49 :             u.has_user = rv->has_user;
     862          49 :             u.user = rv->user;
     863          49 :             u.has_pass = rv->has_pass;
     864          49 :             u.pass = rv->pass;
     865          49 :             u.host = rv->host;
     866          49 :             u.has_port = rv->has_port;
     867          49 :             u.port = rv->port;
     868             :         }
     869             : 
     870             :         // the authority requires an absolute path
     871             :         // or an empty path
     872         138 :         if (it == end ||
     873         111 :             (u.has_authority &&
     874          22 :              (*it != '/' &&
     875           8 :               *it != '?' &&
     876           2 :               *it != '#')))
     877             :         {
     878             :             // path-empty
     879          29 :             return u;
     880             :         }
     881             : 
     882             :         // path-abempty
     883             :         // consume the whole path at once because
     884             :         // we're going to count number of segments
     885             :         // later after the replacements happen
     886             :         static constexpr auto segment_fmt_rule =
     887             :             pct_encoded_fmt_string_rule(path_chars);
     888             :         auto rp = grammar::parse(
     889         109 :             it, end, segment_fmt_rule);
     890             :         // path-abempty is allowed to be empty
     891         109 :         BOOST_ASSERT(rp);
     892         109 :         u.path = *rp;
     893             : 
     894             :         // [ "?" query ]
     895             :         {
     896             :             static constexpr auto query_fmt_rule =
     897             :                 pct_encoded_fmt_string_rule(query_chars);
     898             :             auto rv = grammar::parse(
     899             :                 it, end,
     900         109 :                 grammar::tuple_rule(
     901         109 :                     grammar::squelch(
     902         109 :                         grammar::delim_rule('?')),
     903         109 :                     query_fmt_rule));
     904             :             // query is allowed to be empty but
     905             :             // delim rule is not
     906         109 :             if (rv)
     907             :             {
     908          13 :                 u.has_query = true;
     909          13 :                 u.query = *rv;
     910             :             }
     911             :         }
     912             : 
     913             :         // [ "#" fragment ]
     914             :         {
     915             :             static constexpr auto frag_fmt_rule =
     916             :                 pct_encoded_fmt_string_rule(fragment_chars);
     917             :             auto rv = grammar::parse(
     918             :                 it, end,
     919         109 :                 grammar::tuple_rule(
     920         109 :                     grammar::squelch(
     921         109 :                         grammar::delim_rule('#')),
     922         109 :                     frag_fmt_rule));
     923             :             // frag is allowed to be empty but
     924             :             // delim rule is not
     925         109 :             if (rv)
     926             :             {
     927           7 :                 u.has_frag = true;
     928           7 :                 u.frag = *rv;
     929             :             }
     930             :         }
     931             : 
     932         109 :         return u;
     933             :     }
     934             : };
     935             : 
     936             : constexpr pattern_rule_t pattern_rule{};
     937             : 
     938             : system::result<pattern>
     939         147 : parse_pattern(
     940             :     core::string_view s)
     941             : {
     942             :     return grammar::parse(
     943         147 :         s, pattern_rule);
     944             : }
     945             : 
     946             : } // detail
     947             : } // urls
     948             : } // boost
     949             : 
     950             : #endif

Generated by: LCOV version 1.15