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

Generated by: LCOV version 2.3