LCOV - code coverage report
Current view: top level - url/impl - url_base.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 99.5 % 1520 1512
Test Date: 2026-02-13 15:53:22 Functions: 100.0 % 80 80

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
       3              : // Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
       4              : //
       5              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7              : //
       8              : // Official repository: https://github.com/boostorg/url
       9              : //
      10              : 
      11              : #ifndef BOOST_URL_IMPL_URL_BASE_HPP
      12              : #define BOOST_URL_IMPL_URL_BASE_HPP
      13              : 
      14              : #include <boost/url/encode.hpp>
      15              : #include <boost/url/error.hpp>
      16              : #include <boost/url/host_type.hpp>
      17              : #include <boost/url/scheme.hpp>
      18              : #include <boost/url/url_view.hpp>
      19              : #include <boost/url/detail/any_params_iter.hpp>
      20              : #include <boost/url/detail/any_segments_iter.hpp>
      21              : #include <boost/url/detail/decode.hpp>
      22              : #include <boost/url/detail/encode.hpp>
      23              : #include <boost/url/detail/except.hpp>
      24              : #include <boost/url/detail/normalize.hpp>
      25              : #include <boost/url/detail/path.hpp>
      26              : #include <boost/url/detail/print.hpp>
      27              : #include <boost/url/grammar/ci_string.hpp>
      28              : #include <boost/url/rfc/authority_rule.hpp>
      29              : #include <boost/url/rfc/query_rule.hpp>
      30              : #include <boost/url/rfc/ipv6_address_rule.hpp>
      31              : #include <boost/url/rfc/detail/charsets.hpp>
      32              : #include <boost/url/rfc/detail/host_rule.hpp>
      33              : #include <boost/url/rfc/detail/ipvfuture_rule.hpp>
      34              : #include <boost/url/rfc/detail/path_rules.hpp>
      35              : #include <boost/url/rfc/detail/port_rule.hpp>
      36              : #include <boost/url/rfc/detail/scheme_rule.hpp>
      37              : #include <boost/url/rfc/detail/userinfo_rule.hpp>
      38              : #include <boost/url/grammar/parse.hpp>
      39              : #include <boost/url/detail/move_chars.hpp>
      40              : #include <cstring>
      41              : #include <iostream>
      42              : #include <stdexcept>
      43              : #include <utility>
      44              : 
      45              : namespace boost {
      46              : namespace urls {
      47              : 
      48              : //------------------------------------------------
      49              : 
      50              : // these objects help handle the cases
      51              : // where the user passes in strings that
      52              : // come from inside the url buffer.
      53              : 
      54              : inline
      55         8275 : url_base::
      56              : op_t::
      57              : ~op_t()
      58              : {
      59         8275 :     if(old)
      60         1032 :         u.cleanup(*this);
      61         8275 :     u.check_invariants();
      62         8275 : }
      63              : 
      64              : inline
      65         8275 : url_base::
      66              : op_t::
      67              : op_t(
      68              :     url_base& impl_,
      69              :     core::string_view* s0_,
      70         8275 :     core::string_view* s1_) noexcept
      71         8275 :     : u(impl_)
      72         8275 :     , s0(s0_)
      73         8275 :     , s1(s1_)
      74              : {
      75         8275 :     u.check_invariants();
      76         8275 : }
      77              : 
      78              : inline
      79              : void
      80         2449 : url_base::
      81              : op_t::
      82              : move(
      83              :     char* dest,
      84              :     char const* src,
      85              :     std::size_t n) noexcept
      86              : {
      87         2449 :     if(! n)
      88          410 :         return;
      89         2039 :     if(s0)
      90              :     {
      91         1273 :         if(s1)
      92           63 :             return detail::move_chars(
      93           63 :              dest, src, n, *s0, *s1);
      94         1210 :         return detail::move_chars(
      95         1210 :             dest, src, n, *s0);
      96              :     }
      97          766 :     detail::move_chars(
      98              :         dest, src, n);
      99              : }
     100              : 
     101              : //------------------------------------------------
     102              : 
     103              : // construct reference
     104              : inline
     105         1503 : url_base::
     106              : url_base(
     107         1503 :     detail::url_impl const& impl) noexcept
     108         1503 :     : url_view_base(impl)
     109              : {
     110         1503 : }
     111              : 
     112              : inline
     113              : void
     114          165 : url_base::
     115              : reserve_impl(std::size_t n)
     116              : {
     117          165 :     op_t op(*this);
     118          165 :     reserve_impl(n, op);
     119          164 :     if(s_)
     120          162 :         s_[size()] = '\0';
     121          165 : }
     122              : 
     123              : // make a copy of u
     124              : inline
     125              : void
     126         3734 : url_base::
     127              : copy(url_view_base const& u)
     128              : {
     129         3734 :     if (this == &u)
     130          117 :         return;
     131         3734 :     op_t op(*this);
     132         3734 :     if(u.size() == 0)
     133              :     {
     134          117 :         clear();
     135          117 :         return;
     136              :     }
     137         3617 :     reserve_impl(
     138              :         u.size(), op);
     139         3614 :     impl_ = u.impl();
     140         3614 :     impl_.cs_ = s_;
     141         3614 :     impl_.from_ = {from::url};
     142         3614 :     std::memcpy(s_,
     143         3614 :         u.data(), u.size());
     144         3614 :     s_[size()] = '\0';
     145         3734 : }
     146              : 
     147              : //------------------------------------------------
     148              : //
     149              : // Scheme
     150              : //
     151              : //------------------------------------------------
     152              : 
     153              : inline
     154              : url_base&
     155           56 : url_base::
     156              : set_scheme(core::string_view s)
     157              : {
     158           56 :     set_scheme_impl(
     159              :         s, string_to_scheme(s));
     160           43 :     return *this;
     161              : }
     162              : 
     163              : inline
     164              : url_base&
     165           13 : url_base::
     166              : set_scheme_id(urls::scheme id)
     167              : {
     168           13 :     if(id == urls::scheme::unknown)
     169            1 :         detail::throw_invalid_argument();
     170           12 :     if(id == urls::scheme::none)
     171            1 :         return remove_scheme();
     172           11 :     set_scheme_impl(to_string(id), id);
     173           11 :     return *this;
     174              : }
     175              : 
     176              : inline
     177              : url_base&
     178           36 : url_base::
     179              : remove_scheme()
     180              : {
     181           36 :     op_t op(*this);
     182           36 :     auto const sn = impl_.len(id_scheme);
     183           36 :     if(sn == 0)
     184            9 :         return *this;
     185           27 :     auto const po = impl_.offset(id_path);
     186           27 :     auto fseg = first_segment();
     187              :     bool const encode_colon =
     188           27 :         !has_authority() &&
     189           20 :         impl_.nseg_ > 0 &&
     190           58 :         s_[po] != '/' &&
     191           11 :         fseg.contains(':');
     192           27 :     if(!encode_colon)
     193              :     {
     194              :         // just remove the scheme
     195           18 :         resize_impl(id_scheme, 0, op);
     196           18 :         impl_.scheme_ = urls::scheme::none;
     197           18 :         check_invariants();
     198           18 :         return *this;
     199              :     }
     200              :     // encode any ":" in the first path segment
     201            9 :     BOOST_ASSERT(sn >= 2);
     202            9 :     auto pn = impl_.len(id_path);
     203            9 :     std::size_t cn = 0;
     204           46 :     for (char c: fseg)
     205           37 :         cn += c == ':';
     206              :     std::size_t new_size =
     207            9 :         size() - sn + 2 * cn;
     208            9 :     bool need_resize = new_size > size();
     209            9 :     if (need_resize)
     210              :     {
     211            1 :         resize_impl(
     212            1 :             id_path, pn + 2 * cn, op);
     213              :     }
     214              :     // move [id_scheme, id_path) left
     215            9 :     op.move(
     216              :         s_,
     217            9 :         s_ + sn,
     218              :         po - sn);
     219              :     // move [id_path, id_query) left
     220            9 :     auto qo = impl_.offset(id_query);
     221            9 :     op.move(
     222            9 :         s_ + po - sn,
     223            9 :         s_ + po,
     224              :         qo - po);
     225              :     // move [id_query, id_end) left
     226            9 :     op.move(
     227            9 :         s_ + qo - sn + 2 * cn,
     228            9 :         s_ + qo,
     229            9 :         impl_.offset(id_end) - qo);
     230              : 
     231              :     // adjust part offsets.
     232              :     // (po and qo are invalidated)
     233            9 :     if (need_resize)
     234              :     {
     235            1 :         impl_.adjust_left(id_user, id_end, sn);
     236              :     }
     237              :     else
     238              :     {
     239            8 :         impl_.adjust_left(id_user, id_path, sn);
     240            8 :         impl_.adjust_left(id_query, id_end, sn - 2 * cn);
     241              :     }
     242            9 :     if (encode_colon)
     243              :     {
     244              :         // move the 2nd, 3rd, ... segments
     245            9 :         auto begin = s_ + impl_.offset(id_path);
     246            9 :         auto it = begin;
     247            9 :         auto end = begin + pn;
     248           46 :         while (*it != '/' &&
     249              :                it != end)
     250           37 :             ++it;
     251              :         // we don't need op here because this is
     252              :         // an internal operation
     253            9 :         std::memmove(it + (2 * cn), it, end - it);
     254              : 
     255              :         // move 1st segment
     256            9 :         auto src = s_ + impl_.offset(id_path) + pn;
     257            9 :         auto dest = s_ + impl_.offset(id_query);
     258            9 :         src -= end - it;
     259            9 :         dest -= end - it;
     260            9 :         pn -= end - it;
     261              :         do {
     262           37 :             --src;
     263           37 :             --dest;
     264           37 :             if (*src != ':')
     265              :             {
     266           25 :                 *dest = *src;
     267              :             }
     268              :             else
     269              :             {
     270              :                 // use uppercase as required by
     271              :                 // syntax-based normalization
     272           12 :                 *dest-- = 'A';
     273           12 :                 *dest-- = '3';
     274           12 :                 *dest = '%';
     275              :             }
     276           37 :             --pn;
     277           37 :         } while (pn);
     278              :     }
     279            9 :     s_[size()] = '\0';
     280            9 :     impl_.scheme_ = urls::scheme::none;
     281            9 :     return *this;
     282           36 : }
     283              : 
     284              : //------------------------------------------------
     285              : //
     286              : // Authority
     287              : //
     288              : //------------------------------------------------
     289              : 
     290              : inline
     291              : url_base&
     292          112 : url_base::
     293              : set_encoded_authority(
     294              :     pct_string_view s)
     295              : {
     296          112 :     op_t op(*this, &detail::ref(s));
     297          114 :     authority_view a = grammar::parse(
     298              :         s, authority_rule
     299          112 :             ).value(BOOST_URL_POS);
     300          111 :     auto n = s.size() + 2;
     301              :     auto const need_slash =
     302          133 :         ! is_path_absolute() &&
     303           22 :         impl_.len(id_path) > 0;
     304          111 :     if(need_slash)
     305            2 :         ++n;
     306          111 :     auto dest = resize_impl(
     307              :         id_user, id_path, n, op);
     308          111 :     dest[0] = '/';
     309          111 :     dest[1] = '/';
     310          111 :     std::memcpy(dest + 2,
     311          111 :         s.data(), s.size());
     312          111 :     if(need_slash)
     313            2 :         dest[n - 1] = '/';
     314          111 :     impl_.apply_authority(a);
     315          111 :     if(need_slash)
     316            2 :         impl_.adjust_right(
     317              :                 id_query, id_end, 1);
     318          111 :     return *this;
     319          112 : }
     320              : 
     321              : inline
     322              : url_base&
     323           57 : url_base::
     324              : remove_authority()
     325              : {
     326           57 :     if(! has_authority())
     327           30 :         return *this;
     328              : 
     329           27 :     op_t op(*this);
     330           27 :     auto path = impl_.get(id_path);
     331           27 :     bool const need_dot = path.starts_with("//");
     332           27 :     if(need_dot)
     333              :     {
     334              :         // prepend "/.", can't throw
     335            4 :         auto p = resize_impl(
     336              :             id_user, id_path, 2, op);
     337            4 :         p[0] = '/';
     338            4 :         p[1] = '.';
     339            4 :         impl_.split(id_user, 0);
     340            4 :         impl_.split(id_pass, 0);
     341            4 :         impl_.split(id_host, 0);
     342            4 :         impl_.split(id_port, 0);
     343              :     }
     344              :     else
     345              :     {
     346           23 :         resize_impl(
     347              :             id_user, id_path, 0, op);
     348              :     }
     349           27 :     impl_.host_type_ =
     350              :         urls::host_type::none;
     351           27 :     return *this;
     352           27 : }
     353              : 
     354              : //------------------------------------------------
     355              : //
     356              : // Userinfo
     357              : //
     358              : //------------------------------------------------
     359              : 
     360              : inline
     361              : url_base&
     362           47 : url_base::
     363              : set_userinfo(
     364              :     core::string_view s)
     365              : {
     366           47 :     op_t op(*this, &s);
     367           47 :     encoding_opts opt;
     368           47 :     auto const n = encoded_size(
     369              :         s, detail::userinfo_chars, opt);
     370           47 :     auto dest = set_userinfo_impl(n, op);
     371           47 :     encode(
     372              :         dest,
     373              :         n,
     374              :         s,
     375              :         detail::userinfo_chars,
     376              :         opt);
     377           47 :     auto const pos = impl_.get(
     378              :         id_user, id_host
     379           47 :             ).find_first_of(':');
     380           47 :     if(pos != core::string_view::npos)
     381              :     {
     382            9 :         impl_.split(id_user, pos);
     383              :         // find ':' in plain string
     384              :         auto const pos2 =
     385            9 :             s.find_first_of(':');
     386            9 :         if(pos2 != core::string_view::npos)
     387              :         {
     388              :             // pos2 is the ':' index in plain input (user[:pass])
     389              :             // decoded user is [0, pos2), decoded pass is (pos2, end].
     390            9 :             impl_.decoded_[id_user] =
     391            9 :                 detail::to_size_type(pos2);
     392            9 :             impl_.decoded_[id_pass] =
     393            9 :                 detail::to_size_type(s.size() - pos2 - 1);
     394              :         }
     395              :         else
     396              :         {
     397            0 :             impl_.decoded_[id_user] =
     398            0 :                 detail::to_size_type(s.size());
     399            0 :             impl_.decoded_[id_pass] = 0;
     400              :         }
     401              :     }
     402              :     else
     403              :     {
     404           38 :         impl_.decoded_[id_user] =
     405           38 :             detail::to_size_type(s.size());
     406           38 :         impl_.decoded_[id_pass] = 0;
     407              :     }
     408           47 :     return *this;
     409           47 : }
     410              : 
     411              : inline
     412              : url_base&
     413           52 : url_base::
     414              : set_encoded_userinfo(
     415              :     pct_string_view s)
     416              : {
     417           52 :     op_t op(*this, &detail::ref(s));
     418           52 :     auto const pos = s.find_first_of(':');
     419           52 :     if(pos != core::string_view::npos)
     420              :     {
     421              :         // user:pass
     422            7 :         auto const s0 = s.substr(0, pos);
     423            7 :         auto const s1 = s.substr(pos + 1);
     424              :         auto const n0 =
     425            7 :             detail::re_encoded_size_unsafe(
     426              :                 s0,
     427              :                 detail::user_chars);
     428              :         auto const n1 =
     429            7 :             detail::re_encoded_size_unsafe(s1,
     430              :                 detail::password_chars);
     431              :         auto dest =
     432            7 :             set_userinfo_impl(n0 + n1 + 1, op);
     433            7 :         impl_.decoded_[id_user] =
     434            7 :             detail::to_size_type(detail::re_encode_unsafe(
     435              :                 dest,
     436            7 :                 dest + n0,
     437              :                 s0,
     438              :                 detail::user_chars));
     439            7 :         *dest++ = ':';
     440            7 :         impl_.decoded_[id_pass] =
     441            7 :             detail::to_size_type(detail::re_encode_unsafe(
     442              :                 dest,
     443            7 :                 dest + n1,
     444              :                 s1,
     445              :                 detail::password_chars));
     446            7 :         impl_.split(id_user, 2 + n0);
     447              :     }
     448              :     else
     449              :     {
     450              :         // user
     451              :         auto const n =
     452           45 :             detail::re_encoded_size_unsafe(
     453              :                 s, detail::user_chars);
     454           45 :         auto dest = set_userinfo_impl(n, op);
     455           45 :         impl_.decoded_[id_user] =
     456           90 :             detail::to_size_type(detail::re_encode_unsafe(
     457              :                 dest,
     458           45 :                 dest + n,
     459              :                 s,
     460              :                 detail::user_chars));
     461           45 :         impl_.split(id_user, 2 + n);
     462           45 :         impl_.decoded_[id_pass] = 0;
     463              :     }
     464           52 :     return *this;
     465           52 : }
     466              : 
     467              : inline
     468              : url_base&
     469           24 : url_base::
     470              : remove_userinfo() noexcept
     471              : {
     472           24 :     if(impl_.len(id_pass) == 0)
     473            6 :         return *this; // no userinfo
     474              : 
     475           18 :     op_t op(*this);
     476              :     // keep authority '//'
     477           18 :     resize_impl(
     478              :         id_user, id_host, 2, op);
     479           18 :     impl_.decoded_[id_user] = 0;
     480           18 :     impl_.decoded_[id_pass] = 0;
     481           18 :     return *this;
     482           18 : }
     483              : 
     484              : //------------------------------------------------
     485              : 
     486              : inline
     487              : url_base&
     488           50 : url_base::
     489              : set_user(core::string_view s)
     490              : {
     491           50 :     op_t op(*this, &s);
     492           50 :     encoding_opts opt;
     493           50 :     auto const n = encoded_size(
     494              :         s, detail::user_chars, opt);
     495           50 :     auto dest = set_user_impl(n, op);
     496           50 :     encode_unsafe(
     497              :         dest,
     498              :         n,
     499              :         s,
     500              :         detail::user_chars,
     501              :         opt);
     502           50 :     impl_.decoded_[id_user] =
     503           50 :         detail::to_size_type(s.size());
     504           50 :     return *this;
     505           50 : }
     506              : 
     507              : inline
     508              : url_base&
     509           43 : url_base::
     510              : set_encoded_user(
     511              :     pct_string_view s)
     512              : {
     513           43 :     op_t op(*this, &detail::ref(s));
     514              :     auto const n =
     515           43 :         detail::re_encoded_size_unsafe(
     516              :             s, detail::user_chars);
     517           43 :     auto dest = set_user_impl(n, op);
     518           43 :     impl_.decoded_[id_user] =
     519           86 :         detail::to_size_type(detail::re_encode_unsafe(
     520              :             dest,
     521           43 :             dest + n,
     522              :             s,
     523              :             detail::user_chars));
     524           43 :     BOOST_ASSERT(
     525              :         impl_.decoded_[id_user] ==
     526              :             s.decoded_size());
     527           43 :     return *this;
     528           43 : }
     529              : 
     530              : //------------------------------------------------
     531              : 
     532              : inline
     533              : url_base&
     534           37 : url_base::
     535              : set_password(core::string_view s)
     536              : {
     537           37 :     op_t op(*this, &s);
     538           37 :     encoding_opts opt;
     539           37 :     auto const n = encoded_size(
     540              :         s, detail::password_chars, opt);
     541           37 :     auto dest = set_password_impl(n, op);
     542           37 :     encode_unsafe(
     543              :         dest,
     544              :         n,
     545              :         s,
     546              :         detail::password_chars,
     547              :         opt);
     548           37 :     impl_.decoded_[id_pass] =
     549           37 :         detail::to_size_type(s.size());
     550           37 :     return *this;
     551           37 : }
     552              : 
     553              : inline
     554              : url_base&
     555           39 : url_base::
     556              : set_encoded_password(
     557              :     pct_string_view s)
     558              : {
     559           39 :     op_t op(*this, &detail::ref(s));
     560              :     auto const n =
     561           39 :         detail::re_encoded_size_unsafe(
     562              :             s,
     563              :             detail::password_chars);
     564           39 :     auto dest = set_password_impl(n, op);
     565           39 :     impl_.decoded_[id_pass] =
     566           78 :         detail::to_size_type(detail::re_encode_unsafe(
     567              :             dest,
     568           39 :             dest + n,
     569              :             s,
     570              :             detail::password_chars));
     571           39 :     BOOST_ASSERT(
     572              :         impl_.decoded_[id_pass] ==
     573              :             s.decoded_size());
     574           39 :     return *this;
     575           39 : }
     576              : 
     577              : inline
     578              : url_base&
     579           19 : url_base::
     580              : remove_password() noexcept
     581              : {
     582           19 :     auto const n = impl_.len(id_pass);
     583           19 :     if(n < 2)
     584           12 :         return *this; // no password
     585              : 
     586            7 :     op_t op(*this);
     587              :     // clear password, retain '@'
     588              :     auto dest =
     589            7 :         resize_impl(id_pass, 1, op);
     590            7 :     dest[0] = '@';
     591            7 :     impl_.decoded_[id_pass] = 0;
     592            7 :     return *this;
     593            7 : }
     594              : 
     595              : //------------------------------------------------
     596              : //
     597              : // Host
     598              : //
     599              : //------------------------------------------------
     600              : /*
     601              : host_type       host_type()                 // ipv4, ipv6, ipvfuture, name
     602              : 
     603              : std::string     host()                      // return encoded_host().decode()
     604              : pct_string_view encoded_host()              // return host part, as-is
     605              : std::string     host_address()              // return encoded_host_address().decode()
     606              : pct_string_view encoded_host_address()      // ipv4, ipv6, ipvfut, or encoded name, no brackets
     607              : 
     608              : ipv4_address    host_ipv4_address()         // return ipv4_address or {}
     609              : ipv6_address    host_ipv6_address()         // return ipv6_address or {}
     610              : core::string_view     host_ipvfuture()            // return ipvfuture or {}
     611              : std::string     host_name()                 // return decoded name or ""
     612              : pct_string_view encoded_host_name()         // return encoded host name or ""
     613              : 
     614              : --------------------------------------------------
     615              : 
     616              : set_host( core::string_view )                     // set host part from plain text
     617              : set_encoded_host( pct_string_view )         // set host part from encoded text
     618              : set_host_address( core::string_view )             // set host from ipv4, ipv6, ipvfut, or plain reg-name string
     619              : set_encoded_host_address( pct_string_view ) // set host from ipv4, ipv6, ipvfut, or encoded reg-name string
     620              : 
     621              : set_host_ipv4( ipv4_address )               // set ipv4
     622              : set_host_ipv6( ipv6_address )               // set ipv6
     623              : set_host_ipvfuture( core::string_view )           // set ipvfuture
     624              : set_host_name( core::string_view )                // set name from plain
     625              : set_encoded_host_name( pct_string_view )    // set name from encoded
     626              : */
     627              : 
     628              : // set host part from plain text
     629              : inline
     630              : url_base&
     631           18 : url_base::
     632              : set_host(
     633              :     core::string_view s)
     634              : {
     635           18 :     if( s.size() > 2 &&
     636           21 :         s.front() == '[' &&
     637            3 :         s.back() == ']')
     638              :     {
     639              :         // IP-literal
     640            3 :         if (s[1] != 'v')
     641              :         {
     642              :             // IPv6-address
     643            2 :             auto innersv = s.substr(1, s.size() - 2);
     644            2 :             auto innerit = innersv.begin();
     645            2 :             auto endit = innersv.end();
     646            2 :             auto rv = grammar::parse(
     647              :                 innerit,
     648              :                 endit,
     649              :                 ipv6_address_rule);
     650            2 :             if(rv)
     651              :             {
     652            2 :                 if (innerit == endit)
     653              :                 {
     654            1 :                     set_host_ipv6_and_encoded_zone_id(*rv, {});
     655            2 :                     return *this;
     656              :                 }
     657              :                 // IPv6addrz: https://datatracker.ietf.org/doc/html/rfc6874
     658            1 :                 auto chars_left = endit - innerit;
     659            2 :                 if (chars_left >= 2 &&
     660            1 :                     *innerit++ == '%')
     661              :                 {
     662            1 :                     core::string_view zone_id_str = {&*innerit, std::size_t(chars_left - 1)};
     663            1 :                     set_host_ipv6_and_zone_id(*rv, zone_id_str);
     664            1 :                     return *this;
     665              :                 }
     666              :             }
     667              :         }
     668              :         else
     669              :         {
     670              :             // IPvFuture
     671            1 :             auto rv = grammar::parse(
     672            1 :                 s.substr(1, s.size() - 2),
     673              :                 detail::ipvfuture_rule);
     674            1 :             if(rv)
     675            1 :                 return set_host_ipvfuture(rv->str);
     676              :         }
     677              :     }
     678           15 :     else if(s.size() >= 7) // "0.0.0.0"
     679              :     {
     680              :         // IPv4-address
     681           13 :         auto rv = parse_ipv4_address(s);
     682           13 :         if(rv)
     683            4 :             return set_host_ipv4(*rv);
     684              :     }
     685              : 
     686              :     // reg-name
     687           11 :     op_t op(*this, &s);
     688           11 :     encoding_opts opt;
     689           11 :     auto const n = encoded_size(
     690              :         s, detail::host_chars, opt);
     691           11 :     auto dest = set_host_impl(n, op);
     692           11 :     encode(
     693              :         dest,
     694           11 :         impl_.get(id_path).data() - dest,
     695              :         s,
     696              :         detail::host_chars,
     697              :         opt);
     698           11 :     impl_.decoded_[id_host] =
     699           11 :         detail::to_size_type(s.size());
     700           11 :     impl_.host_type_ =
     701              :         urls::host_type::name;
     702           11 :     return *this;
     703           11 : }
     704              : 
     705              : // set host part from encoded text
     706              : inline
     707              : url_base&
     708          118 : url_base::
     709              : set_encoded_host(
     710              :     pct_string_view s)
     711              : {
     712          118 :     if( s.size() > 2 &&
     713          135 :         s.front() == '[' &&
     714           17 :         s.back() == ']')
     715              :     {
     716              :         // IP-literal
     717           17 :         if (s[1] != 'v')
     718              :         {
     719              :             // IPv6-address
     720           16 :             auto innersv = s.substr(1, s.size() - 2);
     721           16 :             auto innerit = innersv.begin();
     722           16 :             auto endit = innersv.end();
     723           16 :             auto rv = grammar::parse(
     724              :                 innerit,
     725              :                 endit,
     726              :                 ipv6_address_rule);
     727           16 :             if(rv)
     728              :             {
     729            8 :                 if (innerit == endit)
     730              :                 {
     731            5 :                     set_host_ipv6_and_encoded_zone_id(*rv, {});
     732            6 :                     return *this;
     733              :                 }
     734              :                 // IPv6addrz: https://datatracker.ietf.org/doc/html/rfc6874
     735            3 :                 auto chars_left = endit - innerit;
     736            4 :                 if (chars_left >= 3 &&
     737            1 :                     *innerit++ == '%' &&
     738            5 :                     *innerit++ == '2' &&
     739            1 :                     *innerit++ == '5')
     740              :                 {
     741            1 :                     auto const nz = std::size_t(chars_left - 3);
     742            1 :                     core::string_view zone_id_str = {&*innerit, std::size_t(chars_left - 3)};
     743            1 :                     std::size_t dnz = detail::decode_bytes_unsafe(zone_id_str);
     744            1 :                     pct_string_view zone_id_pct = make_pct_string_view_unsafe(innerit, nz, dnz);
     745            1 :                     set_host_ipv6_and_encoded_zone_id(*rv, zone_id_pct);
     746            1 :                     return *this;
     747              :                 }
     748              :             }
     749              :         }
     750              :         else
     751              :         {
     752              :             // IPvFuture
     753            1 :             auto rv = grammar::parse(
     754            1 :                 s.substr(1, s.size() - 2),
     755              :                     detail::ipvfuture_rule);
     756            1 :             if(rv)
     757            1 :                 return set_host_ipvfuture(rv->str);
     758              :         }
     759              :     }
     760          101 :     else if(s.size() >= 7) // "0.0.0.0"
     761              :     {
     762              :         // IPv4-address
     763           57 :         auto rv = parse_ipv4_address(s);
     764           57 :         if(rv)
     765            5 :             return set_host_ipv4(*rv);
     766              :     }
     767              : 
     768              :     // reg-name
     769          106 :     op_t op(*this, &detail::ref(s));
     770          106 :     auto const n = detail::re_encoded_size_unsafe(
     771              :         s, detail::host_chars);
     772          106 :     auto dest = set_host_impl(n, op);
     773          106 :     impl_.decoded_[id_host] =
     774          212 :         detail::to_size_type(detail::re_encode_unsafe(
     775              :             dest,
     776          106 :             impl_.get(id_path).data(),
     777              :             s,
     778              :             detail::host_chars));
     779          106 :     BOOST_ASSERT(impl_.decoded_[id_host] ==
     780              :         s.decoded_size());
     781          106 :     impl_.host_type_ =
     782              :         urls::host_type::name;
     783          106 :     return *this;
     784          106 : }
     785              : 
     786              : inline
     787              : url_base&
     788           10 : url_base::
     789              : set_host_address(
     790              :     core::string_view s)
     791              : {
     792           10 :     if (!s.empty())
     793              :     {
     794              :         // IP-literal
     795            9 :         if (s[0] != 'v')
     796              :         {
     797              :             // IPv6-address
     798            8 :             auto innerit = s.begin();
     799            8 :             auto endit = s.end();
     800            8 :             auto rv = grammar::parse(
     801              :                 innerit,
     802              :                 endit,
     803              :                 ipv6_address_rule);
     804            8 :             if(rv)
     805              :             {
     806            2 :                 if (innerit == endit)
     807              :                 {
     808            1 :                     set_host_ipv6_and_encoded_zone_id(*rv, {});
     809            2 :                     return *this;
     810              :                 }
     811              :                 // IPv6addrz: https://datatracker.ietf.org/doc/html/rfc6874
     812            1 :                 auto chars_left = endit - innerit;
     813            2 :                 if (chars_left >= 2 &&
     814            1 :                     *innerit++ == '%')
     815              :                 {
     816            1 :                     core::string_view zone_id_str = {&*innerit, std::size_t(chars_left - 1)};
     817            1 :                     set_host_ipv6_and_zone_id(*rv, zone_id_str);
     818            1 :                     return *this;
     819              :                 }
     820              :             }
     821              :         }
     822              : 
     823              :         // IPvFuture
     824            7 :         auto rv = grammar::parse(s, detail::ipvfuture_rule);
     825            7 :         if(rv)
     826            1 :             return set_host_ipvfuture(rv->str);
     827              : 
     828            6 :         if(s.size() >= 7) // "0.0.0.0"
     829              :         {
     830              :             // IPv4-address
     831            5 :             auto rv2 = parse_ipv4_address(s);
     832            5 :             if(rv2)
     833            2 :                 return set_host_ipv4(*rv2);
     834              :         }
     835              :     }
     836              : 
     837              :     // reg-name
     838            5 :     op_t op(*this, &s);
     839            5 :     encoding_opts opt;
     840            5 :     auto const n = encoded_size(
     841              :         s, detail::host_chars, opt);
     842            5 :     auto dest = set_host_impl(n, op);
     843            5 :     encode(
     844              :         dest,
     845            5 :         impl_.get(id_path).data() - dest,
     846              :         s,
     847              :         detail::host_chars,
     848              :         opt);
     849            5 :     impl_.decoded_[id_host] =
     850            5 :         detail::to_size_type(s.size());
     851            5 :     impl_.host_type_ =
     852              :         urls::host_type::name;
     853            5 :     return *this;
     854            5 : }
     855              : 
     856              : inline
     857              : url_base&
     858            8 : url_base::
     859              : set_encoded_host_address(
     860              :     pct_string_view s)
     861              : {
     862            8 :     if( !s.empty() )
     863              :     {
     864              :         // IP-literal
     865            7 :         if (s[0] != 'v')
     866              :         {
     867              :             // IPv6-address
     868            6 :             auto innerit = s.begin();
     869            6 :             auto endit = s.end();
     870            6 :             auto rv = grammar::parse(
     871              :                 innerit,
     872              :                 endit,
     873              :                 ipv6_address_rule);
     874            6 :             if(rv)
     875              :             {
     876            2 :                 if (innerit == endit)
     877              :                 {
     878            1 :                     set_host_ipv6_and_encoded_zone_id(*rv, {});
     879            3 :                     return *this;
     880              :                 }
     881              :                 // IPv6addrz: https://datatracker.ietf.org/doc/html/rfc6874
     882            1 :                 auto chars_left = endit - innerit;
     883            2 :                 if (chars_left >= 3 &&
     884            1 :                     *innerit++ == '%' &&
     885            3 :                     *innerit++ == '2' &&
     886            1 :                     *innerit++ == '5')
     887              :                 {
     888            1 :                     auto const nz = std::size_t(chars_left - 3);
     889            1 :                     core::string_view zone_id_str = {&*innerit, std::size_t(chars_left - 3)};
     890            1 :                     std::size_t dnz = detail::decode_bytes_unsafe(zone_id_str);
     891            1 :                     pct_string_view zone_id_pct = make_pct_string_view_unsafe(innerit, nz, dnz);
     892            1 :                     set_host_ipv6_and_encoded_zone_id(*rv, zone_id_pct);
     893            1 :                     return *this;
     894              :                 }
     895              :             }
     896              : 
     897            4 :             if(s.size() >= 7) // "0.0.0.0"
     898              :             {
     899              :                 // IPv4-address
     900            3 :                 auto rv2 = parse_ipv4_address(s);
     901            3 :                 if(rv2)
     902            1 :                    return set_host_ipv4(*rv2);
     903              :             }
     904              :         }
     905              :         else
     906              :         {
     907              :             // IPvFuture
     908            1 :             auto rv = grammar::parse(
     909              :                 s, detail::ipvfuture_rule);
     910            1 :             if(rv)
     911            1 :                 return set_host_ipvfuture(rv->str);
     912              :         }
     913              :     }
     914              : 
     915              :     // reg-name
     916            4 :     op_t op(*this, &detail::ref(s));
     917            4 :     auto const n = detail::re_encoded_size_unsafe(
     918              :         s, detail::host_chars);
     919            4 :     auto dest = set_host_impl(n, op);
     920            4 :     impl_.decoded_[id_host] =
     921            8 :         detail::to_size_type(detail::re_encode_unsafe(
     922              :             dest,
     923            4 :             impl_.get(id_path).data(),
     924              :             s,
     925              :             detail::host_chars));
     926            4 :     BOOST_ASSERT(impl_.decoded_[id_host] ==
     927              :         s.decoded_size());
     928            4 :     impl_.host_type_ =
     929              :         urls::host_type::name;
     930            4 :     return *this;
     931            4 : }
     932              : 
     933              : inline
     934              : url_base&
     935           18 : url_base::
     936              : set_host_ipv4(
     937              :     ipv4_address const& addr)
     938              : {
     939           18 :     op_t op(*this);
     940              :     char buf[urls::ipv4_address::max_str_len];
     941           18 :     auto s = addr.to_buffer(buf, sizeof(buf));
     942           18 :     auto dest = set_host_impl(s.size(), op);
     943           18 :     std::memcpy(dest, s.data(), s.size());
     944           18 :     impl_.decoded_[id_host] =
     945           18 :         detail::to_size_type(impl_.len(id_host));
     946           18 :     impl_.host_type_ = urls::host_type::ipv4;
     947           18 :     auto bytes = addr.to_bytes();
     948           18 :     std::memcpy(
     949           18 :         impl_.ip_addr_,
     950           18 :         bytes.data(),
     951              :         bytes.size());
     952           18 :     return *this;
     953           18 : }
     954              : 
     955              : inline
     956              : url_base&
     957            5 : url_base::
     958              : set_host_ipv6(
     959              :     ipv6_address const& addr)
     960              : {
     961            5 :     set_host_ipv6_and_encoded_zone_id(addr, encoded_zone_id());
     962            5 :     return *this;
     963              : }
     964              : 
     965              : inline
     966              : url_base&
     967            3 : url_base::
     968              : set_zone_id(core::string_view s)
     969              : {
     970            3 :     set_host_ipv6_and_zone_id(host_ipv6_address(), s);
     971            3 :     return *this;
     972              : }
     973              : 
     974              : inline
     975              : url_base&
     976            3 : url_base::
     977              : set_encoded_zone_id(pct_string_view s)
     978              : {
     979            3 :     set_host_ipv6_and_encoded_zone_id(host_ipv6_address(), s);
     980            3 :     return *this;
     981              : }
     982              : 
     983              : inline
     984              : void
     985            5 : url_base::
     986              : set_host_ipv6_and_zone_id(
     987              :     ipv6_address const& addr,
     988              :     core::string_view zone_id)
     989              : {
     990            5 :     op_t op(*this, &zone_id);
     991              :     char ipv6_str_buf[urls::ipv6_address::max_str_len];
     992            5 :     auto ipv6_str = addr.to_buffer(ipv6_str_buf, sizeof(ipv6_str_buf));
     993            5 :     bool const has_zone_id = !zone_id.empty();
     994            5 :     encoding_opts opt;
     995            5 :     auto const ipn = ipv6_str.size();
     996            5 :     auto const zn = encoded_size(zone_id, unreserved_chars, opt);
     997            5 :     auto const n = ipn + 2 + has_zone_id * (3 + zn);
     998            5 :     auto dest = set_host_impl(n, op);
     999            5 :     *dest++ = '[';
    1000            5 :     std::memcpy(dest, ipv6_str.data(), ipn);
    1001            5 :     dest += ipn;
    1002            5 :     if (has_zone_id)
    1003              :     {
    1004            5 :         *dest++ = '%';
    1005            5 :         *dest++ = '2';
    1006            5 :         *dest++ = '5';
    1007            5 :         encode(dest, zn, zone_id, unreserved_chars, opt);
    1008            5 :         dest += zn;
    1009              :     }
    1010            5 :     *dest++ = ']';
    1011              :     // ipn + |"["| + |"]"| + (has_zone_id ? |"%"| + zn : 0)
    1012           10 :     impl_.decoded_[id_host] = detail::to_size_type(
    1013            5 :         ipn + 2 + has_zone_id * (1 + zone_id.size()));
    1014            5 :     impl_.host_type_ = urls::host_type::ipv6;
    1015            5 :     auto bytes = addr.to_bytes();
    1016            5 :     std::memcpy(
    1017            5 :         impl_.ip_addr_,
    1018            5 :         bytes.data(),
    1019              :         bytes.size());
    1020            5 : }
    1021              : 
    1022              : inline
    1023              : void
    1024           18 : url_base::
    1025              : set_host_ipv6_and_encoded_zone_id(
    1026              :     ipv6_address const& addr,
    1027              :     pct_string_view zone_id)
    1028              : {
    1029           18 :     op_t op(*this, &detail::ref(zone_id));
    1030              :     char ipv6_str_buf[urls::ipv6_address::max_str_len];
    1031           18 :     auto ipv6_str = addr.to_buffer(ipv6_str_buf, sizeof(ipv6_str_buf));
    1032           18 :     bool const has_zone_id = !zone_id.empty();
    1033           18 :     auto const ipn = ipv6_str.size();
    1034           18 :     auto const zn = detail::re_encoded_size_unsafe(zone_id, unreserved_chars);
    1035           18 :     auto const n = ipn + 2 + has_zone_id * (3 + zn);
    1036           18 :     auto dest = set_host_impl(n, op);
    1037           18 :     *dest++ = '[';
    1038           18 :     std::memcpy(dest, ipv6_str.data(), ipn);
    1039           18 :     dest += ipn;
    1040           18 :     std::size_t dzn = 0;
    1041           18 :     if (has_zone_id)
    1042              :     {
    1043            6 :         *dest++ = '%';
    1044            6 :         *dest++ = '2';
    1045            6 :         *dest++ = '5';
    1046            6 :         dzn = detail::re_encode_unsafe(dest, dest + zn, zone_id, unreserved_chars);
    1047              :     }
    1048           18 :     *dest++ = ']';
    1049              :     // ipn + |"["| + |"]"| + (has_zone_id ? |"%"| + zn : 0)
    1050           36 :     impl_.decoded_[id_host] = detail::to_size_type(
    1051           18 :         ipn + 2 + has_zone_id * (1 + dzn));
    1052           18 :     impl_.host_type_ = urls::host_type::ipv6;
    1053           18 :     auto bytes = addr.to_bytes();
    1054           18 :     std::memcpy(
    1055           18 :         impl_.ip_addr_,
    1056           18 :         bytes.data(),
    1057              :         bytes.size());
    1058           18 : }
    1059              : 
    1060              : inline
    1061              : url_base&
    1062            7 : url_base::
    1063              : set_host_ipvfuture(
    1064              :     core::string_view s)
    1065              : {
    1066            7 :     op_t op(*this, &s);
    1067              :     // validate
    1068            8 :     grammar::parse(s,
    1069              :         detail::ipvfuture_rule
    1070            8 :             ).value(BOOST_URL_POS);
    1071            6 :     auto dest = set_host_impl(
    1072            6 :         s.size() + 2, op);
    1073            6 :     *dest++ = '[';
    1074            6 :     dest += s.copy(dest, s.size());
    1075            6 :     *dest = ']';
    1076            6 :     impl_.host_type_ =
    1077              :         urls::host_type::ipvfuture;
    1078            6 :     impl_.decoded_[id_host] =
    1079            6 :         detail::to_size_type(s.size() + 2);
    1080            6 :     return *this;
    1081            7 : }
    1082              : 
    1083              : inline
    1084              : url_base&
    1085            4 : url_base::
    1086              : set_host_name(
    1087              :     core::string_view s)
    1088              : {
    1089            4 :     bool is_ipv4 = false;
    1090            4 :     if(s.size() >= 7) // "0.0.0.0"
    1091              :     {
    1092              :         // IPv4-address
    1093            3 :         if(parse_ipv4_address(s).has_value())
    1094            1 :             is_ipv4 = true;
    1095              :     }
    1096            4 :     auto allowed = detail::host_chars;
    1097            4 :     if(is_ipv4)
    1098            1 :         allowed = allowed - '.';
    1099              : 
    1100            4 :     op_t op(*this, &s);
    1101            4 :     encoding_opts opt;
    1102            4 :     auto const n = encoded_size(
    1103              :         s, allowed, opt);
    1104            4 :     auto dest = set_host_impl(n, op);
    1105            4 :     encode_unsafe(
    1106              :         dest,
    1107              :         n,
    1108              :         s,
    1109              :         allowed,
    1110              :         opt);
    1111            4 :     impl_.host_type_ =
    1112              :         urls::host_type::name;
    1113            4 :     impl_.decoded_[id_host] =
    1114            4 :         detail::to_size_type(s.size());
    1115            4 :     return *this;
    1116            4 : }
    1117              : 
    1118              : inline
    1119              : url_base&
    1120            4 : url_base::
    1121              : set_encoded_host_name(
    1122              :     pct_string_view s)
    1123              : {
    1124            4 :     bool is_ipv4 = false;
    1125            4 :     if(s.size() >= 7) // "0.0.0.0"
    1126              :     {
    1127              :         // IPv4-address
    1128            3 :         if(parse_ipv4_address(s).has_value())
    1129            1 :             is_ipv4 = true;
    1130              :     }
    1131            4 :     auto allowed = detail::host_chars;
    1132            4 :     if(is_ipv4)
    1133            1 :         allowed = allowed - '.';
    1134              : 
    1135            4 :     op_t op(*this, &detail::ref(s));
    1136            4 :     auto const n = detail::re_encoded_size_unsafe(
    1137              :         s, allowed);
    1138            4 :     auto dest = set_host_impl(n, op);
    1139            4 :     impl_.decoded_[id_host] =
    1140            8 :         detail::to_size_type(detail::re_encode_unsafe(
    1141              :             dest,
    1142            4 :             dest + n,
    1143              :             s,
    1144              :             allowed));
    1145            4 :     BOOST_ASSERT(
    1146              :         impl_.decoded_[id_host] ==
    1147              :             s.decoded_size());
    1148            4 :     impl_.host_type_ =
    1149              :         urls::host_type::name;
    1150            4 :     return *this;
    1151            4 : }
    1152              : 
    1153              : //------------------------------------------------
    1154              : 
    1155              : inline
    1156              : url_base&
    1157           25 : url_base::
    1158              : set_port_number(
    1159              :     std::uint16_t n)
    1160              : {
    1161           25 :     op_t op(*this);
    1162              :     auto s =
    1163           25 :         detail::make_printed(n);
    1164           25 :     auto dest = set_port_impl(
    1165           25 :         s.string().size(), op);
    1166           25 :     std::memcpy(
    1167           25 :         dest, s.string().data(),
    1168           25 :             s.string().size());
    1169           25 :     impl_.port_number_ = n;
    1170           25 :     return *this;
    1171           25 : }
    1172              : 
    1173              : inline
    1174              : url_base&
    1175           90 : url_base::
    1176              : set_port(
    1177              :     core::string_view s)
    1178              : {
    1179           90 :     op_t op(*this, &s);
    1180          109 :     auto t = grammar::parse(s,
    1181           19 :         detail::port_rule{}
    1182           90 :             ).value(BOOST_URL_POS);
    1183              :     auto dest =
    1184           71 :         set_port_impl(t.str.size(), op);
    1185           71 :     std::memcpy(dest,
    1186           71 :         t.str.data(), t.str.size());
    1187           71 :     if(t.has_number)
    1188           35 :         impl_.port_number_ = t.number;
    1189              :     else
    1190           36 :         impl_.port_number_ = 0;
    1191           71 :     return *this;
    1192           90 : }
    1193              : 
    1194              : inline
    1195              : url_base&
    1196           25 : url_base::
    1197              : remove_port() noexcept
    1198              : {
    1199           25 :     op_t op(*this);
    1200           25 :     resize_impl(id_port, 0, op);
    1201           25 :     impl_.port_number_ = 0;
    1202           50 :     return *this;
    1203           25 : }
    1204              : 
    1205              : //------------------------------------------------
    1206              : //
    1207              : // Compound Fields
    1208              : //
    1209              : //------------------------------------------------
    1210              : 
    1211              : inline
    1212              : url_base&
    1213           14 : url_base::
    1214              : remove_origin()
    1215              : {
    1216              :     // these two calls perform 2 memmoves instead of 1
    1217           14 :     remove_authority();
    1218           14 :     remove_scheme();
    1219           14 :     return *this;
    1220              : }
    1221              : 
    1222              : //------------------------------------------------
    1223              : //
    1224              : // Path
    1225              : //
    1226              : //------------------------------------------------
    1227              : 
    1228              : inline
    1229              : bool
    1230           50 : url_base::
    1231              : set_path_absolute(
    1232              :     bool absolute)
    1233              : {
    1234           50 :     op_t op(*this);
    1235              : 
    1236              :     // check if path empty
    1237           50 :     if(impl_.len(id_path) == 0)
    1238              :     {
    1239           38 :         if(! absolute)
    1240              :         {
    1241              :             // already not absolute
    1242           32 :             return true;
    1243              :         }
    1244              : 
    1245              :         // add '/'
    1246            6 :         auto dest = resize_impl(
    1247              :             id_path, 1, op);
    1248            6 :         *dest = '/';
    1249            6 :         ++impl_.decoded_[id_path];
    1250            6 :         return true;
    1251              :     }
    1252              : 
    1253              :     // check if path absolute
    1254           12 :     if(s_[impl_.offset(id_path)] == '/')
    1255              :     {
    1256            9 :         if(absolute)
    1257              :         {
    1258              :             // already absolute
    1259            2 :             return true;
    1260              :         }
    1261              : 
    1262           11 :         if( has_authority() &&
    1263            4 :             impl_.len(id_path) > 1)
    1264              :         {
    1265              :             // can't do it, paths are always
    1266              :             // absolute when authority present!
    1267            2 :             return false;
    1268              :         }
    1269              : 
    1270            5 :         auto p = encoded_path();
    1271            5 :         auto pos = p.find_first_of(":/", 1);
    1272            6 :         if (pos != core::string_view::npos &&
    1273            1 :             p[pos] == ':')
    1274              :         {
    1275              :             // prepend with .
    1276            1 :             auto n = impl_.len(id_path);
    1277            1 :             resize_impl(id_path, n + 1, op);
    1278            1 :             std::memmove(
    1279            2 :                 s_ + impl_.offset(id_path) + 1,
    1280            1 :                 s_ + impl_.offset(id_path), n);
    1281            1 :             *(s_ + impl_.offset(id_path)) = '.';
    1282            1 :             ++impl_.decoded_[id_path];
    1283            1 :             return true;
    1284              :         }
    1285              : 
    1286              :         // remove '/'
    1287            4 :         auto n = impl_.len(id_port);
    1288            4 :         impl_.split(id_port, n + 1);
    1289            4 :         resize_impl(id_port, n, op);
    1290            4 :         --impl_.decoded_[id_path];
    1291            4 :         return true;
    1292              :     }
    1293              : 
    1294            3 :     if(! absolute)
    1295              :     {
    1296              :         // already not absolute
    1297            1 :         return true;
    1298              :     }
    1299              : 
    1300              :     // add '/'
    1301            2 :     auto n = impl_.len(id_port);
    1302            2 :     auto dest = resize_impl(
    1303            2 :         id_port, n + 1, op) + n;
    1304            2 :     impl_.split(id_port, n);
    1305            2 :     *dest = '/';
    1306            2 :     ++impl_.decoded_[id_path];
    1307            2 :     return true;
    1308           50 : }
    1309              : 
    1310              : inline
    1311              : url_base&
    1312           27 : url_base::
    1313              : set_path(
    1314              :     core::string_view s)
    1315              : {
    1316           27 :     op_t op(*this, &s);
    1317           27 :     encoding_opts opt;
    1318              : 
    1319              : //------------------------------------------------
    1320              : //
    1321              : //  Calculate encoded size
    1322              : //
    1323              : // - "/"s are not encoded
    1324              : // - "%2F"s are not encoded
    1325              : //
    1326              : // - reserved path chars are re-encoded
    1327              : // - colons in first segment might need to be re-encoded
    1328              : // - the path might need to receive a prefix
    1329           27 :     auto const n = encoded_size(
    1330              :         s, detail::path_chars, opt);
    1331           27 :     std::size_t n_reencode_colons = 0;
    1332           27 :     core::string_view first_seg;
    1333           27 :     if (!has_scheme() &&
    1334           42 :         !has_authority() &&
    1335           15 :         !s.starts_with('/'))
    1336              :     {
    1337              :         // the first segment with unencoded colons would look
    1338              :         // like the scheme
    1339            6 :         first_seg = detail::to_sv(s);
    1340            6 :         std::size_t p = s.find('/');
    1341            6 :         if (p != core::string_view::npos)
    1342            2 :             first_seg = s.substr(0, p);
    1343            6 :         n_reencode_colons = std::count(
    1344           12 :             first_seg.begin(), first_seg.end(), ':');
    1345              :     }
    1346              :     // the authority can only be followed by an empty or relative path
    1347              :     // if we have an authority and the path is a non-empty relative path, we
    1348              :     // add the "/" prefix to make it valid.
    1349              :     bool make_absolute =
    1350           27 :         has_authority() &&
    1351           33 :         !s.starts_with('/') &&
    1352            6 :         !s.empty();
    1353              :     // a path starting with "//" might look like the authority.
    1354              :     // we add a "/." prefix to prevent that
    1355              :     bool add_dot_segment =
    1356           50 :         !make_absolute &&
    1357           23 :         s.starts_with("//");
    1358              : 
    1359              : //------------------------------------------------
    1360              : //
    1361              : //  Re-encode data
    1362              : //
    1363           54 :     auto dest = set_path_impl(
    1364           27 :         n + make_absolute + 2 * n_reencode_colons + 2 * add_dot_segment, op);
    1365           27 :     impl_.decoded_[id_path] = 0;
    1366           27 :     if (!dest)
    1367              :     {
    1368            3 :         impl_.nseg_ = 0;
    1369            3 :         return *this;
    1370              :     }
    1371           24 :     if (make_absolute)
    1372              :     {
    1373            4 :         *dest++ = '/';
    1374            4 :         impl_.decoded_[id_path] += 1;
    1375              :     }
    1376           20 :     else if (add_dot_segment)
    1377              :     {
    1378            1 :         *dest++ = '/';
    1379            1 :         *dest++ = '.';
    1380            1 :         impl_.decoded_[id_path] += 2;
    1381              :     }
    1382           48 :     dest += encode_unsafe(
    1383              :         dest,
    1384           24 :         impl_.get(id_query).data() - dest,
    1385              :         first_seg,
    1386           24 :         detail::segment_chars - ':',
    1387              :         opt);
    1388           24 :     dest += encode_unsafe(
    1389              :         dest,
    1390           24 :         impl_.get(id_query).data() - dest,
    1391              :         s.substr(first_seg.size()),
    1392              :         detail::path_chars,
    1393              :         opt);
    1394           24 :     impl_.decoded_[id_path] +=
    1395           24 :         detail::to_size_type(s.size());
    1396           24 :     BOOST_ASSERT(!dest || dest == impl_.get(id_query).data());
    1397           24 :     BOOST_ASSERT(
    1398              :         impl_.decoded_[id_path] ==
    1399              :         s.size() + make_absolute + 2 * add_dot_segment);
    1400              : 
    1401              : //------------------------------------------------
    1402              : //
    1403              : //  Update path parameters
    1404              : //
    1405              :     // get the encoded_path with the replacements we applied
    1406           24 :     if (s == "/")
    1407              :     {
    1408              :         // "/" maps to sequence {}
    1409            3 :         impl_.nseg_ = 0;
    1410              :     }
    1411           21 :     else if (!s.empty())
    1412              :     {
    1413           17 :         if (s.starts_with("/./"))
    1414            1 :             s = s.substr(2);
    1415              :         // count segments as number of '/'s + 1
    1416           34 :         impl_.nseg_ = detail::to_size_type(
    1417           17 :             std::count(
    1418           34 :                 s.begin() + 1, s.end(), '/') + 1);
    1419              :     }
    1420              :     else
    1421              :     {
    1422              :         // an empty relative path maps to sequence {}
    1423            4 :         impl_.nseg_ = 0;
    1424              :     }
    1425              : 
    1426           24 :     check_invariants();
    1427           24 :     return *this;
    1428           27 : }
    1429              : 
    1430              : inline
    1431              : url_base&
    1432          171 : url_base::
    1433              : set_encoded_path(
    1434              :     pct_string_view s)
    1435              : {
    1436          171 :     op_t op(*this, &detail::ref(s));
    1437              : 
    1438              : //------------------------------------------------
    1439              : //
    1440              : //  Calculate re-encoded output size
    1441              : //
    1442              : // - reserved path chars are re-encoded
    1443              : // - colons in first segment might need to be re-encoded
    1444              : // - the path might need to receive a prefix
    1445          171 :     auto const n = detail::re_encoded_size_unsafe(
    1446              :         s, detail::path_chars);
    1447          171 :     std::size_t n_reencode_colons = 0;
    1448          171 :     core::string_view first_seg;
    1449          171 :     if (!has_scheme() &&
    1450          189 :         !has_authority() &&
    1451           18 :         !s.starts_with('/'))
    1452              :     {
    1453              :         // the first segment with unencoded colons would look
    1454              :         // like the scheme
    1455           11 :         first_seg = detail::to_sv(s);
    1456           11 :         std::size_t p = s.find('/');
    1457           11 :         if (p != core::string_view::npos)
    1458            6 :             first_seg = s.substr(0, p);
    1459           11 :         n_reencode_colons = std::count(
    1460           22 :             first_seg.begin(), first_seg.end(), ':');
    1461              :     }
    1462              :     // the authority can only be followed by an empty or relative path
    1463              :     // if we have an authority and the path is a non-empty relative path, we
    1464              :     // add the "/" prefix to make it valid.
    1465              :     bool make_absolute =
    1466          171 :         has_authority() &&
    1467          219 :         !s.starts_with('/') &&
    1468           48 :         !s.empty();
    1469              :     // a path starting with "//" might look like the authority
    1470              :     // we add a "/." prefix to prevent that
    1471              :     bool add_dot_segment =
    1472          329 :         !make_absolute &&
    1473          211 :         !has_authority() &&
    1474           40 :         s.starts_with("//");
    1475              : 
    1476              : //------------------------------------------------
    1477              : //
    1478              : //  Re-encode data
    1479              : //
    1480          342 :     auto dest = set_path_impl(
    1481          171 :         n + make_absolute + 2 * n_reencode_colons + 2 * add_dot_segment, op);
    1482          171 :     impl_.decoded_[id_path] = 0;
    1483          171 :     if (!dest)
    1484              :     {
    1485            1 :         impl_.nseg_ = 0;
    1486            1 :         return *this;
    1487              :     }
    1488          170 :     if (make_absolute)
    1489              :     {
    1490           13 :         *dest++ = '/';
    1491           13 :         impl_.decoded_[id_path] += 1;
    1492              :     }
    1493          157 :     else if (add_dot_segment)
    1494              :     {
    1495            5 :         *dest++ = '/';
    1496            5 :         *dest++ = '.';
    1497            5 :         impl_.decoded_[id_path] += 2;
    1498              :     }
    1499          170 :     impl_.decoded_[id_path] +=
    1500          170 :         detail::to_size_type(detail::re_encode_unsafe(
    1501              :             dest,
    1502          170 :             impl_.get(id_query).data(),
    1503              :             first_seg,
    1504          170 :             detail::segment_chars - ':'));
    1505          170 :     impl_.decoded_[id_path] +=
    1506          340 :         detail::to_size_type(detail::re_encode_unsafe(
    1507              :             dest,
    1508          170 :             impl_.get(id_query).data(),
    1509              :             s.substr(first_seg.size()),
    1510              :             detail::path_chars));
    1511          170 :     BOOST_ASSERT(dest == impl_.get(id_query).data());
    1512          170 :     BOOST_ASSERT(
    1513              :         impl_.decoded_[id_path] ==
    1514              :         s.decoded_size() + make_absolute + 2 * add_dot_segment);
    1515              : 
    1516              : //------------------------------------------------
    1517              : //
    1518              : //  Update path parameters
    1519              : //
    1520              :     // get the encoded_path with the replacements we applied
    1521          170 :     if (s == "/")
    1522              :     {
    1523              :         // "/" maps to sequence {}
    1524           16 :         impl_.nseg_ = 0;
    1525              :     }
    1526          154 :     else if (!s.empty())
    1527              :     {
    1528          117 :         if (s.starts_with("/./"))
    1529            7 :             s = s.substr(2);
    1530              :         // count segments as number of '/'s + 1
    1531          234 :         impl_.nseg_ = detail::to_size_type(
    1532          117 :             std::count(
    1533          234 :                 s.begin() + 1, s.end(), '/') + 1);
    1534              :     }
    1535              :     else
    1536              :     {
    1537              :         // an empty relative path maps to sequence {}
    1538           37 :         impl_.nseg_ = 0;
    1539              :     }
    1540              : 
    1541          170 :     check_invariants();
    1542          170 :     return *this;
    1543          171 : }
    1544              : 
    1545              : inline
    1546              : segments_ref
    1547          267 : url_base::
    1548              : segments() noexcept
    1549              : {
    1550          267 :     return {*this};
    1551              : }
    1552              : 
    1553              : inline
    1554              : segments_encoded_ref
    1555          467 : url_base::
    1556              : encoded_segments() noexcept
    1557              : {
    1558          467 :     return {*this};
    1559              : }
    1560              : 
    1561              : //------------------------------------------------
    1562              : //
    1563              : // Query
    1564              : //
    1565              : //------------------------------------------------
    1566              : 
    1567              : inline
    1568              : url_base&
    1569           11 : url_base::
    1570              : set_query(
    1571              :     core::string_view s)
    1572              : {
    1573           11 :     edit_params(
    1574           11 :         detail::params_iter_impl(impl_),
    1575           11 :         detail::params_iter_impl(impl_, 0),
    1576           22 :         detail::query_string_iter(s, true));
    1577           11 :     return *this;
    1578              : }
    1579              : 
    1580              : inline
    1581              : url_base&
    1582           50 : url_base::
    1583              : set_encoded_query(
    1584              :     pct_string_view s)
    1585              : {
    1586           50 :     op_t op(*this);
    1587           50 :     std::size_t n = 0;      // encoded size
    1588           50 :     std::size_t nparam = 1; // param count
    1589           50 :     auto const end = s.end();
    1590           50 :     auto p = s.begin();
    1591              : 
    1592              :     // measure
    1593          226 :     while(p != end)
    1594              :     {
    1595          176 :         if(*p == '&')
    1596              :         {
    1597            3 :             ++p;
    1598            3 :             ++n;
    1599            3 :             ++nparam;
    1600              :         }
    1601          173 :         else if(*p != '%')
    1602              :         {
    1603          165 :             if(detail::query_chars(*p))
    1604          162 :                 n += 1; // allowed
    1605              :             else
    1606            3 :                 n += 3; // escaped
    1607          165 :             ++p;
    1608              :         }
    1609              :         else
    1610              :         {
    1611              :             // escape
    1612            8 :             n += 3;
    1613            8 :             p += 3;
    1614              :         }
    1615              :     }
    1616              : 
    1617              :     // resize
    1618           50 :     auto dest = resize_impl(
    1619           50 :         id_query, n + 1, op);
    1620           50 :     *dest++ = '?';
    1621              : 
    1622              :     // encode
    1623           50 :     impl_.decoded_[id_query] =
    1624          100 :         detail::to_size_type(detail::re_encode_unsafe(
    1625              :             dest,
    1626           50 :             dest + n,
    1627              :             s,
    1628              :             detail::query_chars));
    1629           50 :     BOOST_ASSERT(
    1630              :         impl_.decoded_[id_query] ==
    1631              :             s.decoded_size());
    1632           50 :     impl_.nparam_ =
    1633           50 :         detail::to_size_type(nparam);
    1634           50 :     return *this;
    1635           50 : }
    1636              : 
    1637              : inline
    1638              : params_ref
    1639           96 : url_base::
    1640              : params() noexcept
    1641              : {
    1642              :     return params_ref(
    1643              :         *this,
    1644              :         encoding_opts{
    1645           96 :             true, false, false});
    1646              : }
    1647              : 
    1648              : inline
    1649              : params_ref
    1650            4 : url_base::
    1651              : params(encoding_opts opt) noexcept
    1652              : {
    1653            4 :     return {*this, opt};
    1654              : }
    1655              : 
    1656              : inline
    1657              : params_encoded_ref
    1658           77 : url_base::
    1659              : encoded_params() noexcept
    1660              : {
    1661           77 :     return {*this};
    1662              : }
    1663              : 
    1664              : inline
    1665              : url_base&
    1666            1 : url_base::
    1667              : set_params(
    1668              :     std::initializer_list<param_view> ps,
    1669              :     encoding_opts opts) noexcept
    1670              : {
    1671            1 :     params(opts).assign(ps);
    1672            1 :     return *this;
    1673              : }
    1674              : 
    1675              : inline
    1676              : url_base&
    1677            1 : url_base::
    1678              : set_encoded_params( std::initializer_list< param_pct_view > ps ) noexcept
    1679              : {
    1680            1 :     encoded_params().assign(ps);
    1681            1 :     return *this;
    1682              : }
    1683              : 
    1684              : inline
    1685              : url_base&
    1686          226 : url_base::
    1687              : remove_query() noexcept
    1688              : {
    1689          226 :     op_t op(*this);
    1690          226 :     resize_impl(id_query, 0, op);
    1691          226 :     impl_.nparam_ = 0;
    1692          226 :     impl_.decoded_[id_query] = 0;
    1693          452 :     return *this;
    1694          226 : }
    1695              : 
    1696              : //------------------------------------------------
    1697              : //
    1698              : // Fragment
    1699              : //
    1700              : //------------------------------------------------
    1701              : 
    1702              : inline
    1703              : url_base&
    1704          234 : url_base::
    1705              : remove_fragment() noexcept
    1706              : {
    1707          234 :     op_t op(*this);
    1708          234 :     resize_impl(id_frag, 0, op);
    1709          234 :     impl_.decoded_[id_frag] = 0;
    1710          468 :     return *this;
    1711          234 : }
    1712              : 
    1713              : inline
    1714              : url_base&
    1715            8 : url_base::
    1716              : set_fragment(core::string_view s)
    1717              : {
    1718            8 :     op_t op(*this, &s);
    1719            8 :     encoding_opts opt;
    1720            8 :     auto const n = encoded_size(
    1721              :         s,
    1722              :         detail::fragment_chars,
    1723              :         opt);
    1724            8 :     auto dest = resize_impl(
    1725              :         id_frag, n + 1, op);
    1726            8 :     *dest++ = '#';
    1727            8 :     encode_unsafe(
    1728              :         dest,
    1729              :         n,
    1730              :         s,
    1731              :         detail::fragment_chars,
    1732              :         opt);
    1733            8 :     impl_.decoded_[id_frag] =
    1734            8 :         detail::to_size_type(s.size());
    1735            8 :     return *this;
    1736            8 : }
    1737              : 
    1738              : inline
    1739              : url_base&
    1740           57 : url_base::
    1741              : set_encoded_fragment(
    1742              :     pct_string_view s)
    1743              : {
    1744           57 :     op_t op(*this, &detail::ref(s));
    1745              :     auto const n =
    1746           57 :         detail::re_encoded_size_unsafe(
    1747              :             s,
    1748              :             detail::fragment_chars);
    1749           57 :     auto dest = resize_impl(
    1750           57 :         id_frag, n + 1, op);
    1751           57 :     *dest++ = '#';
    1752           57 :     impl_.decoded_[id_frag] =
    1753          114 :         detail::to_size_type(detail::re_encode_unsafe(
    1754              :             dest,
    1755           57 :             dest + n,
    1756              :             s,
    1757              :             detail::fragment_chars));
    1758           57 :     BOOST_ASSERT(
    1759              :         impl_.decoded_[id_frag] ==
    1760              :             s.decoded_size());
    1761           57 :     return *this;
    1762           57 : }
    1763              : 
    1764              : //------------------------------------------------
    1765              : //
    1766              : // Resolution
    1767              : //
    1768              : //------------------------------------------------
    1769              : 
    1770              : inline
    1771              : system::result<void>
    1772          466 : url_base::
    1773              : resolve(
    1774              :     url_view_base const& ref)
    1775              : {
    1776          469 :     if (this == &ref &&
    1777            3 :         has_scheme())
    1778              :     {
    1779            2 :         normalize_path();
    1780            2 :         return {};
    1781              :     }
    1782              : 
    1783          464 :     if(! has_scheme())
    1784              :     {
    1785            2 :         BOOST_URL_RETURN_EC(error::not_a_base);
    1786              :     }
    1787              : 
    1788          462 :     op_t op(*this);
    1789              : 
    1790              :     //
    1791              :     // 5.2.2. Transform References
    1792              :     // https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2
    1793              :     //
    1794              : 
    1795          723 :     if( ref.has_scheme() &&
    1796          261 :         ref.scheme() != scheme())
    1797              :     {
    1798          198 :         reserve_impl(ref.size(), op);
    1799          198 :         copy(ref);
    1800          198 :         normalize_path();
    1801          198 :         return {};
    1802              :     }
    1803          264 :     if(ref.has_authority())
    1804              :     {
    1805           70 :         reserve_impl(
    1806           70 :             impl_.offset(id_user) + ref.size(), op);
    1807           70 :         set_encoded_authority(
    1808              :             ref.encoded_authority());
    1809           70 :         set_encoded_path(
    1810              :             ref.encoded_path());
    1811           70 :         if (ref.encoded_path().empty())
    1812           31 :             set_path_absolute(false);
    1813              :         else
    1814           39 :             normalize_path();
    1815           70 :         if(ref.has_query())
    1816            5 :             set_encoded_query(
    1817              :                 ref.encoded_query());
    1818              :         else
    1819           65 :             remove_query();
    1820           70 :         if(ref.has_fragment())
    1821            5 :             set_encoded_fragment(
    1822              :                 ref.encoded_fragment());
    1823              :         else
    1824           65 :             remove_fragment();
    1825           70 :         return {};
    1826              :     }
    1827          194 :     if(ref.encoded_path().empty())
    1828              :     {
    1829           33 :         reserve_impl(
    1830           33 :             impl_.offset(id_query) +
    1831           33 :             ref.size(), op);
    1832           33 :         normalize_path();
    1833           33 :         if(ref.has_query())
    1834              :         {
    1835           11 :             set_encoded_query(
    1836              :                 ref.encoded_query());
    1837              :         }
    1838           33 :         if(ref.has_fragment())
    1839           18 :             set_encoded_fragment(
    1840              :                 ref.encoded_fragment());
    1841              :         else
    1842           15 :             remove_fragment();
    1843           33 :         return {};
    1844              :     }
    1845          161 :     if(ref.is_path_absolute())
    1846              :     {
    1847           38 :         reserve_impl(
    1848           38 :             impl_.offset(id_path) +
    1849           38 :                 ref.size(), op);
    1850           38 :         set_encoded_path(
    1851              :             ref.encoded_path());
    1852           38 :         normalize_path();
    1853           38 :         if(ref.has_query())
    1854            3 :             set_encoded_query(
    1855              :                 ref.encoded_query());
    1856              :         else
    1857           35 :             remove_query();
    1858           38 :         if(ref.has_fragment())
    1859            2 :             set_encoded_fragment(
    1860              :                 ref.encoded_fragment());
    1861              :         else
    1862           36 :             remove_fragment();
    1863           38 :         return {};
    1864              :     }
    1865              :     // General case: ref is relative path
    1866          123 :     reserve_impl(
    1867          123 :         impl_.offset(id_query) +
    1868          123 :         ref.size(), op);
    1869              :     // 5.2.3. Merge Paths
    1870          123 :     auto es = encoded_segments();
    1871          123 :     if(es.size() > 0)
    1872              :     {
    1873          118 :         es.pop_back();
    1874              :     }
    1875          246 :     es.insert(es.end(),
    1876          123 :         ref.encoded_segments().begin(),
    1877          123 :         ref.encoded_segments().end());
    1878          123 :     normalize_path();
    1879          123 :     if(ref.has_query())
    1880           10 :         set_encoded_query(
    1881              :             ref.encoded_query());
    1882              :     else
    1883          113 :         remove_query();
    1884          123 :     if(ref.has_fragment())
    1885           10 :         set_encoded_fragment(
    1886              :             ref.encoded_fragment());
    1887              :     else
    1888          113 :         remove_fragment();
    1889          123 :     return {};
    1890          462 : }
    1891              : 
    1892              : //------------------------------------------------
    1893              : //
    1894              : // Normalization
    1895              : //
    1896              : //------------------------------------------------
    1897              : 
    1898              : template <
    1899              :     class AllowedCharset,
    1900              :     class IgnoredCharset>
    1901              : void
    1902         2001 : url_base::
    1903              : normalize_octets_impl(
    1904              :     int id,
    1905              :     AllowedCharset const& allowed,
    1906              :     IgnoredCharset const& ignored,
    1907              :     op_t& op) noexcept
    1908              : {
    1909         2001 :     char* it = s_ + impl_.offset(id);
    1910         2001 :     char* end = s_ + impl_.offset(id + 1);
    1911         2001 :     char d = 0;
    1912         2001 :     char* dest = it;
    1913        11038 :     while (it < end)
    1914              :     {
    1915         9037 :         if (*it != '%')
    1916              :         {
    1917         8908 :             *dest = *it;
    1918         8908 :             ++it;
    1919         8908 :             ++dest;
    1920         8908 :             continue;
    1921              :         }
    1922          129 :         BOOST_ASSERT(end - it >= 3);
    1923              : 
    1924              :         // decode unreserved octets
    1925          129 :         d = detail::decode_one(it + 1);
    1926          224 :         if (allowed(d) &&
    1927           95 :             !ignored(d))
    1928              :         {
    1929           87 :             *dest = d;
    1930           87 :             it += 3;
    1931           87 :             ++dest;
    1932           87 :             continue;
    1933              :         }
    1934              : 
    1935              :         // uppercase percent-encoding triplets
    1936           42 :         *dest++ = '%';
    1937           42 :         ++it;
    1938           42 :         *dest++ = grammar::to_upper(*it++);
    1939           42 :         *dest++ = grammar::to_upper(*it++);
    1940              :     }
    1941         2001 :     if (it != dest)
    1942              :     {
    1943           35 :         auto diff = it - dest;
    1944           35 :         auto n = impl_.len(id) - diff;
    1945           35 :         shrink_impl(id, n, op);
    1946           35 :         s_[size()] = '\0';
    1947              :     }
    1948         2001 : }
    1949              : 
    1950              : template<class CharSet>
    1951              : void
    1952         1950 : url_base::
    1953              : normalize_octets_impl(
    1954              :     int idx,
    1955              :     CharSet const& allowed,
    1956              :     op_t& op) noexcept
    1957              : {
    1958         1950 :     return normalize_octets_impl(
    1959         1950 :         idx, allowed, detail::empty_chars, op);
    1960              : }
    1961              : 
    1962              : inline
    1963              : url_base&
    1964           50 : url_base::
    1965              : normalize_scheme()
    1966              : {
    1967           50 :     to_lower_impl(id_scheme);
    1968           50 :     return *this;
    1969              : }
    1970              : 
    1971              : inline
    1972              : url_base&
    1973          396 : url_base::
    1974              : normalize_authority()
    1975              : {
    1976          396 :     op_t op(*this);
    1977              : 
    1978              :     // normalize host
    1979          396 :     if (host_type() == urls::host_type::name)
    1980              :     {
    1981          260 :         normalize_octets_impl(
    1982              :             id_host,
    1983              :             detail::reg_name_chars, op);
    1984              :     }
    1985          396 :     decoded_to_lower_impl(id_host);
    1986              : 
    1987              :     // normalize password
    1988          396 :     normalize_octets_impl(id_pass, detail::password_chars, op);
    1989              : 
    1990              :     // normalize user
    1991          396 :     normalize_octets_impl(id_user, detail::user_chars, op);
    1992          792 :     return *this;
    1993          396 : }
    1994              : 
    1995              : inline
    1996              : url_base&
    1997          850 : url_base::
    1998              : normalize_path()
    1999              : {
    2000          850 :     op_t op(*this);
    2001          850 :     normalize_octets_impl(id_path, detail::segment_chars, op);
    2002          850 :     core::string_view p = impl_.get(id_path);
    2003          850 :     char* p_dest = s_ + impl_.offset(id_path);
    2004          850 :     char* p_end = s_ + impl_.offset(id_path + 1);
    2005          850 :     auto pn = p.size();
    2006          850 :     auto skip_dot = 0;
    2007          850 :     bool encode_colons = false;
    2008          850 :     core::string_view first_seg;
    2009              : 
    2010              : //------------------------------------------------
    2011              : //
    2012              : //  Determine unnecessary initial dot segments to skip and
    2013              : //  if we need to encode colons in the first segment
    2014              : //
    2015          850 :     if (
    2016         1114 :         !has_authority() &&
    2017          264 :         p.starts_with("/./"))
    2018              :     {
    2019              :         // check if removing the "/./" would result in "//"
    2020              :         // ex: "/.//", "/././/", "/././/", ...
    2021           14 :         skip_dot = 2;
    2022           15 :         while (p.substr(skip_dot, 3).starts_with("/./"))
    2023            1 :             skip_dot += 2;
    2024           14 :         if (p.substr(skip_dot).starts_with("//"))
    2025           11 :             skip_dot = 2;
    2026              :         else
    2027            3 :             skip_dot = 0;
    2028              :     }
    2029          836 :     else if (
    2030          866 :         !has_scheme() &&
    2031           30 :         !has_authority())
    2032              :     {
    2033           30 :         if (p.starts_with("./"))
    2034              :         {
    2035              :             // check if removing the "./" would result in "//"
    2036              :             // ex: ".//", "././/", "././/", ...
    2037            7 :             skip_dot = 1;
    2038           10 :             while (p.substr(skip_dot, 3).starts_with("/./"))
    2039            3 :                 skip_dot += 2;
    2040            7 :             if (p.substr(skip_dot).starts_with("//"))
    2041            2 :                 skip_dot = 2;
    2042              :             else
    2043            5 :                 skip_dot = 0;
    2044              : 
    2045            7 :             if ( !skip_dot )
    2046              :             {
    2047              :                 // check if removing "./"s would leave us
    2048              :                 // a first segment with an ambiguous ":"
    2049            5 :                 first_seg = p.substr(2);
    2050            7 :                 while (first_seg.starts_with("./"))
    2051            2 :                     first_seg = first_seg.substr(2);
    2052            5 :                 auto i = first_seg.find('/');
    2053            5 :                 if (i != core::string_view::npos)
    2054            1 :                     first_seg = first_seg.substr(0, i);
    2055            5 :                 encode_colons = first_seg.contains(':');
    2056              :             }
    2057              :         }
    2058              :         else
    2059              :         {
    2060              :             // check if normalize_octets_impl
    2061              :             // didn't already create a ":"
    2062              :             // in the first segment
    2063           23 :             first_seg = p;
    2064           23 :             auto i = first_seg.find('/');
    2065           23 :             if (i != core::string_view::npos)
    2066           17 :                 first_seg = p.substr(0, i);
    2067           23 :             encode_colons = first_seg.contains(':');
    2068              :         }
    2069              :     }
    2070              : 
    2071              : //------------------------------------------------
    2072              : //
    2073              : //  Encode colons in the first segment
    2074              : //
    2075          850 :     if (encode_colons)
    2076              :     {
    2077              :         // prepend with "./"
    2078              :         // (resize_impl never throws)
    2079              :         auto cn =
    2080            5 :             std::count(
    2081              :                 first_seg.begin(),
    2082              :                 first_seg.end(),
    2083            5 :                 ':');
    2084            5 :         resize_impl(
    2085            5 :             id_path, pn + (2 * cn), op);
    2086              :         // move the 2nd, 3rd, ... segments
    2087            5 :         auto begin = s_ + impl_.offset(id_path);
    2088            5 :         auto it = begin;
    2089            5 :         auto end = begin + pn;
    2090           11 :         while (core::string_view(it, 2) == "./")
    2091            6 :             it += 2;
    2092           57 :         while (*it != '/' &&
    2093              :                it != end)
    2094           52 :             ++it;
    2095              :         // we don't need op here because this is
    2096              :         // an internal operation
    2097            5 :         std::memmove(it + (2 * cn), it, end - it);
    2098              : 
    2099              :         // move 1st segment
    2100            5 :         auto src = s_ + impl_.offset(id_path) + pn;
    2101            5 :         auto dest = s_ + impl_.offset(id_query);
    2102            5 :         src -= end - it;
    2103            5 :         dest -= end - it;
    2104            5 :         pn -= end - it;
    2105              :         do {
    2106           64 :             --src;
    2107           64 :             --dest;
    2108           64 :             if (*src != ':')
    2109              :             {
    2110           57 :                 *dest = *src;
    2111              :             }
    2112              :             else
    2113              :             {
    2114              :                 // use uppercase as required by
    2115              :                 // syntax-based normalization
    2116            7 :                 *dest-- = 'A';
    2117            7 :                 *dest-- = '3';
    2118            7 :                 *dest = '%';
    2119              :             }
    2120           64 :             --pn;
    2121           64 :         } while (pn);
    2122            5 :         skip_dot = 0;
    2123            5 :         p = impl_.get(id_path);
    2124            5 :         pn = p.size();
    2125            5 :         p_dest = s_ + impl_.offset(id_path);
    2126            5 :         p_end = s_ + impl_.offset(id_path + 1);
    2127              :     }
    2128              : 
    2129              : //------------------------------------------------
    2130              : //
    2131              : //  Remove "." and ".." segments
    2132              : //
    2133          850 :     p.remove_prefix(skip_dot);
    2134          850 :     p_dest += skip_dot;
    2135          850 :     auto n = detail::remove_dot_segments(
    2136              :         p_dest, p_end, p);
    2137              : 
    2138              : //------------------------------------------------
    2139              : //
    2140              : //  Update path parameters
    2141              : //
    2142          850 :     if (n != pn)
    2143              :     {
    2144          139 :         BOOST_ASSERT(n < pn);
    2145          139 :         shrink_impl(id_path, n + skip_dot, op);
    2146          139 :         p = encoded_path();
    2147          139 :         if (p == "/")
    2148            9 :             impl_.nseg_ = 0;
    2149          130 :         else if (!p.empty())
    2150          256 :             impl_.nseg_ = detail::to_size_type(
    2151          128 :                 std::count(
    2152          256 :                     p.begin() + 1, p.end(), '/') + 1);
    2153              :         else
    2154            2 :             impl_.nseg_ = 0;
    2155          139 :         impl_.decoded_[id_path] =
    2156          139 :             detail::to_size_type(detail::decode_bytes_unsafe(
    2157              :                 impl_.get(id_path)));
    2158              :     }
    2159          850 :     return *this;
    2160          850 : }
    2161              : 
    2162              : inline
    2163              : url_base&
    2164           51 : url_base::
    2165              : normalize_query()
    2166              : {
    2167           51 :     op_t op(*this);
    2168           51 :     normalize_octets_impl(
    2169              :         id_query,
    2170              :         detail::query_chars,
    2171              :         detail::query_ignore_chars,
    2172              :         op);
    2173          102 :     return *this;
    2174           51 : }
    2175              : 
    2176              : inline
    2177              : url_base&
    2178           48 : url_base::
    2179              : normalize_fragment()
    2180              : {
    2181           48 :     op_t op(*this);
    2182           48 :     normalize_octets_impl(
    2183              :         id_frag, detail::fragment_chars, op);
    2184           96 :     return *this;
    2185           48 : }
    2186              : 
    2187              : inline
    2188              : url_base&
    2189           47 : url_base::
    2190              : normalize()
    2191              : {
    2192           47 :     normalize_fragment();
    2193           47 :     normalize_query();
    2194           47 :     normalize_path();
    2195           47 :     normalize_authority();
    2196           47 :     normalize_scheme();
    2197           47 :     return *this;
    2198              : }
    2199              : 
    2200              : //------------------------------------------------
    2201              : //
    2202              : // Implementation
    2203              : //
    2204              : //------------------------------------------------
    2205              : 
    2206              : inline
    2207              : void
    2208        18355 : url_base::
    2209              : check_invariants() const noexcept
    2210              : {
    2211        18355 :     BOOST_ASSERT(
    2212              :         impl_.len(id_scheme) == 0 ||
    2213              :         impl_.get(id_scheme).ends_with(':'));
    2214        18355 :     BOOST_ASSERT(
    2215              :         impl_.len(id_user) == 0 ||
    2216              :         impl_.get(id_user).starts_with("//"));
    2217        18355 :     BOOST_ASSERT(
    2218              :         impl_.len(id_pass) == 0 ||
    2219              :         impl_.get(id_user).starts_with("//"));
    2220        18355 :     BOOST_ASSERT(
    2221              :         impl_.len(id_pass) == 0 ||
    2222              :         (impl_.len(id_pass) == 1 &&
    2223              :             impl_.get(id_pass) == "@") ||
    2224              :         (impl_.len(id_pass) > 1 &&
    2225              :             impl_.get(id_pass).starts_with(':') &&
    2226              :             impl_.get(id_pass).ends_with('@')));
    2227        18355 :     BOOST_ASSERT(
    2228              :         impl_.len(id_user, id_path) == 0 ||
    2229              :         impl_.get(id_user).starts_with("//"));
    2230        18355 :     BOOST_ASSERT(impl_.decoded_[id_path] >=
    2231              :         ((impl_.len(id_path) + 2) / 3));
    2232        18355 :     BOOST_ASSERT(
    2233              :         impl_.len(id_port) == 0 ||
    2234              :         impl_.get(id_port).starts_with(':'));
    2235        18355 :     BOOST_ASSERT(
    2236              :         impl_.len(id_query) == 0 ||
    2237              :         impl_.get(id_query).starts_with('?'));
    2238        18355 :     BOOST_ASSERT(
    2239              :         (impl_.len(id_query) == 0 && impl_.nparam_ == 0) ||
    2240              :         (impl_.len(id_query) > 0 && impl_.nparam_ > 0));
    2241        18355 :     BOOST_ASSERT(
    2242              :         impl_.len(id_frag) == 0 ||
    2243              :         impl_.get(id_frag).starts_with('#'));
    2244        18355 :     BOOST_ASSERT(c_str()[size()] == '\0');
    2245        18355 : }
    2246              : 
    2247              : inline
    2248              : char*
    2249         1625 : url_base::
    2250              : resize_impl(
    2251              :     int id,
    2252              :     std::size_t new_size,
    2253              :     op_t& op)
    2254              : {
    2255         1625 :     return resize_impl(
    2256         1625 :         id, id + 1, new_size, op);
    2257              : }
    2258              : 
    2259              : inline
    2260              : char*
    2261         1896 : url_base::
    2262              : resize_impl(
    2263              :     int first,
    2264              :     int last,
    2265              :     std::size_t new_len,
    2266              :     op_t& op)
    2267              : {
    2268         1896 :     auto const n0 = impl_.len(first, last);
    2269         1896 :     if(new_len == 0 && n0 == 0)
    2270          389 :         return s_ + impl_.offset(first);
    2271         1507 :     if(new_len <= n0)
    2272          530 :         return shrink_impl(
    2273          530 :             first, last, new_len, op);
    2274              : 
    2275              :     // growing
    2276          977 :     std::size_t n = new_len - n0;
    2277          977 :     reserve_impl(size() + n, op);
    2278              :     auto const pos =
    2279          977 :         impl_.offset(last);
    2280              :     // adjust chars
    2281          977 :     op.move(
    2282          977 :         s_ + pos + n,
    2283          977 :         s_ + pos,
    2284          977 :         impl_.offset(id_end) -
    2285              :             pos + 1);
    2286              :     // collapse (first, last)
    2287          977 :     impl_.collapse(first, last,
    2288          977 :         impl_.offset(last) + n);
    2289              :     // shift (last, end) right
    2290          977 :     impl_.adjust_right(last, id_end, n);
    2291          977 :     s_[size()] = '\0';
    2292          977 :     return s_ + impl_.offset(first);
    2293              : }
    2294              : 
    2295              : inline
    2296              : char*
    2297          174 : url_base::
    2298              : shrink_impl(
    2299              :     int id,
    2300              :     std::size_t new_size,
    2301              :     op_t& op)
    2302              : {
    2303          174 :     return shrink_impl(
    2304          174 :         id, id + 1, new_size, op);
    2305              : }
    2306              : 
    2307              : inline
    2308              : char*
    2309          704 : url_base::
    2310              : shrink_impl(
    2311              :     int first,
    2312              :     int last,
    2313              :     std::size_t new_len,
    2314              :     op_t& op)
    2315              : {
    2316              :     // shrinking
    2317          704 :     auto const n0 = impl_.len(first, last);
    2318          704 :     BOOST_ASSERT(new_len <= n0);
    2319          704 :     std::size_t n = n0 - new_len;
    2320              :     auto const pos =
    2321          704 :         impl_.offset(last);
    2322              :     // adjust chars
    2323          704 :     op.move(
    2324          704 :         s_ + pos - n,
    2325          704 :         s_ + pos,
    2326          704 :         impl_.offset(
    2327          704 :             id_end) - pos + 1);
    2328              :     // collapse (first, last)
    2329          704 :     impl_.collapse(first,  last,
    2330          704 :         impl_.offset(last) - n);
    2331              :     // shift (last, end) left
    2332          704 :     impl_.adjust_left(last, id_end, n);
    2333          704 :     s_[size()] = '\0';
    2334          704 :     return s_ + impl_.offset(first);
    2335              : }
    2336              : 
    2337              : //------------------------------------------------
    2338              : 
    2339              : inline
    2340              : void
    2341           67 : url_base::
    2342              : set_scheme_impl(
    2343              :     core::string_view s,
    2344              :     urls::scheme id)
    2345              : {
    2346           67 :     op_t op(*this, &s);
    2347           67 :     check_invariants();
    2348           80 :     grammar::parse(
    2349           13 :         s, detail::scheme_rule()
    2350           80 :             ).value(BOOST_URL_POS);
    2351           54 :     auto const n = s.size();
    2352           54 :     auto const p = impl_.offset(id_path);
    2353              : 
    2354              :     // check for "./" prefix
    2355              :     bool const has_dot =
    2356          162 :         [this, p]
    2357              :     {
    2358           54 :         if(impl_.nseg_ == 0)
    2359           32 :             return false;
    2360           22 :         if(first_segment().size() < 2)
    2361            9 :             return false;
    2362           13 :         auto const src = s_ + p;
    2363           13 :         if(src[0] != '.')
    2364           10 :             return false;
    2365            3 :         if(src[1] != '/')
    2366            0 :             return false;
    2367            3 :         return true;
    2368           54 :     }();
    2369              : 
    2370              :     // Remove "./"
    2371           54 :     if(has_dot)
    2372              :     {
    2373              :         // do this first, for
    2374              :         // strong exception safety
    2375            6 :         reserve_impl(
    2376            3 :             size() + n + 1 - 2, op);
    2377            3 :         op.move(
    2378            3 :             s_ + p,
    2379            3 :             s_ + p + 2,
    2380            3 :             size() + 1 -
    2381              :                 (p + 2));
    2382            3 :         impl_.set_size(
    2383              :             id_path,
    2384            3 :             impl_.len(id_path) - 2);
    2385            3 :         s_[size()] = '\0';
    2386              :     }
    2387              : 
    2388           54 :     auto dest = resize_impl(
    2389              :         id_scheme, n + 1, op);
    2390           54 :     s.copy(dest, n);
    2391           54 :     dest[n] = ':';
    2392           54 :     impl_.scheme_ = id;
    2393           54 :     check_invariants();
    2394           67 : }
    2395              : 
    2396              : inline
    2397              : char*
    2398          101 : url_base::
    2399              : set_user_impl(
    2400              :     std::size_t n,
    2401              :     op_t& op)
    2402              : {
    2403          101 :     check_invariants();
    2404          101 :     if(impl_.len(id_pass) != 0)
    2405              :     {
    2406              :         // keep "//"
    2407           50 :         auto dest = resize_impl(
    2408              :             id_user, 2 + n, op);
    2409           50 :         check_invariants();
    2410           50 :         return dest + 2;
    2411              :     }
    2412              :     // add authority
    2413              :     bool const make_absolute =
    2414           91 :         !is_path_absolute() &&
    2415           40 :         !impl_.get(id_path).empty();
    2416          102 :     auto dest = resize_impl(
    2417           51 :         id_user, 2 + n + 1 + make_absolute, op);
    2418           51 :     impl_.split(id_user, 2 + n);
    2419           51 :     dest[0] = '/';
    2420           51 :     dest[1] = '/';
    2421           51 :     dest[2 + n] = '@';
    2422           51 :     if (make_absolute)
    2423              :     {
    2424            4 :         impl_.split(id_pass, 1);
    2425            4 :         impl_.split(id_host, 0);
    2426            4 :         impl_.split(id_port, 0);
    2427            4 :         dest[3 + n] = '/';
    2428              :     }
    2429           51 :     check_invariants();
    2430           51 :     return dest + 2;
    2431              : }
    2432              : 
    2433              : inline
    2434              : char*
    2435           82 : url_base::
    2436              : set_password_impl(
    2437              :     std::size_t n,
    2438              :     op_t& op)
    2439              : {
    2440           82 :     check_invariants();
    2441           82 :     if(impl_.len(id_user) != 0)
    2442              :     {
    2443              :         // already have authority
    2444           66 :         auto const dest = resize_impl(
    2445              :             id_pass, 1 + n + 1, op);
    2446           66 :         dest[0] = ':';
    2447           66 :         dest[n + 1] = '@';
    2448           66 :         check_invariants();
    2449           66 :         return dest + 1;
    2450              :     }
    2451              :     // add authority
    2452              :     bool const make_absolute =
    2453           25 :             !is_path_absolute() &&
    2454            9 :             !impl_.get(id_path).empty();
    2455              :     auto const dest =
    2456           32 :         resize_impl(
    2457              :         id_user, id_host,
    2458           16 :         2 + 1 + n + 1 + make_absolute, op);
    2459           16 :     impl_.split(id_user, 2);
    2460           16 :     dest[0] = '/';
    2461           16 :     dest[1] = '/';
    2462           16 :     dest[2] = ':';
    2463           16 :     dest[2 + n + 1] = '@';
    2464           16 :     if (make_absolute)
    2465              :     {
    2466            2 :         impl_.split(id_pass, 2 + n);
    2467            2 :         impl_.split(id_host, 0);
    2468            2 :         impl_.split(id_port, 0);
    2469            2 :         dest[4 + n] = '/';
    2470              :     }
    2471           16 :     check_invariants();
    2472           16 :     return dest + 3;
    2473              : }
    2474              : 
    2475              : inline
    2476              : char*
    2477           99 : url_base::
    2478              : set_userinfo_impl(
    2479              :     std::size_t n,
    2480              :     op_t& op)
    2481              : {
    2482              :     // "//" {dest} "@"
    2483           99 :     check_invariants();
    2484              :     bool const make_absolute =
    2485          180 :             !is_path_absolute() &&
    2486           81 :             !impl_.get(id_path).empty();
    2487          198 :     auto dest = resize_impl(
    2488           99 :         id_user, id_host, n + 3 + make_absolute, op);
    2489           99 :     impl_.split(id_user, n + 2);
    2490           99 :     dest[0] = '/';
    2491           99 :     dest[1] = '/';
    2492           99 :     dest[n + 2] = '@';
    2493           99 :     if (make_absolute)
    2494              :     {
    2495            2 :         impl_.split(id_pass, 1);
    2496            2 :         impl_.split(id_host, 0);
    2497            2 :         impl_.split(id_port, 0);
    2498            2 :         dest[3 + n] = '/';
    2499              :     }
    2500           99 :     check_invariants();
    2501           99 :     return dest + 2;
    2502              : }
    2503              : 
    2504              : inline
    2505              : char*
    2506          238 : url_base::
    2507              : set_host_impl(
    2508              :     std::size_t n,
    2509              :     op_t& op)
    2510              : {
    2511          238 :     check_invariants();
    2512          238 :     if(impl_.len(id_user) == 0)
    2513              :     {
    2514              :         // add authority
    2515              :         bool make_absolute =
    2516          216 :             !is_path_absolute() &&
    2517          106 :             impl_.len(id_path) != 0;
    2518          110 :         auto pn = impl_.len(id_path);
    2519          220 :         auto dest = resize_impl(
    2520          110 :             id_user, n + 2 + make_absolute, op);
    2521          110 :         impl_.split(id_user, 2);
    2522          110 :         impl_.split(id_pass, 0);
    2523          110 :         impl_.split(id_host, n);
    2524          110 :         impl_.split(id_port, 0);
    2525          110 :         impl_.split(id_path, pn + make_absolute);
    2526          110 :         if (make_absolute)
    2527              :         {
    2528            7 :             dest[n + 2] = '/';
    2529            7 :             ++impl_.decoded_[id_path];
    2530              :         }
    2531          110 :         dest[0] = '/';
    2532          110 :         dest[1] = '/';
    2533          110 :         check_invariants();
    2534          110 :         return dest + 2;
    2535              :     }
    2536              :     // already have authority
    2537          128 :     auto const dest = resize_impl(
    2538              :         id_host, n, op);
    2539          128 :     check_invariants();
    2540          128 :     return dest;
    2541              : }
    2542              : 
    2543              : inline
    2544              : char*
    2545          117 : url_base::
    2546              : set_port_impl(
    2547              :     std::size_t n,
    2548              :     op_t& op)
    2549              : {
    2550          117 :     check_invariants();
    2551          117 :     if(impl_.len(id_user) != 0)
    2552              :     {
    2553              :         // authority exists
    2554           95 :         auto dest = resize_impl(
    2555              :             id_port, n + 1, op);
    2556           95 :         dest[0] = ':';
    2557           95 :         check_invariants();
    2558           95 :         return dest + 1;
    2559              :     }
    2560              :     bool make_absolute =
    2561           38 :         !is_path_absolute() &&
    2562           16 :         impl_.len(id_path) != 0;
    2563           44 :     auto dest = resize_impl(
    2564           22 :         id_user, 3 + n + make_absolute, op);
    2565           22 :     impl_.split(id_user, 2);
    2566           22 :     impl_.split(id_pass, 0);
    2567           22 :     impl_.split(id_host, 0);
    2568           22 :     dest[0] = '/';
    2569           22 :     dest[1] = '/';
    2570           22 :     dest[2] = ':';
    2571           22 :     if (make_absolute)
    2572              :     {
    2573            2 :         impl_.split(id_port, n + 1);
    2574            2 :         dest[n + 3] = '/';
    2575            2 :         ++impl_.decoded_[id_path];
    2576              :     }
    2577           22 :     check_invariants();
    2578           22 :     return dest + 3;
    2579              : }
    2580              : 
    2581              : inline
    2582              : char*
    2583          198 : url_base::
    2584              : set_path_impl(
    2585              :     std::size_t n,
    2586              :     op_t& op)
    2587              : {
    2588          198 :     check_invariants();
    2589          198 :     auto const dest = resize_impl(
    2590              :         id_path, n, op);
    2591          198 :     return dest;
    2592              : }
    2593              : 
    2594              : 
    2595              : //------------------------------------------------
    2596              : 
    2597              : // return the first segment of the path.
    2598              : // this is needed for some algorithms.
    2599              : inline
    2600              : core::string_view
    2601           49 : url_base::
    2602              : first_segment() const noexcept
    2603              : {
    2604           49 :     if(impl_.nseg_ == 0)
    2605            7 :         return {};
    2606           42 :     auto const p0 = impl_.cs_ +
    2607           42 :         impl_.offset(id_path) +
    2608           42 :             detail::path_prefix(
    2609           42 :                 impl_.get(id_path));
    2610           42 :     auto const end = impl_.cs_ +
    2611           42 :         impl_.offset(id_query);
    2612           42 :     if(impl_.nseg_ == 1)
    2613           44 :         return core::string_view(
    2614           22 :             p0, end - p0);
    2615           20 :     auto p = p0;
    2616           54 :     while(*p != '/')
    2617           34 :         ++p;
    2618           20 :     BOOST_ASSERT(p < end);
    2619           20 :     return core::string_view(p0, p - p0);
    2620              : }
    2621              : 
    2622              : inline
    2623              : detail::segments_iter_impl
    2624          598 : url_base::
    2625              : edit_segments(
    2626              :     detail::segments_iter_impl const& it0,
    2627              :     detail::segments_iter_impl const& it1,
    2628              :     detail::any_segments_iter&& src,
    2629              :     // -1 = preserve
    2630              :     //  0 = make relative (can fail)
    2631              :     //  1 = make absolute
    2632              :     int absolute)
    2633              : {
    2634              :     // Iterator doesn't belong to this url
    2635          598 :     BOOST_ASSERT(it0.ref.alias_of(impl_));
    2636              : 
    2637              :     // Iterator doesn't belong to this url
    2638          598 :     BOOST_ASSERT(it1.ref.alias_of(impl_));
    2639              : 
    2640              :     // Iterator is in the wrong order
    2641          598 :     BOOST_ASSERT(it0.index <= it1.index);
    2642              : 
    2643              :     // Iterator is out of range
    2644          598 :     BOOST_ASSERT(it0.index <= impl_.nseg_);
    2645          598 :     BOOST_ASSERT(it0.pos <= impl_.len(id_path));
    2646              : 
    2647              :     // Iterator is out of range
    2648          598 :     BOOST_ASSERT(it1.index <= impl_.nseg_);
    2649          598 :     BOOST_ASSERT(it1.pos <= impl_.len(id_path));
    2650              : 
    2651              : //------------------------------------------------
    2652              : //
    2653              : //  Calculate output prefix
    2654              : //
    2655              : //  0 = ""
    2656              : //  1 = "/"
    2657              : //  2 = "./"
    2658              : //  3 = "/./"
    2659              : //
    2660          598 :     bool const is_abs = is_path_absolute();
    2661          598 :     if(has_authority())
    2662              :     {
    2663              :         // Check if the new
    2664              :         // path would be empty
    2665          213 :         if( src.fast_nseg == 0 &&
    2666          108 :             it0.index == 0 &&
    2667           18 :             it1.index == impl_.nseg_)
    2668              :         {
    2669              :             // VFALCO we don't have
    2670              :             // access to nchar this early
    2671              :             //
    2672              :             //BOOST_ASSERT(nchar == 0);
    2673           15 :             absolute = 0;
    2674              :         }
    2675              :         else
    2676              :         {
    2677              :             // prefix "/" required
    2678          198 :             absolute = 1;
    2679              :         }
    2680              :     }
    2681          385 :     else if(absolute < 0)
    2682              :     {
    2683          385 :         absolute = is_abs; // preserve
    2684              :     }
    2685          598 :     auto const path_pos = impl_.offset(id_path);
    2686              : 
    2687          598 :     std::size_t nchar = 0;
    2688          598 :     std::size_t prefix = 0;
    2689          598 :     bool encode_colons = false;
    2690          598 :     bool cp_src_prefix = false;
    2691          598 :     if(it0.index > 0)
    2692              :     {
    2693              :         // first segment unchanged
    2694          323 :         prefix = src.fast_nseg > 0;
    2695              :     }
    2696          275 :     else if(src.fast_nseg > 0)
    2697              :     {
    2698              :         // first segment from src
    2699          222 :         if(! src.front.empty())
    2700              :         {
    2701          163 :             if( src.front == "." &&
    2702            7 :                     src.fast_nseg > 1)
    2703            4 :                 if (src.s.empty())
    2704              :                 {
    2705              :                     // if front is ".", we need the extra "." in the prefix
    2706              :                     // which will maintain the invariant that segments represent
    2707              :                     // {"."}
    2708            4 :                     prefix = 2 + absolute;
    2709              :                 }
    2710              :                 else
    2711              :                 {
    2712              :                     // if the "." prefix is explicitly required from set_path
    2713              :                     // we do not include an extra "." segment
    2714            0 :                     prefix = absolute;
    2715            0 :                     cp_src_prefix = true;
    2716              :                 }
    2717          152 :             else if(absolute)
    2718           79 :                 prefix = 1;
    2719          142 :             else if(has_scheme() ||
    2720           69 :                     ! src.front.contains(':'))
    2721           68 :                 prefix = 0;
    2722              :             else
    2723              :             {
    2724            5 :                 prefix = 0;
    2725            5 :                 encode_colons = true;
    2726              :             }
    2727              :         }
    2728              :         else
    2729              :         {
    2730           66 :             prefix = 2 + absolute;
    2731              :         }
    2732              :     }
    2733              :     else
    2734              :     {
    2735              :         // first segment from it1
    2736           53 :         auto const p =
    2737           53 :             impl_.cs_ + path_pos + it1.pos;
    2738          106 :         switch(impl_.cs_ +
    2739           53 :             impl_.offset(id_query) - p)
    2740              :         {
    2741           34 :         case 0:
    2742              :             // points to end
    2743           34 :             prefix = absolute;
    2744           34 :             break;
    2745           11 :         default:
    2746           11 :             BOOST_ASSERT(*p == '/');
    2747           11 :             if(p[1] != '/')
    2748              :             {
    2749           11 :                 if(absolute)
    2750            5 :                     prefix = 1;
    2751           11 :                 else if(has_scheme() ||
    2752           11 :                         ! it1.dereference().contains(':'))
    2753            5 :                     prefix = 0;
    2754              :                 else
    2755            1 :                     prefix = 2;
    2756           11 :                 break;
    2757              :             }
    2758              :             // empty
    2759              :             BOOST_FALLTHROUGH;
    2760              :         case 1:
    2761              :             // empty
    2762            8 :             BOOST_ASSERT(*p == '/');
    2763            8 :             prefix = 2 + absolute;
    2764            8 :             break;
    2765              :         }
    2766              :     }
    2767              : 
    2768              : //  append '/' to new segs
    2769              : //  if inserting at front.
    2770          598 :     std::size_t const suffix =
    2771          778 :         it1.index == 0 &&
    2772          662 :         impl_.nseg_ > 0 &&
    2773           64 :         src.fast_nseg > 0;
    2774              : 
    2775              : //------------------------------------------------
    2776              : //
    2777              : //  Measure the number of encoded characters
    2778              : //  of output, and the number of inserted
    2779              : //  segments including internal separators.
    2780              : //
    2781          598 :     src.encode_colons = encode_colons;
    2782          598 :     std::size_t nseg = 0;
    2783          598 :     if(src.measure(nchar))
    2784              :     {
    2785          409 :         src.encode_colons = false;
    2786              :         for(;;)
    2787              :         {
    2788          734 :             ++nseg;
    2789          734 :             if(! src.measure(nchar))
    2790          407 :                 break;
    2791          325 :             ++nchar;
    2792              :         }
    2793              :     }
    2794              : 
    2795          596 :     switch(src.fast_nseg)
    2796              :     {
    2797          189 :     case 0:
    2798          189 :         BOOST_ASSERT(nseg == 0);
    2799          189 :         break;
    2800          220 :     case 1:
    2801          220 :         BOOST_ASSERT(nseg == 1);
    2802          220 :         break;
    2803          187 :     case 2:
    2804          187 :         BOOST_ASSERT(nseg >= 2);
    2805          187 :         break;
    2806              :     }
    2807              : 
    2808              : //------------------------------------------------
    2809              : //
    2810              : //  Calculate [pos0, pos1) to remove
    2811              : //
    2812          596 :     auto pos0 = it0.pos;
    2813          596 :     if(it0.index == 0)
    2814              :     {
    2815              :         // patch pos for prefix
    2816          273 :         pos0 = 0;
    2817              :     }
    2818          596 :     auto pos1 = it1.pos;
    2819          596 :     if(it1.index == 0)
    2820              :     {
    2821              :         // patch pos for prefix
    2822          180 :         pos1 = detail::path_prefix(
    2823              :             impl_.get(id_path));
    2824              :     }
    2825          416 :     else if(
    2826          416 :         it0.index == 0 &&
    2827           93 :         it1.index < impl_.nseg_ &&
    2828              :         nseg == 0)
    2829              :     {
    2830              :         // Remove the slash from segment it1
    2831              :         // if it is becoming the new first
    2832              :         // segment.
    2833           19 :         ++pos1;
    2834              :     }
    2835              :     // calc decoded size of old range
    2836              :     auto const dn0 =
    2837          596 :         detail::decode_bytes_unsafe(
    2838              :             core::string_view(
    2839          596 :                 impl_.cs_ +
    2840          596 :                     impl_.offset(id_path) +
    2841              :                     pos0,
    2842              :                 pos1 - pos0));
    2843              : 
    2844              : //------------------------------------------------
    2845              : //
    2846              : //  Resize
    2847              : //
    2848         1192 :     op_t op(*this, &src.s);
    2849              :     char* dest;
    2850              :     char const* end;
    2851              :     {
    2852          596 :         auto const nremove = pos1 - pos0;
    2853              :         // check overflow
    2854         1192 :         if( nchar <= max_size() && (
    2855          596 :             prefix + suffix <=
    2856          596 :                 max_size() - nchar))
    2857              :         {
    2858          596 :             nchar = prefix + nchar + suffix;
    2859          941 :             if( nchar <= nremove ||
    2860          345 :                 nchar - nremove <=
    2861          345 :                     max_size() - size())
    2862          596 :                 goto ok;
    2863              :         }
    2864              :         // too large
    2865            0 :         detail::throw_length_error();
    2866          596 :     ok:
    2867              :         auto const new_size =
    2868          596 :             size() + nchar - nremove;
    2869          596 :         reserve_impl(new_size, op);
    2870          596 :         dest = s_ + path_pos + pos0;
    2871          596 :         op.move(
    2872          596 :             dest + nchar,
    2873          596 :             s_ + path_pos + pos1,
    2874          596 :             size() - path_pos - pos1);
    2875         1192 :         impl_.set_size(
    2876              :             id_path,
    2877          596 :             impl_.len(id_path) + nchar - nremove);
    2878          596 :         BOOST_ASSERT(size() == new_size);
    2879          596 :         end = dest + nchar;
    2880          596 :         auto const nseg1 =
    2881          596 :             static_cast<std::ptrdiff_t>(impl_.nseg_) +
    2882          596 :             static_cast<std::ptrdiff_t>(nseg) -
    2883          596 :             static_cast<std::ptrdiff_t>(it1.index) +
    2884          596 :             static_cast<std::ptrdiff_t>(it0.index) -
    2885              :             static_cast<std::ptrdiff_t>(cp_src_prefix);
    2886          596 :         BOOST_ASSERT(nseg1 >= 0);
    2887          596 :         impl_.nseg_ = detail::to_size_type(nseg1);
    2888          596 :         if(s_)
    2889          594 :             s_[size()] = '\0';
    2890              :     }
    2891              : 
    2892              : //------------------------------------------------
    2893              : //
    2894              : //  Output segments and internal separators:
    2895              : //
    2896              : //  prefix [ segment [ '/' segment ] ] suffix
    2897              : //
    2898          596 :     auto const dest0 = dest;
    2899          596 :     switch(prefix)
    2900              :     {
    2901           38 :     case 3:
    2902           38 :         *dest++ = '/';
    2903           38 :         *dest++ = '.';
    2904           38 :         *dest++ = '/';
    2905           38 :         break;
    2906           41 :     case 2:
    2907           41 :         *dest++ = '.';
    2908              :         BOOST_FALLTHROUGH;
    2909          323 :     case 1:
    2910          323 :         *dest++ = '/';
    2911          323 :         break;
    2912          235 :     default:
    2913          235 :         break;
    2914              :     }
    2915          596 :     src.rewind();
    2916          596 :     if(nseg > 0)
    2917              :     {
    2918          407 :         src.encode_colons = encode_colons;
    2919              :         for(;;)
    2920              :         {
    2921          732 :             src.copy(dest, end);
    2922          732 :             if(--nseg == 0)
    2923          407 :                 break;
    2924          325 :             *dest++ = '/';
    2925          325 :             src.encode_colons = false;
    2926              :         }
    2927          407 :         if(suffix)
    2928           64 :             *dest++ = '/';
    2929              :     }
    2930          596 :     BOOST_ASSERT(dest == dest0 + nchar);
    2931              : 
    2932              :     // calc decoded size of new range,
    2933              :     auto const dn =
    2934          596 :         detail::decode_bytes_unsafe(
    2935          596 :             core::string_view(dest0, dest - dest0));
    2936          596 :     if(dn >= dn0)
    2937          360 :         impl_.decoded_[id_path] +=
    2938          360 :             detail::to_size_type(dn - dn0);
    2939              :     else
    2940          236 :         impl_.decoded_[id_path] -=
    2941          236 :             detail::to_size_type(dn0 - dn);
    2942              : 
    2943              :     return detail::segments_iter_impl(
    2944         1192 :         impl_, pos0, it0.index);
    2945              : }
    2946              : 
    2947              : //------------------------------------------------
    2948              : 
    2949              : inline
    2950              : auto
    2951          147 : url_base::
    2952              : edit_params(
    2953              :     detail::params_iter_impl const& it0,
    2954              :     detail::params_iter_impl const& it1,
    2955              :     detail::any_params_iter&& src) ->
    2956              :         detail::params_iter_impl
    2957              : {
    2958          147 :     auto pos0 = impl_.offset(id_query);
    2959          147 :     auto pos1 = pos0 + it1.pos;
    2960          147 :     pos0 = pos0 + it0.pos;
    2961              : 
    2962              :     // Iterators belong to this url
    2963          147 :     BOOST_ASSERT(it0.ref.alias_of(impl_));
    2964          147 :     BOOST_ASSERT(it1.ref.alias_of(impl_));
    2965              : 
    2966              :     // Iterators is in the right order
    2967          147 :     BOOST_ASSERT(it0.index <= it1.index);
    2968              : 
    2969              :     // Iterators are within range
    2970          147 :     BOOST_ASSERT(it0.index <= impl_.nparam_);
    2971          147 :     BOOST_ASSERT(pos0 <= impl_.offset(id_frag));
    2972          147 :     BOOST_ASSERT(it1.index <= impl_.nparam_);
    2973          147 :     BOOST_ASSERT(pos1 <= impl_.offset(id_frag));
    2974              : 
    2975              :     // calc decoded size of old range,
    2976              :     // minus one if '?' or '&' prefixed
    2977              :     auto dn0 =
    2978              :         static_cast<std::ptrdiff_t>(
    2979          147 :             detail::decode_bytes_unsafe(
    2980              :                 core::string_view(
    2981          147 :                     impl_.cs_ + pos0,
    2982          147 :                     pos1 - pos0)));
    2983          147 :     if(impl_.len(id_query) > 0)
    2984          107 :         dn0 -= 1;
    2985          147 :     if(dn0 < 0)
    2986           46 :         dn0 = 0;
    2987              : 
    2988              : //------------------------------------------------
    2989              : //
    2990              : //  Measure the number of encoded characters
    2991              : //  of output, and the number of inserted
    2992              : //  segments including internal separators.
    2993              : //
    2994              : 
    2995          147 :     std::size_t nchar = 0;
    2996          147 :     std::size_t nparam = 0;
    2997          147 :     if(src.measure(nchar))
    2998              :     {
    2999          120 :         ++nchar; // for '?' or '&'
    3000              :         for(;;)
    3001              :         {
    3002          187 :             ++nparam;
    3003          187 :             if(! src.measure(nchar))
    3004          120 :                 break;
    3005           67 :             ++nchar; // for '&'
    3006              :         }
    3007              :     }
    3008              : 
    3009              : //------------------------------------------------
    3010              : //
    3011              : //  Resize
    3012              : //
    3013          142 :     op_t op(*this, &src.s0, &src.s1);
    3014              :     char* dest;
    3015              :     char const* end;
    3016              :     {
    3017          142 :         auto const nremove = pos1 - pos0;
    3018              :         // check overflow
    3019          245 :         if( nchar > nremove &&
    3020          103 :             nchar - nremove >
    3021          103 :                 max_size() - size())
    3022              :         {
    3023              :             // too large
    3024            0 :             detail::throw_length_error();
    3025              :         }
    3026          142 :         auto const nparam1 =
    3027          142 :             static_cast<std::ptrdiff_t>(impl_.nparam_) +
    3028          142 :             static_cast<std::ptrdiff_t>(nparam) -
    3029          142 :             static_cast<std::ptrdiff_t>(it1.index) +
    3030          142 :             static_cast<std::ptrdiff_t>(it0.index);
    3031          142 :         BOOST_ASSERT(nparam1 >= 0);
    3032          142 :         reserve_impl(size() + nchar - nremove, op);
    3033          142 :         dest = s_ + pos0;
    3034          142 :         end = dest + nchar;
    3035          142 :         if(impl_.nparam_ > 0)
    3036              :         {
    3037              :             // needed when we move
    3038              :             // the beginning of the query
    3039          102 :             s_[impl_.offset(id_query)] = '&';
    3040              :         }
    3041          142 :         op.move(
    3042          142 :             dest + nchar,
    3043          142 :             impl_.cs_ + pos1,
    3044          142 :             size() - pos1);
    3045          284 :         impl_.set_size(
    3046              :             id_query,
    3047          142 :             impl_.len(id_query) +
    3048              :                 nchar - nremove);
    3049          142 :         impl_.nparam_ =
    3050          142 :             detail::to_size_type(nparam1);
    3051          142 :         if(nparam1 > 0)
    3052              :         {
    3053              :             // needed when we erase
    3054              :             // the beginning of the query
    3055          142 :             s_[impl_.offset(id_query)] = '?';
    3056              :         }
    3057          142 :         if(s_)
    3058          142 :             s_[size()] = '\0';
    3059              :     }
    3060          142 :     auto const dest0 = dest;
    3061              : 
    3062              : //------------------------------------------------
    3063              : //
    3064              : //  Output params and internal separators:
    3065              : //
    3066              : //  [ '?' param ] [ '&' param ]
    3067              : //
    3068          142 :     if(nparam > 0)
    3069              :     {
    3070          120 :         if(it0.index == 0)
    3071           75 :             *dest++ = '?';
    3072              :         else
    3073           45 :             *dest++ = '&';
    3074          120 :         src.rewind();
    3075              :         for(;;)
    3076              :         {
    3077          187 :             src.copy(dest, end);
    3078          187 :             if(--nparam == 0)
    3079          120 :                 break;
    3080           67 :             *dest++ = '&';
    3081              :         }
    3082              :     }
    3083              : 
    3084              :     // calc decoded size of new range,
    3085              :     // minus one if '?' or '&' prefixed
    3086              :     auto dn =
    3087              :         static_cast<std::ptrdiff_t>(
    3088          142 :             detail::decode_bytes_unsafe(
    3089          142 :                 core::string_view(dest0, dest - dest0)));
    3090          142 :     if(impl_.len(id_query) > 0)
    3091          142 :         dn -= 1;
    3092          142 :     if(dn < 0)
    3093           22 :         dn = 0;
    3094              : 
    3095          142 :     if(dn >= dn0)
    3096          101 :         impl_.decoded_[id_query] +=
    3097          101 :             detail::to_size_type(dn - dn0);
    3098              :     else
    3099           41 :         impl_.decoded_[id_query] -=
    3100           41 :             detail::to_size_type(dn0 - dn);
    3101              : 
    3102              :     return detail::params_iter_impl(
    3103          142 :         impl_,
    3104          142 :         pos0 - impl_.offset_[id_query],
    3105          284 :         it0.index);
    3106          142 : }
    3107              : 
    3108              : //------------------------------------------------
    3109              : 
    3110              : inline
    3111              : void
    3112          396 : url_base::
    3113              : decoded_to_lower_impl(int id) noexcept
    3114              : {
    3115          396 :     char* it = s_ + impl_.offset(id);
    3116          396 :     char const* const end = s_ + impl_.offset(id + 1);
    3117         2405 :     while(it < end)
    3118              :     {
    3119         2009 :         if (*it != '%')
    3120              :         {
    3121         4008 :             *it = grammar::to_lower(
    3122         2004 :                 *it);
    3123         2004 :             ++it;
    3124         2004 :             continue;
    3125              :         }
    3126            5 :         it += 3;
    3127              :     }
    3128          396 : }
    3129              : 
    3130              : inline
    3131              : void
    3132           50 : url_base::
    3133              : to_lower_impl(int id) noexcept
    3134              : {
    3135           50 :     char* it = s_ + impl_.offset(id);
    3136           50 :     char const* const end = s_ + impl_.offset(id + 1);
    3137          229 :     while(it < end)
    3138              :     {
    3139          358 :         *it = grammar::to_lower(
    3140          179 :             *it);
    3141          179 :         ++it;
    3142              :     }
    3143           50 : }
    3144              : 
    3145              : } // urls
    3146              : } // boost
    3147              : 
    3148              : #endif
        

Generated by: LCOV version 2.3