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

Generated by: LCOV version 2.3