ext-boost/boost/url/impl/url_base.ipp
2023-05-22 18:45:02 +10:00

2573 lines
57 KiB
C++

//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#ifndef BOOST_URL_IMPL_URL_BASE_IPP
#define BOOST_URL_IMPL_URL_BASE_IPP
#include <boost/url/url_base.hpp>
#include <boost/url/error.hpp>
#include <boost/url/host_type.hpp>
#include <boost/url/scheme.hpp>
#include <boost/url/url_view.hpp>
#include <boost/url/detail/any_params_iter.hpp>
#include <boost/url/detail/any_segments_iter.hpp>
#include <boost/url/detail/encode.hpp>
#include <boost/url/detail/except.hpp>
#include <boost/url/detail/move_chars.hpp>
#include <boost/url/detail/print.hpp>
#include <boost/url/rfc/authority_rule.hpp>
#include <boost/url/rfc/query_rule.hpp>
#include <boost/url/rfc/detail/charsets.hpp>
#include <boost/url/rfc/detail/host_rule.hpp>
#include <boost/url/rfc/detail/ipvfuture_rule.hpp>
#include <boost/url/rfc/detail/path_rules.hpp>
#include <boost/url/rfc/detail/port_rule.hpp>
#include <boost/url/rfc/detail/scheme_rule.hpp>
#include <boost/url/rfc/detail/userinfo_rule.hpp>
#include <boost/url/grammar/parse.hpp>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <utility>
namespace boost {
namespace urls {
//------------------------------------------------
// these objects help handle the cases
// where the user passes in strings that
// come from inside the url buffer.
url_base::
op_t::
~op_t()
{
if(old)
u.cleanup(*this);
u.check_invariants();
}
url_base::
op_t::
op_t(
url_base& impl_,
string_view* s0_,
string_view* s1_) noexcept
: u(impl_)
, s0(s0_)
, s1(s1_)
{
u.check_invariants();
}
void
url_base::
op_t::
move(
char* dest,
char const* src,
std::size_t n) noexcept
{
if(! n)
return;
if(s0)
{
if(s1)
return detail::move_chars(
dest, src, n, *s0, *s1);
return detail::move_chars(
dest, src, n, *s0);
}
detail::move_chars(
dest, src, n);
}
//------------------------------------------------
// construct reference
url_base::
url_base(
detail::url_impl const& impl) noexcept
: url_view_base(impl)
{
}
void
url_base::
reserve_impl(std::size_t n)
{
op_t op(*this);
reserve_impl(n, op);
if(s_)
s_[size()] = '\0';
}
// make a copy of u
void
url_base::
copy(url_view_base const& u)
{
if (this == &u)
return;
op_t op(*this);
if(u.size() == 0)
{
clear();
return;
}
reserve_impl(
u.size(), op);
impl_ = u.impl_;
impl_.cs_ = s_;
impl_.from_ = {from::url};
std::memcpy(s_,
u.data(), u.size());
s_[size()] = '\0';
}
//------------------------------------------------
//
// Scheme
//
//------------------------------------------------
url_base&
url_base::
set_scheme(string_view s)
{
set_scheme_impl(
s, string_to_scheme(s));
return *this;
}
url_base&
url_base::
set_scheme_id(urls::scheme id)
{
if(id == urls::scheme::unknown)
detail::throw_invalid_argument();
if(id == urls::scheme::none)
return remove_scheme();
set_scheme_impl(to_string(id), id);
return *this;
}
url_base&
url_base::
remove_scheme()
{
op_t op(*this);
auto const sn = impl_.len(id_scheme);
if(sn == 0)
return *this;
auto const po = impl_.offset(id_path);
auto fseg = first_segment();
bool const encode_colon =
!has_authority() &&
impl_.nseg_ > 0 &&
s_[po] != '/' &&
fseg.contains(':');
if(!encode_colon)
{
// just remove the scheme
resize_impl(id_scheme, 0, op);
impl_.scheme_ = urls::scheme::none;
check_invariants();
return *this;
}
// encode any ":" in the first path segment
BOOST_ASSERT(sn >= 2);
auto pn = impl_.len(id_path);
std::size_t cn = 0;
for (char c: fseg)
cn += c == ':';
std::size_t new_size =
size() - sn + 2 * cn;
bool need_resize = new_size > size();
if (need_resize)
{
resize_impl(
id_path, pn + 2 * cn, op);
}
// move [id_scheme, id_path) left
op.move(
s_,
s_ + sn,
po - sn);
// move [id_path, id_query) left
auto qo = impl_.offset(id_query);
op.move(
s_ + po - sn,
s_ + po,
qo - po);
// move [id_query, id_end) left
op.move(
s_ + qo - sn + 2 * cn,
s_ + qo,
impl_.offset(id_end) - qo);
// adjust part offsets.
// (po and qo are invalidated)
if (need_resize)
{
impl_.adjust(id_user, id_end, 0 - sn);
}
else
{
impl_.adjust(id_user, id_path, 0 - sn);
impl_.adjust(id_query, id_end, 0 - sn + 2 * cn);
}
if (encode_colon)
{
// move the 2nd, 3rd, ... segments
auto begin = s_ + impl_.offset(id_path);
auto it = begin;
auto end = begin + pn;
while (*it != '/' &&
it != end)
++it;
// we don't need op here because this is
// an internal operation
std::memmove(it + (2 * cn), it, end - it);
// move 1st segment
auto src = s_ + impl_.offset(id_path) + pn;
auto dest = s_ + impl_.offset(id_query);
src -= end - it;
dest -= end - it;
pn -= end - it;
do {
--src;
--dest;
if (*src != ':')
{
*dest = *src;
}
else
{
// use uppercase as required by
// syntax-based normalization
*dest-- = 'A';
*dest-- = '3';
*dest = '%';
}
--pn;
} while (pn);
}
s_[size()] = '\0';
impl_.scheme_ = urls::scheme::none;
return *this;
}
//------------------------------------------------
//
// Authority
//
//------------------------------------------------
url_base&
url_base::
set_encoded_authority(
pct_string_view s)
{
op_t op(*this, &detail::ref(s));
authority_view a = grammar::parse(
s, authority_rule
).value(BOOST_URL_POS);
auto n = s.size() + 2;
auto const need_slash =
! is_path_absolute() &&
impl_.len(id_path) > 0;
if(need_slash)
++n;
auto dest = resize_impl(
id_user, id_path, n, op);
dest[0] = '/';
dest[1] = '/';
std::memcpy(dest + 2,
s.data(), s.size());
if(need_slash)
dest[n - 1] = '/';
impl_.apply_authority(a);
if(need_slash)
impl_.adjust(
id_query, id_end, 1);
return *this;
}
url_base&
url_base::
remove_authority()
{
if(! has_authority())
return *this;
op_t op(*this);
auto path = impl_.get(id_path);
bool const need_dot = path.starts_with("//");
if(need_dot)
{
// prepend "/.", can't throw
auto p = resize_impl(
id_user, id_path, 2, op);
p[0] = '/';
p[1] = '.';
impl_.split(id_user, 0);
impl_.split(id_pass, 0);
impl_.split(id_host, 0);
impl_.split(id_port, 0);
}
else
{
resize_impl(
id_user, id_path, 0, op);
}
impl_.host_type_ =
urls::host_type::none;
return *this;
}
//------------------------------------------------
//
// Userinfo
//
//------------------------------------------------
url_base&
url_base::
set_userinfo(
string_view s)
{
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s, detail::userinfo_chars, opt);
auto dest = set_userinfo_impl(n, op);
encode(
dest,
n,
s,
detail::userinfo_chars,
opt);
auto const pos = impl_.get(
id_user, id_host
).find_first_of(':');
if(pos != string_view::npos)
{
impl_.split(id_user, pos);
// find ':' in plain string
auto const pos2 =
s.find_first_of(':');
impl_.decoded_[id_user] =
pos2 - 1;
impl_.decoded_[id_pass] =
s.size() - pos2;
}
else
{
impl_.decoded_[id_user] = s.size();
impl_.decoded_[id_pass] = 0;
}
return *this;
}
url_base&
url_base::
set_encoded_userinfo(
pct_string_view s)
{
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const pos = s.find_first_of(':');
if(pos != string_view::npos)
{
// user:pass
auto const s0 = s.substr(0, pos);
auto const s1 = s.substr(pos + 1);
auto const n0 =
detail::re_encoded_size_unsafe(
s0,
detail::user_chars,
opt);
auto const n1 =
detail::re_encoded_size_unsafe(s1,
detail::password_chars,
opt);
auto dest =
set_userinfo_impl(n0 + n1 + 1, op);
impl_.decoded_[id_user] =
detail::re_encode_unsafe(
dest,
dest + n0,
s0,
detail::user_chars,
opt);
*dest++ = ':';
impl_.decoded_[id_pass] =
detail::re_encode_unsafe(
dest,
dest + n1,
s1,
detail::password_chars,
opt);
impl_.split(id_user, 2 + n0);
}
else
{
// user
auto const n =
detail::re_encoded_size_unsafe(
s, detail::user_chars, opt);
auto dest = set_userinfo_impl(n, op);
impl_.decoded_[id_user] =
detail::re_encode_unsafe(
dest,
dest + n,
s,
detail::user_chars,
opt);
impl_.split(id_user, 2 + n);
impl_.decoded_[id_pass] = 0;
}
return *this;
}
url_base&
url_base::
remove_userinfo() noexcept
{
if(impl_.len(id_pass) == 0)
return *this; // no userinfo
op_t op(*this);
// keep authority '//'
resize_impl(
id_user, id_host, 2, op);
impl_.decoded_[id_user] = 0;
impl_.decoded_[id_pass] = 0;
return *this;
}
//------------------------------------------------
url_base&
url_base::
set_user(string_view s)
{
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s, detail::user_chars, opt);
auto dest = set_user_impl(n, op);
encode_unsafe(
dest,
n,
s,
detail::user_chars,
opt);
impl_.decoded_[id_user] = s.size();
return *this;
}
url_base&
url_base::
set_encoded_user(
pct_string_view s)
{
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const n =
detail::re_encoded_size_unsafe(
s, detail::user_chars, opt);
auto dest = set_user_impl(n, op);
impl_.decoded_[id_user] =
detail::re_encode_unsafe(
dest,
dest + n,
s,
detail::user_chars,
opt);
BOOST_ASSERT(
impl_.decoded_[id_user] ==
s.decoded_size());
return *this;
}
//------------------------------------------------
url_base&
url_base::
set_password(string_view s)
{
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s, detail::password_chars, opt);
auto dest = set_password_impl(n, op);
encode_unsafe(
dest,
n,
s,
detail::password_chars,
opt);
impl_.decoded_[id_pass] = s.size();
return *this;
}
url_base&
url_base::
set_encoded_password(
pct_string_view s)
{
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const n =
detail::re_encoded_size_unsafe(
s,
detail::password_chars,
opt);
auto dest = set_password_impl(n, op);
impl_.decoded_[id_pass] =
detail::re_encode_unsafe(
dest,
dest + n,
s,
detail::password_chars,
opt);
BOOST_ASSERT(
impl_.decoded_[id_pass] ==
s.decoded_size());
return *this;
}
url_base&
url_base::
remove_password() noexcept
{
auto const n = impl_.len(id_pass);
if(n < 2)
return *this; // no password
op_t op(*this);
// clear password, retain '@'
auto dest =
resize_impl(id_pass, 1, op);
dest[0] = '@';
impl_.decoded_[id_pass] = 0;
return *this;
}
//------------------------------------------------
//
// Host
//
//------------------------------------------------
/*
host_type host_type() // ipv4, ipv6, ipvfuture, name
std::string host() // return encoded_host().decode()
pct_string_view encoded_host() // return host part, as-is
std::string host_address() // return encoded_host_address().decode()
pct_string_view encoded_host_address() // ipv4, ipv6, ipvfut, or encoded name, no brackets
ipv4_address host_ipv4_address() // return ipv4_address or {}
ipv6_address host_ipv6_address() // return ipv6_address or {}
string_view host_ipvfuture() // return ipvfuture or {}
std::string host_name() // return decoded name or ""
pct_string_view encoded_host_name() // return encoded host name or ""
--------------------------------------------------
set_host( string_view ) // set host part from plain text
set_encoded_host( pct_string_view ) // set host part from encoded text
set_host_address( string_view ) // set host from ipv4, ipv6, ipvfut, or plain reg-name string
set_encoded_host_address( pct_string_view ) // set host from ipv4, ipv6, ipvfut, or encoded reg-name string
set_host_ipv4( ipv4_address ) // set ipv4
set_host_ipv6( ipv6_address ) // set ipv6
set_host_ipvfuture( string_view ) // set ipvfuture
set_host_name( string_view ) // set name from plain
set_encoded_host_name( pct_string_view ) // set name from encoded
*/
// set host part from plain text
url_base&
url_base::
set_host(
string_view s)
{
if( s.size() > 2 &&
s.front() == '[' &&
s.back() == ']')
{
// IP-literal
{
// IPv6-address
auto rv = parse_ipv6_address(
s.substr(1, s.size() - 2));
if(rv)
return set_host_ipv6(*rv);
}
{
// IPvFuture
auto rv = grammar::parse(
s.substr(1, s.size() - 2),
detail::ipvfuture_rule);
if(rv)
return set_host_ipvfuture(rv->str);
}
}
else if(s.size() >= 7) // "0.0.0.0"
{
// IPv4-address
auto rv = parse_ipv4_address(s);
if(rv)
return set_host_ipv4(*rv);
}
// reg-name
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s, detail::host_chars, opt);
auto dest = set_host_impl(n, op);
encode(
dest,
impl_.get(id_path).data() - dest,
s,
detail::host_chars,
opt);
impl_.decoded_[id_host] = s.size();
impl_.host_type_ =
urls::host_type::name;
return *this;
}
// set host part from encoded text
url_base&
url_base::
set_encoded_host(
pct_string_view s)
{
if( s.size() > 2 &&
s.front() == '[' &&
s.back() == ']')
{
// IP-literal
{
// IPv6-address
auto rv = parse_ipv6_address(
s.substr(1, s.size() - 2));
if(rv)
return set_host_ipv6(*rv);
}
{
// IPvFuture
auto rv = grammar::parse(
s.substr(1, s.size() - 2),
detail::ipvfuture_rule);
if(rv)
return set_host_ipvfuture(rv->str);
}
}
else if(s.size() >= 7) // "0.0.0.0"
{
// IPv4-address
auto rv = parse_ipv4_address(s);
if(rv)
return set_host_ipv4(*rv);
}
// reg-name
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const n = detail::re_encoded_size_unsafe(
s, detail::host_chars, opt);
auto dest = set_host_impl(n, op);
impl_.decoded_[id_host] =
detail::re_encode_unsafe(
dest,
impl_.get(id_path).data(),
s,
detail::host_chars,
opt);
BOOST_ASSERT(impl_.decoded_[id_host] ==
s.decoded_size());
impl_.host_type_ =
urls::host_type::name;
return *this;
}
url_base&
url_base::
set_host_address(
string_view s)
{
{
// IPv6-address
auto rv = parse_ipv6_address(s);
if(rv)
return set_host_ipv6(*rv);
}
{
// IPvFuture
auto rv = grammar::parse(
s, detail::ipvfuture_rule);
if(rv)
return set_host_ipvfuture(rv->str);
}
if(s.size() >= 7) // "0.0.0.0"
{
// IPv4-address
auto rv = parse_ipv4_address(s);
if(rv)
return set_host_ipv4(*rv);
}
// reg-name
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s, detail::host_chars, opt);
auto dest = set_host_impl(n, op);
encode(
dest,
impl_.get(id_path).data() - dest,
s,
detail::host_chars,
opt);
impl_.decoded_[id_host] = s.size();
impl_.host_type_ =
urls::host_type::name;
return *this;
}
url_base&
url_base::
set_encoded_host_address(
pct_string_view s)
{
{
// IPv6-address
auto rv = parse_ipv6_address(s);
if(rv)
return set_host_ipv6(*rv);
}
{
// IPvFuture
auto rv = grammar::parse(
s, detail::ipvfuture_rule);
if(rv)
return set_host_ipvfuture(rv->str);
}
if(s.size() >= 7) // "0.0.0.0"
{
// IPv4-address
auto rv = parse_ipv4_address(s);
if(rv)
return set_host_ipv4(*rv);
}
// reg-name
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const n = detail::re_encoded_size_unsafe(
s, detail::host_chars, opt);
auto dest = set_host_impl(n, op);
impl_.decoded_[id_host] =
detail::re_encode_unsafe(
dest,
impl_.get(id_path).data(),
s,
detail::host_chars,
opt);
BOOST_ASSERT(impl_.decoded_[id_host] ==
s.decoded_size());
impl_.host_type_ =
urls::host_type::name;
return *this;
}
url_base&
url_base::
set_host_ipv4(
ipv4_address const& addr)
{
op_t op(*this);
char buf[urls::ipv4_address::max_str_len];
auto s = addr.to_buffer(buf, sizeof(buf));
auto dest = set_host_impl(s.size(), op);
std::memcpy(dest, s.data(), s.size());
impl_.decoded_[id_host] = impl_.len(id_host);
impl_.host_type_ = urls::host_type::ipv4;
auto bytes = addr.to_bytes();
std::memcpy(
impl_.ip_addr_,
bytes.data(),
bytes.size());
return *this;
}
url_base&
url_base::
set_host_ipv6(
ipv6_address const& addr)
{
op_t op(*this);
char buf[2 +
urls::ipv6_address::max_str_len];
auto s = addr.to_buffer(
buf + 1, sizeof(buf) - 2);
buf[0] = '[';
buf[s.size() + 1] = ']';
auto const n = s.size() + 2;
auto dest = set_host_impl(n, op);
std::memcpy(dest, buf, n);
impl_.decoded_[id_host] = n;
impl_.host_type_ = urls::host_type::ipv6;
auto bytes = addr.to_bytes();
std::memcpy(
impl_.ip_addr_,
bytes.data(),
bytes.size());
return *this;
}
url_base&
url_base::
set_host_ipvfuture(
string_view s)
{
op_t op(*this, &s);
// validate
grammar::parse(s,
detail::ipvfuture_rule
).value(BOOST_URL_POS);
auto dest = set_host_impl(
s.size() + 2, op);
*dest++ = '[';
dest += s.copy(dest, s.size());
*dest = ']';
impl_.host_type_ =
urls::host_type::ipvfuture;
impl_.decoded_[id_host] = s.size() + 2;
return *this;
}
url_base&
url_base::
set_host_name(
string_view s)
{
bool is_ipv4 = false;
if(s.size() >= 7) // "0.0.0.0"
{
// IPv4-address
if(parse_ipv4_address(s).has_value())
is_ipv4 = true;
}
auto allowed = detail::host_chars;
if(is_ipv4)
allowed = allowed - '.';
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s, allowed, opt);
auto dest = set_host_impl(n, op);
encode_unsafe(
dest,
n,
s,
allowed,
opt);
impl_.host_type_ =
urls::host_type::name;
impl_.decoded_[id_host] = s.size();
return *this;
}
url_base&
url_base::
set_encoded_host_name(
pct_string_view s)
{
bool is_ipv4 = false;
if(s.size() >= 7) // "0.0.0.0"
{
// IPv4-address
if(parse_ipv4_address(s).has_value())
is_ipv4 = true;
}
auto allowed = detail::host_chars;
if(is_ipv4)
allowed = allowed - '.';
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const n = detail::re_encoded_size_unsafe(
s, allowed, opt);
auto dest = set_host_impl(n, op);
impl_.decoded_[id_host] =
detail::re_encode_unsafe(
dest,
dest + n,
s,
allowed,
opt);
BOOST_ASSERT(
impl_.decoded_[id_host] ==
s.decoded_size());
impl_.host_type_ =
urls::host_type::name;
return *this;
}
//------------------------------------------------
url_base&
url_base::
set_port_number(
std::uint16_t n)
{
op_t op(*this);
auto s =
detail::make_printed(n);
auto dest = set_port_impl(
s.string().size(), op);
std::memcpy(
dest, s.string().data(),
s.string().size());
impl_.port_number_ = n;
return *this;
}
url_base&
url_base::
set_port(
string_view s)
{
op_t op(*this, &s);
auto t = grammar::parse(s,
detail::port_rule{}
).value(BOOST_URL_POS);
auto dest =
set_port_impl(t.str.size(), op);
std::memcpy(dest,
t.str.data(), t.str.size());
if(t.has_number)
impl_.port_number_ = t.number;
else
impl_.port_number_ = 0;
return *this;
}
url_base&
url_base::
remove_port() noexcept
{
op_t op(*this);
resize_impl(id_port, 0, op);
impl_.port_number_ = 0;
return *this;
}
//------------------------------------------------
//
// Compound Fields
//
//------------------------------------------------
url_base&
url_base::
remove_origin()
{
// these two calls perform 2 memmoves instead of 1
remove_authority();
remove_scheme();
return *this;
}
//------------------------------------------------
//
// Path
//
//------------------------------------------------
bool
url_base::
set_path_absolute(
bool absolute)
{
op_t op(*this);
// check if path empty
if(impl_.len(id_path) == 0)
{
if(! absolute)
{
// already not absolute
return true;
}
// add '/'
auto dest = resize_impl(
id_path, 1, op);
*dest = '/';
++impl_.decoded_[id_path];
return true;
}
// check if path absolute
if(s_[impl_.offset(id_path)] == '/')
{
if(absolute)
{
// already absolute
return true;
}
if( has_authority() &&
impl_.len(id_path) > 1)
{
// can't do it, paths are always
// absolute when authority present!
return false;
}
auto p = encoded_path();
auto pos = p.find_first_of(":/", 1);
if (pos != string_view::npos &&
p[pos] == ':')
{
// prepend with .
auto n = impl_.len(id_path);
resize_impl(id_path, n + 1, op);
std::memmove(
s_ + impl_.offset(id_path) + 1,
s_ + impl_.offset(id_path), n);
*(s_ + impl_.offset(id_path)) = '.';
++impl_.decoded_[id_path];
return true;
}
// remove '/'
auto n = impl_.len(id_port);
impl_.split(id_port, n + 1);
resize_impl(id_port, n, op);
--impl_.decoded_[id_path];
return true;
}
if(! absolute)
{
// already not absolute
return true;
}
// add '/'
auto n = impl_.len(id_port);
auto dest = resize_impl(
id_port, n + 1, op) + n;
impl_.split(id_port, n);
*dest = '/';
++impl_.decoded_[id_path];
return true;
}
url_base&
url_base::
set_path(
string_view s)
{
edit_segments(
detail::segments_iter_impl(
detail::path_ref(impl_)),
detail::segments_iter_impl(
detail::path_ref(impl_), 0),
detail::path_iter(s),
s.starts_with('/'));
return *this;
}
url_base&
url_base::
set_encoded_path(
pct_string_view s)
{
edit_segments(
detail::segments_iter_impl(
detail::path_ref(impl_)),
detail::segments_iter_impl(
detail::path_ref(impl_), 0),
detail::path_encoded_iter(s),
s.starts_with('/'));
return *this;
}
segments_ref
url_base::
segments() noexcept
{
return {*this};
}
segments_encoded_ref
url_base::
encoded_segments() noexcept
{
return {*this};
}
//------------------------------------------------
//
// Query
//
//------------------------------------------------
url_base&
url_base::
set_query(
string_view s)
{
edit_params(
detail::params_iter_impl(impl_),
detail::params_iter_impl(impl_, 0),
detail::query_iter(s, true));
return *this;
}
url_base&
url_base::
set_encoded_query(
pct_string_view s)
{
op_t op(*this);
encoding_opts opt;
std::size_t n = 0; // encoded size
std::size_t nparam = 1; // param count
auto const end = s.end();
auto p = s.begin();
// measure
while(p != end)
{
if(*p == '&')
{
++p;
++n;
++nparam;
}
else if(*p != '%')
{
if(detail::query_chars(*p))
n += 1; // allowed
else
n += 3; // escaped
++p;
}
else
{
// escape
n += 3;
p += 3;
}
}
// resize
auto dest = resize_impl(
id_query, n + 1, op);
*dest++ = '?';
// encode
impl_.decoded_[id_query] =
detail::re_encode_unsafe(
dest,
dest + n,
s,
detail::query_chars,
opt);
BOOST_ASSERT(
impl_.decoded_[id_query] ==
s.decoded_size());
impl_.nparam_ = nparam;
return *this;
}
params_ref
url_base::
params() noexcept
{
return params_ref(
*this,
encoding_opts{
true, false, false});
}
params_ref
url_base::
params(encoding_opts opt) noexcept
{
return params_ref(*this, opt);
}
params_encoded_ref
url_base::
encoded_params() noexcept
{
return {*this};
}
url_base&
url_base::
set_params( std::initializer_list<param_view> ps ) noexcept
{
params().assign(ps);
return *this;
}
url_base&
url_base::
set_encoded_params( std::initializer_list< param_pct_view > ps ) noexcept
{
encoded_params().assign(ps);
return *this;
}
url_base&
url_base::
remove_query() noexcept
{
op_t op(*this);
resize_impl(id_query, 0, op);
impl_.nparam_ = 0;
impl_.decoded_[id_query] = 0;
return *this;
}
//------------------------------------------------
//
// Fragment
//
//------------------------------------------------
url_base&
url_base::
remove_fragment() noexcept
{
op_t op(*this);
resize_impl(id_frag, 0, op);
impl_.decoded_[id_frag] = 0;
return *this;
}
url_base&
url_base::
set_fragment(string_view s)
{
op_t op(*this, &s);
encoding_opts opt;
auto const n = encoded_size(
s,
detail::fragment_chars,
opt);
auto dest = resize_impl(
id_frag, n + 1, op);
*dest++ = '#';
encode_unsafe(
dest,
n,
s,
detail::fragment_chars,
opt);
impl_.decoded_[id_frag] = s.size();
return *this;
}
url_base&
url_base::
set_encoded_fragment(
pct_string_view s)
{
op_t op(*this, &detail::ref(s));
encoding_opts opt;
auto const n =
detail::re_encoded_size_unsafe(
s,
detail::fragment_chars,
opt);
auto dest = resize_impl(
id_frag, n + 1, op);
*dest++ = '#';
impl_.decoded_[id_frag] =
detail::re_encode_unsafe(
dest,
dest + n,
s,
detail::fragment_chars,
opt);
BOOST_ASSERT(
impl_.decoded_[id_frag] ==
s.decoded_size());
return *this;
}
//------------------------------------------------
//
// Resolution
//
//------------------------------------------------
result<void>
url_base::
resolve(
url_view_base const& ref)
{
if (this == &ref &&
has_scheme())
{
normalize_path();
return {};
}
if(! has_scheme())
{
BOOST_URL_RETURN_EC(error::not_a_base);
}
op_t op(*this);
//
// 5.2.2. Transform References
// https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2
//
if(ref.has_scheme())
{
reserve_impl(ref.size(), op);
copy(ref);
normalize_path();
return {};
}
if(ref.has_authority())
{
reserve_impl(
impl_.offset(id_user) + ref.size(), op);
set_encoded_authority(
ref.encoded_authority());
set_encoded_path(
ref.encoded_path());
if (ref.encoded_path().empty())
set_path_absolute(false);
else
normalize_path();
if(ref.has_query())
set_encoded_query(
ref.encoded_query());
else
remove_query();
if(ref.has_fragment())
set_encoded_fragment(
ref.encoded_fragment());
else
remove_fragment();
return {};
}
if(ref.encoded_path().empty())
{
reserve_impl(
impl_.offset(id_query) +
ref.size(), op);
normalize_path();
if(ref.has_query())
{
set_encoded_query(
ref.encoded_query());
}
if(ref.has_fragment())
set_encoded_fragment(
ref.encoded_fragment());
return {};
}
if(ref.is_path_absolute())
{
reserve_impl(
impl_.offset(id_path) +
ref.size(), op);
set_encoded_path(
ref.encoded_path());
normalize_path();
if(ref.has_query())
set_encoded_query(
ref.encoded_query());
else
remove_query();
if(ref.has_fragment())
set_encoded_fragment(
ref.encoded_fragment());
else
remove_fragment();
return {};
}
// General case: ref is relative path
reserve_impl(
impl_.offset(id_query) +
ref.size(), op);
// 5.2.3. Merge Paths
auto es = encoded_segments();
if(es.size() > 0)
{
es.pop_back();
}
es.insert(es.end(),
ref.encoded_segments().begin(),
ref.encoded_segments().end());
normalize_path();
if(ref.has_query())
set_encoded_query(
ref.encoded_query());
else
remove_query();
if(ref.has_fragment())
set_encoded_fragment(
ref.encoded_fragment());
else
remove_fragment();
return {};
}
//------------------------------------------------
//
// Normalization
//
//------------------------------------------------
template <class Charset>
void
url_base::
normalize_octets_impl(
int id,
Charset const& allowed,
op_t& op) noexcept
{
char* it = s_ + impl_.offset(id);
char* end = s_ + impl_.offset(id + 1);
char d = 0;
char* dest = it;
while (it < end)
{
if (*it != '%')
{
*dest = *it;
++it;
++dest;
continue;
}
BOOST_ASSERT(end - it >= 3);
// decode unreserved octets
d = detail::decode_one(it + 1);
if (allowed(d))
{
*dest = d;
it += 3;
++dest;
continue;
}
// uppercase percent-encoding triplets
*dest++ = '%';
++it;
*dest++ = grammar::to_upper(*it++);
*dest++ = grammar::to_upper(*it++);
}
if (it != dest)
{
auto diff = it - dest;
auto n = impl_.len(id) - diff;
shrink_impl(id, n, op);
s_[size()] = '\0';
}
}
url_base&
url_base::
normalize_scheme()
{
to_lower_impl(id_scheme);
return *this;
}
url_base&
url_base::
normalize_authority()
{
op_t op(*this);
// normalize host
if (host_type() == urls::host_type::name)
{
normalize_octets_impl(
id_host,
detail::reg_name_chars, op);
}
decoded_to_lower_impl(id_host);
// normalize password
normalize_octets_impl(id_pass, detail::password_chars, op);
// normalize user
normalize_octets_impl(id_user, detail::user_chars, op);
return *this;
}
url_base&
url_base::
normalize_path()
{
op_t op(*this);
normalize_octets_impl(id_path, detail::path_chars, op);
string_view p = impl_.get(id_path);
char* p_dest = s_ + impl_.offset(id_path);
char* p_end = s_ + impl_.offset(id_path + 1);
auto pn = p.size();
auto skip_dot = 0;
bool encode_colons = false;
string_view first_seg;
//------------------------------------------------
//
// Determine unnecessary initial dot segments to skip and
// if we need to encode colons in the first segment
//
if (
!has_authority() &&
p.starts_with("/./"))
{
// check if removing the "/./" would result in "//"
// ex: "/.//", "/././/", "/././/", ...
skip_dot = 2;
while (p.substr(skip_dot, 3).starts_with("/./"))
skip_dot += 2;
if (p.substr(skip_dot).starts_with("//"))
skip_dot = 2;
else
skip_dot = 0;
}
else if (
!has_scheme())
{
if (p.starts_with("./"))
{
// check if removing the "./" would result in "//"
// ex: ".//", "././/", "././/", ...
skip_dot = 1;
while (p.substr(skip_dot, 3).starts_with("/./"))
skip_dot += 2;
if (p.substr(skip_dot).starts_with("//"))
skip_dot = 2;
else
skip_dot = 0;
if ( !skip_dot )
{
// check if removing "./"s would leave us
// a first segment with an ambiguous ":"
first_seg = p.substr(2);
while (first_seg.starts_with("./"))
first_seg = first_seg.substr(2);
auto i = first_seg.find('/');
if (i != string_view::npos)
first_seg = first_seg.substr(0, i);
encode_colons = first_seg.contains(':');
}
}
else
{
// check if normalize_octets_impl
// didn't already create a ":"
// in the first segment
first_seg = p;
auto i = first_seg.find('/');
if (i != string_view::npos)
first_seg = p.substr(0, i);
encode_colons = first_seg.contains(':');
}
}
//------------------------------------------------
//
// Encode colons in the first segment
//
if (encode_colons)
{
// prepend with "./"
// (resize_impl never throws)
auto cn =
std::count(
first_seg.begin(),
first_seg.end(),
':');
resize_impl(
id_path, pn + (2 * cn), op);
// move the 2nd, 3rd, ... segments
auto begin = s_ + impl_.offset(id_path);
auto it = begin;
auto end = begin + pn;
while (string_view(it, 2) == "./")
it += 2;
while (*it != '/' &&
it != end)
++it;
// we don't need op here because this is
// an internal operation
std::memmove(it + (2 * cn), it, end - it);
// move 1st segment
auto src = s_ + impl_.offset(id_path) + pn;
auto dest = s_ + impl_.offset(id_query);
src -= end - it;
dest -= end - it;
pn -= end - it;
do {
--src;
--dest;
if (*src != ':')
{
*dest = *src;
}
else
{
// use uppercase as required by
// syntax-based normalization
*dest-- = 'A';
*dest-- = '3';
*dest = '%';
}
--pn;
} while (pn);
skip_dot = 0;
p = impl_.get(id_path);
pn = p.size();
p_dest = s_ + impl_.offset(id_path);
p_end = s_ + impl_.offset(id_path + 1);
}
//------------------------------------------------
//
// Remove "." and ".." segments
//
p.remove_prefix(skip_dot);
p_dest += skip_dot;
auto n = detail::remove_dot_segments(
p_dest, p_end, p);
//------------------------------------------------
//
// Update path parameters
//
if (n != pn)
{
BOOST_ASSERT(n < pn);
shrink_impl(id_path, n + skip_dot, op);
p = encoded_path();
if (p == "/")
impl_.nseg_ = 0;
else if (!p.empty())
impl_.nseg_ = std::count(
p.begin() + 1, p.end(), '/') + 1;
else
impl_.nseg_ = 0;
impl_.decoded_[id_path] =
detail::decode_bytes_unsafe(impl_.get(id_path));
}
return *this;
}
url_base&
url_base::
normalize_query()
{
op_t op(*this);
normalize_octets_impl(
id_query, detail::query_chars, op);
return *this;
}
url_base&
url_base::
normalize_fragment()
{
op_t op(*this);
normalize_octets_impl(
id_frag, detail::fragment_chars, op);
return *this;
}
url_base&
url_base::
normalize()
{
normalize_fragment();
normalize_query();
normalize_path();
normalize_authority();
normalize_scheme();
return *this;
}
//------------------------------------------------
//
// Implementation
//
//------------------------------------------------
void
url_base::
check_invariants() const noexcept
{
BOOST_ASSERT(pi_);
BOOST_ASSERT(
impl_.len(id_scheme) == 0 ||
impl_.get(id_scheme).ends_with(':'));
BOOST_ASSERT(
impl_.len(id_user) == 0 ||
impl_.get(id_user).starts_with("//"));
BOOST_ASSERT(
impl_.len(id_pass) == 0 ||
impl_.get(id_user).starts_with("//"));
BOOST_ASSERT(
impl_.len(id_pass) == 0 ||
(impl_.len(id_pass) == 1 &&
impl_.get(id_pass) == "@") ||
(impl_.len(id_pass) > 1 &&
impl_.get(id_pass).starts_with(':') &&
impl_.get(id_pass).ends_with('@')));
BOOST_ASSERT(
impl_.len(id_user, id_path) == 0 ||
impl_.get(id_user).starts_with("//"));
BOOST_ASSERT(impl_.decoded_[id_path] >=
((impl_.len(id_path) + 2) / 3));
BOOST_ASSERT(
impl_.len(id_port) == 0 ||
impl_.get(id_port).starts_with(':'));
BOOST_ASSERT(
impl_.len(id_query) == 0 ||
impl_.get(id_query).starts_with('?'));
BOOST_ASSERT(
(impl_.len(id_query) == 0 && impl_.nparam_ == 0) ||
(impl_.len(id_query) > 0 && impl_.nparam_ > 0));
BOOST_ASSERT(
impl_.len(id_frag) == 0 ||
impl_.get(id_frag).starts_with('#'));
BOOST_ASSERT(c_str()[size()] == '\0');
}
char*
url_base::
resize_impl(
int id,
std::size_t new_size,
op_t& op)
{
return resize_impl(
id, id + 1, new_size, op);
}
char*
url_base::
resize_impl(
int first,
int last,
std::size_t new_len,
op_t& op)
{
auto const n0 = impl_.len(first, last);
if(new_len == 0 && n0 == 0)
return s_ + impl_.offset(first);
if(new_len <= n0)
return shrink_impl(
first, last, new_len, op);
// growing
std::size_t n = new_len - n0;
reserve_impl(size() + n, op);
auto const pos =
impl_.offset(last);
// adjust chars
op.move(
s_ + pos + n,
s_ + pos,
impl_.offset(id_end) -
pos + 1);
// collapse (first, last)
impl_.collapse(first, last,
impl_.offset(last) + n);
// shift (last, end) right
impl_.adjust(last, id_end, n);
s_[size()] = '\0';
return s_ + impl_.offset(first);
}
char*
url_base::
shrink_impl(
int id,
std::size_t new_size,
op_t& op)
{
return shrink_impl(
id, id + 1, new_size, op);
}
char*
url_base::
shrink_impl(
int first,
int last,
std::size_t new_len,
op_t& op)
{
// shrinking
auto const n0 = impl_.len(first, last);
BOOST_ASSERT(new_len <= n0);
std::size_t n = n0 - new_len;
auto const pos =
impl_.offset(last);
// adjust chars
op.move(
s_ + pos - n,
s_ + pos,
impl_.offset(
id_end) - pos + 1);
// collapse (first, last)
impl_.collapse(first, last,
impl_.offset(last) - n);
// shift (last, end) left
impl_.adjust(
last, id_end, 0 - n);
s_[size()] = '\0';
return s_ + impl_.offset(first);
}
//------------------------------------------------
void
url_base::
set_scheme_impl(
string_view s,
urls::scheme id)
{
op_t op(*this, &s);
check_invariants();
grammar::parse(
s, detail::scheme_rule()
).value(BOOST_URL_POS);
auto const n = s.size();
auto const p = impl_.offset(id_path);
// check for "./" prefix
bool const has_dot =
[this, p]
{
if(impl_.nseg_ == 0)
return false;
if(first_segment().size() < 2)
return false;
auto const src = s_ + p;
if(src[0] != '.')
return false;
if(src[1] != '/')
return false;
return true;
}();
// Remove "./"
if(has_dot)
{
// do this first, for
// strong exception safety
reserve_impl(
size() + n + 1 - 2, op);
op.move(
s_ + p,
s_ + p + 2,
size() + 1 -
(p + 2));
impl_.set_size(
id_path,
impl_.len(id_path) - 2);
s_[size()] = '\0';
}
auto dest = resize_impl(
id_scheme, n + 1, op);
s.copy(dest, n);
dest[n] = ':';
impl_.scheme_ = id;
check_invariants();
}
char*
url_base::
set_user_impl(
std::size_t n,
op_t& op)
{
check_invariants();
if(impl_.len(id_pass) != 0)
{
// keep "//"
auto dest = resize_impl(
id_user, 2 + n, op);
check_invariants();
return dest + 2;
}
// add authority
auto dest = resize_impl(
id_user, 2 + n + 1, op);
impl_.split(id_user, 2 + n);
dest[0] = '/';
dest[1] = '/';
dest[2 + n] = '@';
check_invariants();
return dest + 2;
}
char*
url_base::
set_password_impl(
std::size_t n,
op_t& op)
{
check_invariants();
if(impl_.len(id_user) != 0)
{
// already have authority
auto const dest = resize_impl(
id_pass, 1 + n + 1, op);
dest[0] = ':';
dest[n + 1] = '@';
check_invariants();
return dest + 1;
}
// add authority
auto const dest =
resize_impl(
id_user, id_host,
2 + 1 + n + 1, op);
impl_.split(id_user, 2);
dest[0] = '/';
dest[1] = '/';
dest[2] = ':';
dest[2 + n + 1] = '@';
check_invariants();
return dest + 3;
}
char*
url_base::
set_userinfo_impl(
std::size_t n,
op_t& op)
{
// "//" {dest} "@"
check_invariants();
auto dest = resize_impl(
id_user, id_host, n + 3, op);
impl_.split(id_user, n + 2);
dest[0] = '/';
dest[1] = '/';
dest[n + 2] = '@';
check_invariants();
return dest + 2;
}
char*
url_base::
set_host_impl(
std::size_t n,
op_t& op)
{
check_invariants();
if(impl_.len(id_user) == 0)
{
// add authority
auto dest = resize_impl(
id_user, n + 2, op);
impl_.split(id_user, 2);
impl_.split(id_pass, 0);
dest[0] = '/';
dest[1] = '/';
check_invariants();
return dest + 2;
}
// already have authority
auto const dest = resize_impl(
id_host, n, op);
check_invariants();
return dest;
}
char*
url_base::
set_port_impl(
std::size_t n,
op_t& op)
{
check_invariants();
if(impl_.len(id_user) != 0)
{
// authority exists
auto dest = resize_impl(
id_port, n + 1, op);
dest[0] = ':';
check_invariants();
return dest + 1;
}
auto dest = resize_impl(
id_user, 3 + n, op);
impl_.split(id_user, 2);
impl_.split(id_pass, 0);
impl_.split(id_host, 0);
dest[0] = '/';
dest[1] = '/';
dest[2] = ':';
check_invariants();
return dest + 3;
}
//------------------------------------------------
// return the first segment of the path.
// this is needed for some algorithms.
string_view
url_base::
first_segment() const noexcept
{
if(impl_.nseg_ == 0)
return {};
auto const p0 = impl_.cs_ +
impl_.offset(id_path) +
detail::path_prefix(
impl_.get(id_path));
auto const end = impl_.cs_ +
impl_.offset(id_query);
if(impl_.nseg_ == 1)
return string_view(
p0, end - p0);
auto p = p0;
while(*p != '/')
++p;
BOOST_ASSERT(p < end);
return string_view(p0, p - p0);
}
detail::segments_iter_impl
url_base::
edit_segments(
detail::segments_iter_impl const& it0,
detail::segments_iter_impl const& it1,
detail::any_segments_iter&& src,
// -1 = preserve
// 0 = make relative (can fail)
// 1 = make absolute
int absolute)
{
// Iterator doesn't belong to this url
BOOST_ASSERT(it0.ref.alias_of(impl_));
// Iterator doesn't belong to this url
BOOST_ASSERT(it1.ref.alias_of(impl_));
// Iterator is in the wrong order
BOOST_ASSERT(it0.index <= it1.index);
// Iterator is out of range
BOOST_ASSERT(it0.index <= impl_.nseg_);
BOOST_ASSERT(it0.pos <= impl_.len(id_path));
// Iterator is out of range
BOOST_ASSERT(it1.index <= impl_.nseg_);
BOOST_ASSERT(it1.pos <= impl_.len(id_path));
//------------------------------------------------
//
// Calculate output prefix
//
// 0 = ""
// 1 = "/"
// 2 = "./"
// 3 = "/./"
//
bool const is_abs = is_path_absolute();
if(has_authority())
{
// Check if the new
// path would be empty
if( src.fast_nseg == 0 &&
it0.index == 0 &&
it1.index == impl_.nseg_)
{
// VFALCO we don't have
// access to nchar this early
//
//BOOST_ASSERT(nchar == 0);
absolute = 0;
}
else
{
// prefix "/" required
absolute = 1;
}
}
else if(absolute < 0)
{
absolute = is_abs; // preserve
}
auto const path_pos = impl_.offset(id_path);
std::size_t nchar = 0;
std::size_t prefix = 0;
bool encode_colons = false;
bool cp_src_prefix = false;
if(it0.index > 0)
{
// first segment unchanged
prefix = src.fast_nseg > 0;
}
else if(src.fast_nseg > 0)
{
// first segment from src
if(! src.front.empty())
{
if( src.front == "." &&
src.fast_nseg > 1)
if (src.s.empty())
{
// if front is ".", we need the extra "." in the prefix
// which will maintain the invariant that segments represent
// {"."}
prefix = 2 + absolute;
}
else
{
// if the "." prefix is explicitly required from set_path
// we do not include an extra "." segment
prefix = absolute;
cp_src_prefix = true;
}
else if(absolute)
prefix = 1;
else if(has_scheme() ||
! src.front.contains(':'))
prefix = 0;
else
{
prefix = 0;
encode_colons = true;
}
}
else
{
prefix = 2 + absolute;
}
}
else
{
// first segment from it1
auto const p =
impl_.cs_ + path_pos + it1.pos;
switch(impl_.cs_ +
impl_.offset(id_query) - p)
{
case 0:
// points to end
prefix = absolute;
break;
default:
BOOST_ASSERT(*p == '/');
if(p[1] != '/')
{
if(absolute)
prefix = 1;
else if(has_scheme() ||
! it1.dereference().contains(':'))
prefix = 0;
else
prefix = 2;
break;
}
// empty
BOOST_FALLTHROUGH;
case 1:
// empty
BOOST_ASSERT(*p == '/');
prefix = 2 + absolute;
break;
}
}
// append '/' to new segs
// if inserting at front.
std::size_t const suffix =
it1.index == 0 &&
impl_.nseg_ > 0 &&
src.fast_nseg > 0;
//------------------------------------------------
//
// Measure the number of encoded characters
// of output, and the number of inserted
// segments including internal separators.
//
src.encode_colons = encode_colons;
std::size_t nseg = 0;
if(src.measure(nchar))
{
src.encode_colons = false;
for(;;)
{
++nseg;
if(! src.measure(nchar))
break;
++nchar;
}
}
switch(src.fast_nseg)
{
case 0:
BOOST_ASSERT(nseg == 0);
break;
case 1:
BOOST_ASSERT(nseg == 1);
break;
case 2:
BOOST_ASSERT(nseg >= 2);
break;
}
//------------------------------------------------
//
// Calculate [pos0, pos1) to remove
//
auto pos0 = it0.pos;
if(it0.index == 0)
{
// patch pos for prefix
pos0 = 0;
}
auto pos1 = it1.pos;
if(it1.index == 0)
{
// patch pos for prefix
pos1 = detail::path_prefix(
impl_.get(id_path));
}
else if(
it0.index == 0 &&
it1.index < impl_.nseg_ &&
nseg == 0)
{
// Remove the slash from segment it1
// if it is becoming the new first
// segment.
++pos1;
}
// calc decoded size of old range
auto const dn0 =
detail::decode_bytes_unsafe(
string_view(
impl_.cs_ +
impl_.offset(id_path) +
pos0,
pos1 - pos0));
//------------------------------------------------
//
// Resize
//
op_t op(*this, &src.s);
char* dest;
char const* end;
{
auto const nremove = pos1 - pos0;
// check overflow
if( nchar <= max_size() && (
prefix + suffix <=
max_size() - nchar))
{
nchar = prefix + nchar + suffix;
if( nchar <= nremove ||
nchar - nremove <=
max_size() - size())
goto ok;
}
// too large
detail::throw_length_error();
ok:
auto const new_size =
size() + nchar - nremove;
reserve_impl(new_size, op);
dest = s_ + path_pos + pos0;
op.move(
dest + nchar,
s_ + path_pos + pos1,
size() - path_pos - pos1);
impl_.set_size(
id_path,
impl_.len(id_path) + nchar - nremove);
BOOST_ASSERT(size() == new_size);
end = dest + nchar;
impl_.nseg_ = impl_.nseg_ + nseg - (
it1.index - it0.index) - cp_src_prefix;
if(s_)
s_[size()] = '\0';
}
//------------------------------------------------
//
// Output segments and internal separators:
//
// prefix [ segment [ '/' segment ] ] suffix
//
auto const dest0 = dest;
switch(prefix)
{
case 3:
*dest++ = '/';
*dest++ = '.';
*dest++ = '/';
break;
case 2:
*dest++ = '.';
BOOST_FALLTHROUGH;
case 1:
*dest++ = '/';
break;
default:
break;
}
src.rewind();
if(nseg > 0)
{
src.encode_colons = encode_colons;
for(;;)
{
src.copy(dest, end);
if(--nseg == 0)
break;
*dest++ = '/';
src.encode_colons = false;
}
if(suffix)
*dest++ = '/';
}
BOOST_ASSERT(dest == dest0 + nchar);
// calc decoded size of new range,
auto const dn =
detail::decode_bytes_unsafe(
string_view(dest0, dest - dest0));
impl_.decoded_[id_path] += dn - dn0;
return detail::segments_iter_impl(
impl_, pos0, it0.index);
}
//------------------------------------------------
auto
url_base::
edit_params(
detail::params_iter_impl const& it0,
detail::params_iter_impl const& it1,
detail::any_params_iter&& src) ->
detail::params_iter_impl
{
auto pos0 = impl_.offset(id_query);
auto pos1 = pos0 + it1.pos;
pos0 = pos0 + it0.pos;
// Iterator doesn't belong to this url
BOOST_ASSERT(it0.ref.alias_of(impl_));
// Iterator doesn't belong to this url
BOOST_ASSERT(it1.ref.alias_of(impl_));
// Iterator is in the wrong order
BOOST_ASSERT(it0.index <= it1.index);
// Iterator is out of range
BOOST_ASSERT(it0.index <= impl_.nparam_);
BOOST_ASSERT(pos0 <= impl_.offset(id_frag));
// Iterator is out of range
BOOST_ASSERT(it1.index <= impl_.nparam_);
BOOST_ASSERT(pos1 <= impl_.offset(id_frag));
// calc decoded size of old range,
// minus one if '?' or '&' prefixed
auto const dn0 =
detail::decode_bytes_unsafe(
string_view(
impl_.cs_ + pos0,
pos1 - pos0)) - (
impl_.len(id_query) > 0);
//------------------------------------------------
//
// Measure the number of encoded characters
// of output, and the number of inserted
// segments including internal separators.
//
std::size_t nchar = 0;
std::size_t nparam = 0;
if(src.measure(nchar))
{
++nchar; // for '?' or '&'
for(;;)
{
++nparam;
if(! src.measure(nchar))
break;
++nchar; // for '&'
}
}
//------------------------------------------------
//
// Resize
//
op_t op(*this, &src.s0, &src.s1);
char* dest;
char const* end;
{
auto const nremove = pos1 - pos0;
// check overflow
if( nchar > nremove &&
nchar - nremove >
max_size() - size())
{
// too large
detail::throw_length_error();
}
auto const nparam1 =
impl_.nparam_ + nparam - (
it1.index - it0.index);
reserve_impl(size() + nchar - nremove, op);
dest = s_ + pos0;
end = dest + nchar;
if(impl_.nparam_ > 0)
{
// needed when we move
// the beginning of the query
s_[impl_.offset(id_query)] = '&';
}
op.move(
dest + nchar,
impl_.cs_ + pos1,
size() - pos1);
impl_.set_size(
id_query,
impl_.len(id_query) +
nchar - nremove);
impl_.nparam_ = nparam1;
if(nparam1 > 0)
{
// needed when we erase
// the beginning of the query
s_[impl_.offset(id_query)] = '?';
}
if(s_)
s_[size()] = '\0';
}
auto const dest0 = dest;
//------------------------------------------------
//
// Output params and internal separators:
//
// [ '?' param ] [ '&' param ]
//
if(nparam > 0)
{
if(it0.index == 0)
*dest++ = '?';
else
*dest++ = '&';
src.rewind();
for(;;)
{
src.copy(dest, end);
if(--nparam == 0)
break;
*dest++ = '&';
}
}
// calc decoded size of new range,
// minus one if '?' or '&' prefixed
auto const dn =
detail::decode_bytes_unsafe(
string_view(dest0, dest - dest0)) - (
impl_.len(id_query) > 0);
impl_.decoded_[id_query] += (dn - dn0);
return detail::params_iter_impl(
impl_,
pos0 - impl_.offset_[id_query],
it0.index);
}
//------------------------------------------------
void
url_base::
decoded_to_lower_impl(int id) noexcept
{
char* it = s_ + impl_.offset(id);
char const* const end = s_ + impl_.offset(id + 1);
while(it < end)
{
if (*it != '%')
{
*it = grammar::to_lower(
*it);
++it;
continue;
}
it += 3;
}
}
void
url_base::
to_lower_impl(int id) noexcept
{
char* it = s_ + impl_.offset(id);
char const* const end = s_ + impl_.offset(id + 1);
while(it < end)
{
*it = grammar::to_lower(
*it);
++it;
}
}
} // urls
} // boost
#endif