LCOV - code coverage report
Current view: top level - url/impl - url_base.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 99.5 % 1533 1525 8
Test Date: 2026-10-13 02:41:11 Functions: 100.0 % 81 81

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

Generated by: LCOV version 2.3