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

Generated by: LCOV version 2.1