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

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

Generated by: LCOV version 2.3