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-25 21:00:01 Functions: 100.0 % 10 10

           TLA  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 HIT         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