LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/libs/url/src/detail - pattern.cpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 364 364
Test Date: 2026-02-13 15:53:22 Functions: 100.0 % 10 10

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

Generated by: LCOV version 2.3