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

Generated by: LCOV version 2.3