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

Generated by: LCOV version 2.1