LCOV - code coverage report
Current view: top level - libs/url/src/detail - pattern.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 368 368
Test Date: 2026-01-27 21:05:51 Functions: 100.0 % 10 10

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

Generated by: LCOV version 2.3