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