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 18599 : url_base::
56 : op_t::
57 : ~op_t()
58 : {
59 18599 : if(old)
60 2575 : u.cleanup(*this);
61 18599 : u.check_invariants();
62 18599 : }
63 :
64 : inline
65 18599 : url_base::
66 : op_t::
67 : op_t(
68 : url_base& impl_,
69 : core::string_view* s0_,
70 18599 : core::string_view* s1_) noexcept
71 18599 : : u(impl_)
72 18599 : , s0(s0_)
73 18599 : , s1(s1_)
74 : {
75 18599 : u.check_invariants();
76 18599 : }
77 :
78 : inline
79 : void
80 9461 : url_base::
81 : op_t::
82 : move(
83 : char* dest,
84 : char const* src,
85 : std::size_t n) noexcept
86 : {
87 9461 : if(! n)
88 2523 : return;
89 6938 : 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 2019 : 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 5300 : url_base::
127 : copy(url_view_base const& u)
128 : {
129 5300 : if (this == &u)
130 140 : return;
131 5286 : op_t op(*this);
132 5286 : if(u.size() == 0)
133 : {
134 126 : clear();
135 126 : return;
136 : }
137 5160 : reserve_impl(
138 : u.size(), op);
139 5157 : impl_ = u.impl();
140 5157 : impl_.cs_ = s_;
141 5157 : impl_.from_ = {from::url};
142 5157 : std::memcpy(s_,
143 5157 : u.data(), u.size());
144 5157 : s_[size()] = '\0';
145 5286 : }
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 3211 : url_base::
1916 : normalize_octets_impl(
1917 : int id,
1918 : AllowedCharset const& allowed,
1919 : IgnoredCharset const& ignored,
1920 : op_t& op) noexcept
1921 : {
1922 3211 : char* it = s_ + impl_.offset(id);
1923 3211 : char* end = s_ + impl_.offset(id + 1);
1924 3211 : char d = 0;
1925 3211 : char* dest = it;
1926 16962 : while (it < end)
1927 : {
1928 13751 : if (*it != '%')
1929 : {
1930 13511 : *dest = *it;
1931 13511 : ++it;
1932 13511 : ++dest;
1933 13511 : continue;
1934 : }
1935 240 : BOOST_ASSERT(end - it >= 3);
1936 :
1937 : // decode unreserved octets
1938 240 : d = detail::decode_one(it + 1);
1939 322 : if (allowed(d) &&
1940 82 : !ignored(d))
1941 : {
1942 82 : *dest = d;
1943 82 : it += 3;
1944 82 : ++dest;
1945 82 : continue;
1946 : }
1947 :
1948 : // uppercase percent-encoding triplets
1949 158 : *dest++ = '%';
1950 158 : ++it;
1951 158 : *dest++ = grammar::to_upper(*it++);
1952 158 : *dest++ = grammar::to_upper(*it++);
1953 : }
1954 3211 : if (it != dest)
1955 : {
1956 32 : auto diff = it - dest;
1957 32 : auto n = impl_.len(id) - diff;
1958 32 : shrink_impl(id, n, op);
1959 32 : s_[size()] = '\0';
1960 : }
1961 3211 : }
1962 :
1963 : template<class CharSet>
1964 : void
1965 3211 : url_base::
1966 : normalize_octets_impl(
1967 : int idx,
1968 : CharSet const& allowed,
1969 : op_t& op) noexcept
1970 : {
1971 3211 : return normalize_octets_impl(
1972 3211 : idx, allowed, detail::empty_chars, op);
1973 : }
1974 :
1975 : inline
1976 : url_base&
1977 263 : url_base::
1978 : normalize_scheme()
1979 : {
1980 263 : to_lower_impl(id_scheme);
1981 263 : return *this;
1982 : }
1983 :
1984 : inline
1985 : url_base&
1986 609 : url_base::
1987 : normalize_authority()
1988 : {
1989 609 : op_t op(*this);
1990 :
1991 : // normalize host
1992 609 : if (host_type() == urls::host_type::name)
1993 : {
1994 356 : normalize_octets_impl(
1995 : id_host,
1996 : detail::reg_name_chars, op);
1997 : }
1998 609 : decoded_to_lower_impl(id_host);
1999 :
2000 : // normalize password
2001 609 : normalize_octets_impl(id_pass, detail::password_chars, op);
2002 :
2003 : // normalize user
2004 609 : normalize_octets_impl(id_user, detail::user_chars, op);
2005 1218 : return *this;
2006 609 : }
2007 :
2008 : inline
2009 : url_base&
2010 1112 : url_base::
2011 : normalize_path()
2012 : {
2013 1112 : op_t op(*this);
2014 :
2015 : //
2016 : // Step 1: Percent-encoding normalization
2017 : //
2018 : // Decode percent-encoded unreserved characters,
2019 : // and uppercase remaining hex digits.
2020 : //
2021 : // This can introduce new '.' from %2E,
2022 : // but reserved characters stay encoded.
2023 : //
2024 : // These new '.' characters can create
2025 : // ambiguities that Steps 2 and 3 must handle.
2026 : //
2027 1112 : normalize_octets_impl(id_path, unreserved_chars, op);
2028 1112 : core::string_view p = impl_.get(id_path);
2029 1112 : char* p_dest = s_ + impl_.offset(id_path);
2030 1112 : char* p_end = s_ + impl_.offset(id_path + 1);
2031 1112 : auto pn = p.size();
2032 :
2033 : //
2034 : // Step 2: Preserve existing path shields
2035 : //
2036 : // If the URL has no authority, a path starting
2037 : // with "//" would be misinterpreted as an
2038 : // authority when serialized and re-parsed.
2039 : // Some paths already have a dot prefix ("/."
2040 : // or "./") that shields against this. We
2041 : // preserve them so remove_dot_segments (Step 3)
2042 : // does not strip them. Step 1 can also reveal
2043 : // new shields by decoding %2E to '.'.
2044 : //
2045 : // This is an optimization. Step 4 would create
2046 : // a new shield anyway if remove_dot_segments
2047 : // produces a problematic output, but preserving
2048 : // an existing shield avoids the memmove in
2049 : // Step 4.
2050 : //
2051 : // path_shield_len: number of leading bytes
2052 : // to skip during remove_dot_segments.
2053 : // Always 0 (no shield) or 2 ("/." or "./").
2054 : //
2055 1112 : auto path_shield_len = 0;
2056 1112 : if (!has_authority())
2057 : {
2058 354 : if (p.starts_with("/./"))
2059 : {
2060 : // (a) Absolute path with "/." prefix.
2061 : //
2062 : // Check if "/." shields a "//"
2063 : // underneath (possibly through
2064 : // multiple "/./" layers like "/././/").
2065 : // If so, preserve the first 2 chars.
2066 : //
2067 : // /.//evil -> /.//evil (preserved)
2068 : // Without it: //evil (ambiguous).
2069 : // /././/evil -> /.//evil (same idea)
2070 : //
2071 : // Requires no authority (with authority,
2072 : // "//" in the path is unambiguous):
2073 : // http://h/.//x -> http://h//x (OK)
2074 : // Scheme is irrelevant (absolute paths
2075 : // can't be confused with a scheme):
2076 : // scheme:/.//x and /.//x both need it.
2077 : //
2078 25 : path_shield_len = 2;
2079 27 : while (p.substr(path_shield_len, 3).starts_with("/./"))
2080 : {
2081 2 : path_shield_len += 2;
2082 : }
2083 25 : if (p.substr(path_shield_len).starts_with("//"))
2084 : {
2085 17 : path_shield_len = 2;
2086 : }
2087 : else
2088 : {
2089 8 : path_shield_len = 0;
2090 : }
2091 : }
2092 329 : else if (
2093 417 : !has_scheme() &&
2094 88 : p.starts_with("./"))
2095 : {
2096 : // (b) Relative path with "./" prefix,
2097 : // no scheme.
2098 : //
2099 : // Check if "./" shields a "//"
2100 : // underneath. If so, preserve it.
2101 : //
2102 : // .//evil -> .//evil (preserved)
2103 : // Without it: //evil (ambiguous).
2104 : // ././/evil -> .//evil (same idea)
2105 : //
2106 : // Requires no authority (with authority,
2107 : // "//" in the path is unambiguous):
2108 : // //h/.//x -> //h//x (OK)
2109 : // Requires no scheme: with a scheme,
2110 : // remove_dot_segments would strip "./"
2111 : // and Step 4 handles any resulting "//".
2112 : //
2113 21 : path_shield_len = 1;
2114 26 : while (p.substr(path_shield_len, 3).starts_with("/./"))
2115 : {
2116 5 : path_shield_len += 2;
2117 : }
2118 21 : if (p.substr(path_shield_len).starts_with("//"))
2119 : {
2120 13 : path_shield_len = 2;
2121 : }
2122 : else
2123 : {
2124 8 : path_shield_len = 0;
2125 : }
2126 : }
2127 : }
2128 :
2129 : //
2130 : // Step 3: Remove "." and ".." segments
2131 : //
2132 : // RFC 3986 Section 5.2.4.
2133 : //
2134 : // If path_shield_len > 0, the first
2135 : // path_shield_len characters are an existing
2136 : // path shield ("/." or "./") that must
2137 : // be preserved (see Step 2), we tell
2138 : // remove_dot_segments to start after that
2139 : // prefix.
2140 : //
2141 : // Note: remove_dot_segments only recognizes
2142 : // literal '.' and '..', not encoded forms like
2143 : // '%2E'. This is correct here because Step 1
2144 : // already decoded %2E to '.'. If this function
2145 : // were called without Step 1, encoded dots
2146 : // would be missed.
2147 : //
2148 1112 : bool was_absolute = is_path_absolute();
2149 1112 : p.remove_prefix(path_shield_len);
2150 1112 : p_dest += path_shield_len;
2151 1112 : auto n = detail::remove_dot_segments(
2152 1112 : p_dest, p_end, p);
2153 :
2154 : //
2155 : // Step 4: Create path shield if needed,
2156 : // then shrink path to final size
2157 : //
2158 : // remove_dot_segments can produce output that
2159 : // needs a 2-byte shield prefix, as explained
2160 : // in step 2. The memmove below writes within
2161 : // the path region; when ".." cancellation has
2162 : // consumed >= 2 bytes there is already slack,
2163 : // but for short inputs that take the shield
2164 : // branch without any cancellation (e.g. "//"),
2165 : // the region has to grow to fit the prefix.
2166 : //
2167 3336 : bool needs_shield = [&]()
2168 : {
2169 1112 : if (path_shield_len)
2170 : {
2171 : // Step 2 already preserved a shield.
2172 30 : return false;
2173 : }
2174 1082 : if (has_authority())
2175 : {
2176 : // With an authority, "//" in the path
2177 : // is unambiguous and the path is always
2178 : // absolute (path-abempty).
2179 758 : return false;
2180 : }
2181 324 : if (n == 0)
2182 : {
2183 : // Empty output. Nothing to shield.
2184 29 : return false;
2185 : }
2186 295 : if (p_dest[0] != '/')
2187 : {
2188 : // Output doesn't start with '/'.
2189 : // No authority ambiguity, and if it
2190 : // was relative, it stayed relative.
2191 129 : return false;
2192 : }
2193 166 : if (n >= 2 && p_dest[1] == '/')
2194 : {
2195 : // Output starts with "//": would be
2196 : // misinterpreted as authority.
2197 : // /a/..//evil -> //evil
2198 : // so we need a shield
2199 : // /a/..//evil -> /.//evil
2200 11 : return true;
2201 : }
2202 155 : if (!was_absolute)
2203 : {
2204 : // Relative path became absolute: ".."
2205 : // canceled all leading segments and
2206 : // the remaining input started with "/"
2207 : // because of how remove_dot_segments is
2208 : // defined.
2209 : // a/..//evil -> /evil -> .//evil
2210 : // a/b/../..//evil -> /evil -> .//evil
2211 14 : return true;
2212 : }
2213 141 : return false;
2214 1112 : }();
2215 1112 : if (needs_shield)
2216 : {
2217 25 : if (n + 2 > pn)
2218 : {
2219 : // No ".. cancellation slack — grow the path
2220 : // region to fit the 2-byte shield. p_dest may
2221 : // be invalidated by the underlying reallocation.
2222 3 : p_dest = resize_impl(id_path, n + 2, op);
2223 3 : pn = n + 2;
2224 : }
2225 25 : std::memmove(p_dest + 2, p_dest, n);
2226 25 : if (was_absolute)
2227 : {
2228 : // "/." keeps the path absolute.
2229 11 : p_dest[0] = '/';
2230 11 : p_dest[1] = '.';
2231 : }
2232 : else
2233 : {
2234 : // "./" keeps the path relative.
2235 14 : p_dest[0] = '.';
2236 14 : p_dest[1] = '/';
2237 : }
2238 25 : path_shield_len = 2;
2239 : }
2240 1112 : if (n != pn)
2241 : {
2242 200 : BOOST_ASSERT(n < pn);
2243 200 : shrink_impl(id_path, n + path_shield_len, op);
2244 : }
2245 :
2246 : //
2247 : // Step 5: Encode colons in first segment
2248 : //
2249 : // If the URL has no scheme and no authority,
2250 : // a colon in the first path segment would be
2251 : // misinterpreted as a scheme delimiter when
2252 : // serialized and re-parsed.
2253 : //
2254 : // Colons can appear in the first segment because
2255 : // remove_dot_segments (Step 3) canceled preceding
2256 : // segments via ".." and exposed a colon that was
2257 : // deeper in the path.
2258 : //
2259 : // Example (exposed by dot removal):
2260 : // a/../b:c -> Step 3: b:c
2261 : // -> Step 5: b%3Ac
2262 : //
2263 : // ALL colons in the first segment must be
2264 : // encoded, not just the first one. In a
2265 : // schemeless relative-reference, the first
2266 : // segment must be segment-nz-nc (RFC 3986
2267 : // Section 4.2), which does not allow ':':
2268 : // path-noscheme = segment-nz-nc *( "/" segment )
2269 : // segment-nz-nc = 1*( unreserved / pct-encoded
2270 : // / sub-delims / "@" )
2271 : // So "b%3Ac:d" would fail to re-parse.
2272 : // All colons must go: "b%3Ac%3Ad".
2273 : //
2274 : // Requires no scheme (with a scheme, colons
2275 : // in the path are unambiguous):
2276 : // scheme:a:b -> scheme:a:b (OK)
2277 : // Requires no authority (with authority,
2278 : // path starts with "/" so the first segment
2279 : // is empty, no colon issue).
2280 : //
2281 1230 : if (!has_scheme() &&
2282 118 : !has_authority())
2283 : {
2284 99 : p = impl_.get(id_path);
2285 99 : core::string_view first_seg = p;
2286 99 : auto i = first_seg.find('/');
2287 99 : if (i != core::string_view::npos)
2288 : {
2289 68 : first_seg = p.substr(0, i);
2290 : }
2291 99 : if (first_seg.contains(':'))
2292 : {
2293 12 : pn = p.size();
2294 : auto cn =
2295 12 : std::count(
2296 : first_seg.begin(),
2297 : first_seg.end(),
2298 12 : ':');
2299 12 : resize_impl(
2300 12 : id_path, pn + (2 * cn), op);
2301 12 : auto begin = s_ + impl_.offset(id_path);
2302 12 : auto it = begin;
2303 12 : auto end = begin + pn;
2304 92 : while (it != end &&
2305 81 : *it != '/')
2306 : {
2307 80 : ++it;
2308 : }
2309 12 : std::memmove(it + (2 * cn), it, end - it);
2310 12 : auto src = s_ + impl_.offset(id_path) + pn;
2311 12 : auto dest = s_ + impl_.offset(id_query);
2312 12 : src -= end - it;
2313 12 : dest -= end - it;
2314 12 : pn -= end - it;
2315 : do {
2316 80 : --src;
2317 80 : --dest;
2318 80 : if (*src != ':')
2319 : {
2320 56 : *dest = *src;
2321 : }
2322 : else
2323 : {
2324 24 : *dest-- = 'A';
2325 24 : *dest-- = '3';
2326 24 : *dest = '%';
2327 : }
2328 80 : --pn;
2329 80 : } while (pn);
2330 : }
2331 : }
2332 :
2333 : //
2334 : // Step 6: Update path parameters
2335 : //
2336 : {
2337 : // The segment count is adjusted for the
2338 : // "/." or "./" shield prefix, which is
2339 : // not a segment of its own.
2340 1112 : p = encoded_path();
2341 1112 : std::size_t nseg = 0;
2342 1112 : if (!p.empty())
2343 : {
2344 983 : nseg = std::count(
2345 1966 : p.begin() + 1, p.end(), '/') + 1;
2346 : }
2347 1112 : impl_.nseg_ = detail::to_size_type(
2348 : detail::path_segments(p, nseg));
2349 1112 : impl_.decoded_[id_path] =
2350 1112 : detail::to_size_type(detail::decode_bytes_unsafe(
2351 : impl_.get(id_path)));
2352 : }
2353 1112 : return *this;
2354 1112 : }
2355 :
2356 : inline
2357 : url_base&
2358 264 : url_base::
2359 : normalize_query()
2360 : {
2361 264 : op_t op(*this);
2362 264 : normalize_octets_impl(
2363 : id_query, unreserved_chars, op);
2364 528 : return *this;
2365 264 : }
2366 :
2367 : inline
2368 : url_base&
2369 261 : url_base::
2370 : normalize_fragment()
2371 : {
2372 261 : op_t op(*this);
2373 261 : normalize_octets_impl(
2374 : id_frag, unreserved_chars, op);
2375 522 : return *this;
2376 261 : }
2377 :
2378 : inline
2379 : url_base&
2380 260 : url_base::
2381 : normalize()
2382 : {
2383 260 : normalize_fragment();
2384 260 : normalize_query();
2385 260 : normalize_path();
2386 260 : normalize_authority();
2387 260 : normalize_scheme();
2388 260 : return *this;
2389 : }
2390 :
2391 : //------------------------------------------------
2392 : //
2393 : // Implementation
2394 : //
2395 : //------------------------------------------------
2396 :
2397 : inline
2398 : void
2399 44237 : url_base::
2400 : check_invariants() const noexcept
2401 : {
2402 44237 : BOOST_ASSERT(
2403 : impl_.len(id_scheme) == 0 ||
2404 : impl_.get(id_scheme).ends_with(':'));
2405 44237 : BOOST_ASSERT(
2406 : impl_.len(id_user) == 0 ||
2407 : impl_.get(id_user).starts_with("//"));
2408 44237 : BOOST_ASSERT(
2409 : impl_.len(id_pass) == 0 ||
2410 : impl_.get(id_user).starts_with("//"));
2411 44237 : BOOST_ASSERT(
2412 : impl_.len(id_pass) == 0 ||
2413 : (impl_.len(id_pass) == 1 &&
2414 : impl_.get(id_pass) == "@") ||
2415 : (impl_.len(id_pass) > 1 &&
2416 : impl_.get(id_pass).starts_with(':') &&
2417 : impl_.get(id_pass).ends_with('@')));
2418 44237 : BOOST_ASSERT(
2419 : impl_.len(id_user, id_path) == 0 ||
2420 : impl_.get(id_user).starts_with("//"));
2421 44237 : BOOST_ASSERT(impl_.decoded_[id_path] >=
2422 : ((impl_.len(id_path) + 2) / 3));
2423 44237 : BOOST_ASSERT(
2424 : impl_.len(id_port) == 0 ||
2425 : impl_.get(id_port).starts_with(':'));
2426 44237 : BOOST_ASSERT(
2427 : impl_.len(id_query) == 0 ||
2428 : impl_.get(id_query).starts_with('?'));
2429 44237 : BOOST_ASSERT(
2430 : (impl_.len(id_query) == 0 && impl_.nparam_ == 0) ||
2431 : (impl_.len(id_query) > 0 && impl_.nparam_ > 0));
2432 44237 : BOOST_ASSERT(
2433 : impl_.len(id_frag) == 0 ||
2434 : impl_.get(id_frag).starts_with('#'));
2435 44237 : BOOST_ASSERT(c_str()[size()] == '\0');
2436 44237 : }
2437 :
2438 : inline
2439 : char*
2440 5543 : url_base::
2441 : resize_impl(
2442 : int id,
2443 : std::size_t new_size,
2444 : op_t& op)
2445 : {
2446 5543 : return resize_impl(
2447 5543 : id, id + 1, new_size, op);
2448 : }
2449 :
2450 : inline
2451 : char*
2452 6278 : url_base::
2453 : resize_impl(
2454 : int first,
2455 : int last,
2456 : std::size_t new_len,
2457 : op_t& op)
2458 : {
2459 6278 : auto const n0 = impl_.len(first, last);
2460 6278 : if(new_len == 0 && n0 == 0)
2461 1091 : return s_ + impl_.offset(first);
2462 5187 : if(new_len <= n0)
2463 2220 : return shrink_impl(
2464 2220 : first, last, new_len, op);
2465 :
2466 : // growing
2467 2967 : std::size_t n = new_len - n0;
2468 2967 : reserve_impl(size() + n, op);
2469 : auto const pos =
2470 2967 : impl_.offset(last);
2471 : // adjust chars
2472 2967 : op.move(
2473 2967 : s_ + pos + n,
2474 2967 : s_ + pos,
2475 2967 : impl_.offset(id_end) -
2476 : pos + 1);
2477 : // collapse (first, last)
2478 2967 : impl_.collapse(first, last,
2479 2967 : impl_.offset(last) + n);
2480 : // shift (last, end) right
2481 2967 : impl_.adjust_right(last, id_end, n);
2482 2967 : s_[size()] = '\0';
2483 2967 : return s_ + impl_.offset(first);
2484 : }
2485 :
2486 : inline
2487 : char*
2488 232 : url_base::
2489 : shrink_impl(
2490 : int id,
2491 : std::size_t new_size,
2492 : op_t& op)
2493 : {
2494 232 : return shrink_impl(
2495 232 : id, id + 1, new_size, op);
2496 : }
2497 :
2498 : inline
2499 : char*
2500 2452 : url_base::
2501 : shrink_impl(
2502 : int first,
2503 : int last,
2504 : std::size_t new_len,
2505 : op_t& op)
2506 : {
2507 : // shrinking
2508 2452 : auto const n0 = impl_.len(first, last);
2509 2452 : BOOST_ASSERT(new_len <= n0);
2510 2452 : std::size_t n = n0 - new_len;
2511 : auto const pos =
2512 2452 : impl_.offset(last);
2513 : // adjust chars
2514 2452 : op.move(
2515 2452 : s_ + pos - n,
2516 2452 : s_ + pos,
2517 2452 : impl_.offset(
2518 2452 : id_end) - pos + 1);
2519 : // collapse (first, last)
2520 2452 : impl_.collapse(first, last,
2521 2452 : impl_.offset(last) - n);
2522 : // shift (last, end) left
2523 2452 : impl_.adjust_left(last, id_end, n);
2524 2452 : s_[size()] = '\0';
2525 2452 : return s_ + impl_.offset(first);
2526 : }
2527 :
2528 : //------------------------------------------------
2529 :
2530 : inline
2531 : void
2532 529 : url_base::
2533 : set_scheme_impl(
2534 : core::string_view s,
2535 : urls::scheme id)
2536 : {
2537 529 : op_t op(*this, &s);
2538 529 : check_invariants();
2539 577 : grammar::parse(
2540 48 : s, detail::scheme_rule()
2541 577 : ).value(BOOST_URL_POS);
2542 481 : auto const n = s.size();
2543 481 : auto const p = impl_.offset(id_path);
2544 :
2545 : // check for "./" prefix
2546 : bool const has_dot =
2547 1443 : [this, p]
2548 : {
2549 481 : if(impl_.nseg_ == 0)
2550 213 : return false;
2551 268 : if(first_segment().size() < 2)
2552 42 : return false;
2553 226 : auto const src = s_ + p;
2554 226 : if(src[0] != '.')
2555 220 : return false;
2556 6 : if(src[1] != '/')
2557 MIS 0 : return false;
2558 HIT 6 : return true;
2559 481 : }();
2560 :
2561 : // Remove "./"
2562 481 : if(has_dot)
2563 : {
2564 : // do this first, for
2565 : // strong exception safety
2566 12 : reserve_impl(
2567 6 : size() + n + 1 - 2, op);
2568 6 : op.move(
2569 6 : s_ + p,
2570 6 : s_ + p + 2,
2571 6 : size() + 1 -
2572 : (p + 2));
2573 6 : impl_.set_size(
2574 : id_path,
2575 6 : impl_.len(id_path) - 2);
2576 6 : impl_.decoded_[id_path] -= 2;
2577 6 : s_[size()] = '\0';
2578 : }
2579 :
2580 481 : auto dest = resize_impl(
2581 : id_scheme, n + 1, op);
2582 481 : s.copy(dest, n);
2583 481 : dest[n] = ':';
2584 481 : impl_.scheme_ = id;
2585 481 : check_invariants();
2586 529 : }
2587 :
2588 : inline
2589 : char*
2590 399 : url_base::
2591 : set_user_impl(
2592 : std::size_t n,
2593 : op_t& op)
2594 : {
2595 399 : check_invariants();
2596 399 : if(impl_.len(id_pass) != 0)
2597 : {
2598 : // keep "//"
2599 147 : auto dest = resize_impl(
2600 : id_user, 2 + n, op);
2601 147 : check_invariants();
2602 147 : return dest + 2;
2603 : }
2604 : // add authority
2605 : bool const make_absolute =
2606 339 : !is_path_absolute() &&
2607 87 : !impl_.get(id_path).empty();
2608 504 : auto dest = resize_impl(
2609 252 : id_user, 2 + n + 1 + make_absolute, op);
2610 252 : impl_.split(id_user, 2 + n);
2611 252 : dest[0] = '/';
2612 252 : dest[1] = '/';
2613 252 : dest[2 + n] = '@';
2614 252 : if (make_absolute)
2615 : {
2616 7 : impl_.split(id_pass, 1);
2617 7 : impl_.split(id_host, 0);
2618 7 : impl_.split(id_port, 0);
2619 7 : dest[3 + n] = '/';
2620 7 : ++impl_.decoded_[id_path];
2621 : }
2622 252 : check_invariants();
2623 252 : return dest + 2;
2624 : }
2625 :
2626 : inline
2627 : char*
2628 379 : url_base::
2629 : set_password_impl(
2630 : std::size_t n,
2631 : op_t& op)
2632 : {
2633 379 : check_invariants();
2634 379 : if(impl_.len(id_user) != 0)
2635 : {
2636 : // already have authority
2637 314 : auto const dest = resize_impl(
2638 : id_pass, 1 + n + 1, op);
2639 314 : dest[0] = ':';
2640 314 : dest[n + 1] = '@';
2641 314 : check_invariants();
2642 314 : return dest + 1;
2643 : }
2644 : // add authority
2645 : bool const make_absolute =
2646 106 : !is_path_absolute() &&
2647 41 : !impl_.get(id_path).empty();
2648 : auto const dest =
2649 130 : resize_impl(
2650 : id_user, id_host,
2651 65 : 2 + 1 + n + 1 + make_absolute, op);
2652 65 : impl_.split(id_user, 2);
2653 65 : dest[0] = '/';
2654 65 : dest[1] = '/';
2655 65 : dest[2] = ':';
2656 65 : dest[2 + n + 1] = '@';
2657 65 : if (make_absolute)
2658 : {
2659 8 : impl_.split(id_pass, 2 + n);
2660 8 : impl_.split(id_host, 0);
2661 8 : impl_.split(id_port, 0);
2662 8 : dest[4 + n] = '/';
2663 8 : ++impl_.decoded_[id_path];
2664 : }
2665 65 : check_invariants();
2666 65 : return dest + 3;
2667 : }
2668 :
2669 : inline
2670 : char*
2671 278 : url_base::
2672 : set_userinfo_impl(
2673 : std::size_t n,
2674 : op_t& op)
2675 : {
2676 : // "//" {dest} "@"
2677 278 : check_invariants();
2678 : bool const make_absolute =
2679 408 : !is_path_absolute() &&
2680 130 : !impl_.get(id_path).empty();
2681 556 : auto dest = resize_impl(
2682 278 : id_user, id_host, n + 3 + make_absolute, op);
2683 278 : impl_.split(id_user, n + 2);
2684 278 : dest[0] = '/';
2685 278 : dest[1] = '/';
2686 278 : dest[n + 2] = '@';
2687 278 : if (make_absolute)
2688 : {
2689 4 : impl_.split(id_pass, 1);
2690 4 : impl_.split(id_host, 0);
2691 4 : impl_.split(id_port, 0);
2692 4 : dest[3 + n] = '/';
2693 4 : ++impl_.decoded_[id_path];
2694 : }
2695 278 : check_invariants();
2696 278 : return dest + 2;
2697 : }
2698 :
2699 : inline
2700 : char*
2701 742 : url_base::
2702 : set_host_impl(
2703 : std::size_t n,
2704 : op_t& op)
2705 : {
2706 742 : check_invariants();
2707 742 : if(impl_.len(id_user) == 0)
2708 : {
2709 : // add authority
2710 : bool make_absolute =
2711 322 : !is_path_absolute() &&
2712 148 : impl_.len(id_path) != 0;
2713 174 : auto pn = impl_.len(id_path);
2714 348 : auto dest = resize_impl(
2715 174 : id_user, n + 2 + make_absolute, op);
2716 174 : impl_.split(id_user, 2);
2717 174 : impl_.split(id_pass, 0);
2718 174 : impl_.split(id_host, n);
2719 174 : impl_.split(id_port, 0);
2720 174 : impl_.split(id_path, pn + make_absolute);
2721 174 : if (make_absolute)
2722 : {
2723 9 : dest[n + 2] = '/';
2724 9 : ++impl_.decoded_[id_path];
2725 : }
2726 174 : dest[0] = '/';
2727 174 : dest[1] = '/';
2728 174 : check_invariants();
2729 174 : return dest + 2;
2730 : }
2731 : // already have authority
2732 568 : auto const dest = resize_impl(
2733 : id_host, n, op);
2734 568 : check_invariants();
2735 568 : return dest;
2736 : }
2737 :
2738 : inline
2739 : char*
2740 605 : url_base::
2741 : set_port_impl(
2742 : std::size_t n,
2743 : op_t& op)
2744 : {
2745 605 : check_invariants();
2746 605 : if(impl_.len(id_user) != 0)
2747 : {
2748 : // authority exists
2749 504 : auto dest = resize_impl(
2750 : id_port, n + 1, op);
2751 504 : dest[0] = ':';
2752 504 : check_invariants();
2753 504 : return dest + 1;
2754 : }
2755 : bool make_absolute =
2756 174 : !is_path_absolute() &&
2757 73 : impl_.len(id_path) != 0;
2758 202 : auto dest = resize_impl(
2759 101 : id_user, 3 + n + make_absolute, op);
2760 101 : impl_.split(id_user, 2);
2761 101 : impl_.split(id_pass, 0);
2762 101 : impl_.split(id_host, 0);
2763 101 : dest[0] = '/';
2764 101 : dest[1] = '/';
2765 101 : dest[2] = ':';
2766 101 : if (make_absolute)
2767 : {
2768 8 : impl_.split(id_port, n + 1);
2769 8 : dest[n + 3] = '/';
2770 8 : ++impl_.decoded_[id_path];
2771 : }
2772 101 : check_invariants();
2773 101 : return dest + 3;
2774 : }
2775 :
2776 : inline
2777 : char*
2778 598 : url_base::
2779 : set_path_impl(
2780 : std::size_t n,
2781 : op_t& op)
2782 : {
2783 598 : check_invariants();
2784 598 : auto const dest = resize_impl(
2785 : id_path, n, op);
2786 598 : return dest;
2787 : }
2788 :
2789 :
2790 : //------------------------------------------------
2791 :
2792 : // return the first segment of the path.
2793 : // this is needed for some algorithms.
2794 : inline
2795 : core::string_view
2796 268 : url_base::
2797 : first_segment() const noexcept
2798 : {
2799 268 : if(impl_.nseg_ == 0)
2800 MIS 0 : return {};
2801 HIT 268 : auto const p0 = impl_.cs_ +
2802 268 : impl_.offset(id_path) +
2803 268 : detail::path_prefix(
2804 268 : impl_.get(id_path));
2805 268 : auto const end = impl_.cs_ +
2806 268 : impl_.offset(id_query);
2807 268 : if(impl_.nseg_ == 1)
2808 160 : return core::string_view(
2809 80 : p0, end - p0);
2810 188 : auto p = p0;
2811 855 : while(*p != '/')
2812 : {
2813 667 : BOOST_ASSERT(p < end);
2814 667 : ++p;
2815 : }
2816 188 : return core::string_view(p0, p - p0);
2817 : }
2818 :
2819 : inline
2820 : detail::segments_iter_impl
2821 2315 : url_base::
2822 : edit_segments(
2823 : detail::segments_iter_impl const& it0,
2824 : detail::segments_iter_impl const& it1,
2825 : detail::any_segments_iter&& src,
2826 : // -1 = preserve
2827 : // 0 = make relative (can fail)
2828 : // 1 = make absolute
2829 : int absolute)
2830 : {
2831 : // Iterator doesn't belong to this url
2832 2315 : BOOST_ASSERT(it0.ref.alias_of(impl_));
2833 :
2834 : // Iterator doesn't belong to this url
2835 2315 : BOOST_ASSERT(it1.ref.alias_of(impl_));
2836 :
2837 : // Iterator is in the wrong order
2838 2315 : BOOST_ASSERT(it0.index <= it1.index);
2839 :
2840 : // Iterator is out of range
2841 2315 : BOOST_ASSERT(it0.index <= impl_.nseg_);
2842 2315 : BOOST_ASSERT(it0.pos <= impl_.len(id_path));
2843 :
2844 : // Iterator is out of range
2845 2315 : BOOST_ASSERT(it1.index <= impl_.nseg_);
2846 2315 : BOOST_ASSERT(it1.pos <= impl_.len(id_path));
2847 :
2848 : //------------------------------------------------
2849 : //
2850 : // Calculate output prefix
2851 : //
2852 : // 0 = ""
2853 : // 1 = "/"
2854 : // 2 = "./"
2855 : // 3 = "/./"
2856 : //
2857 2315 : bool const is_abs = is_path_absolute();
2858 2315 : if(has_authority())
2859 : {
2860 : // Check if the new
2861 : // path would be empty
2862 1855 : if( src.fast_nseg == 0 &&
2863 911 : it0.index == 0 &&
2864 655 : it1.index == impl_.nseg_)
2865 : {
2866 : // VFALCO we don't have
2867 : // access to nchar this early
2868 : //
2869 : //BOOST_ASSERT(nchar == 0);
2870 627 : absolute = 0;
2871 : }
2872 : else
2873 : {
2874 : // prefix "/" required
2875 1228 : absolute = 1;
2876 : }
2877 : }
2878 460 : else if(absolute < 0)
2879 : {
2880 460 : absolute = is_abs; // preserve
2881 : }
2882 2315 : auto const path_pos = impl_.offset(id_path);
2883 :
2884 2315 : std::size_t nchar = 0;
2885 2315 : std::size_t prefix = 0;
2886 2315 : bool encode_colons = false;
2887 2315 : bool cp_src_prefix = false;
2888 2315 : if(it0.index > 0)
2889 : {
2890 : // first segment unchanged
2891 870 : prefix = src.fast_nseg > 0;
2892 : }
2893 1445 : else if(src.fast_nseg > 0)
2894 : {
2895 : // first segment from src
2896 718 : if(! src.front.empty())
2897 : {
2898 640 : if( src.front == "." &&
2899 7 : src.fast_nseg > 1)
2900 4 : if (src.s.empty())
2901 : {
2902 : // if front is ".", we need the extra "." in the prefix
2903 : // which will maintain the invariant that segments represent
2904 : // {"."}
2905 4 : prefix = 2 + absolute;
2906 : }
2907 : else
2908 : {
2909 : // if the "." prefix is explicitly required from set_path
2910 : // we do not include an extra "." segment
2911 MIS 0 : prefix = absolute;
2912 0 : cp_src_prefix = true;
2913 : }
2914 HIT 629 : else if(absolute)
2915 542 : prefix = 1;
2916 166 : else if(has_scheme() ||
2917 79 : ! src.front.contains(':'))
2918 82 : prefix = 0;
2919 : else
2920 : {
2921 5 : prefix = 0;
2922 5 : encode_colons = true;
2923 : }
2924 : }
2925 : else
2926 : {
2927 85 : prefix = 2 + absolute;
2928 : }
2929 : }
2930 : else
2931 : {
2932 : // first segment from it1
2933 727 : auto const p =
2934 727 : impl_.cs_ + path_pos + it1.pos;
2935 1454 : switch(impl_.cs_ +
2936 727 : impl_.offset(id_query) - p)
2937 : {
2938 680 : case 0:
2939 : // points to end
2940 680 : prefix = absolute;
2941 680 : break;
2942 36 : default:
2943 36 : BOOST_ASSERT(*p == '/');
2944 36 : if(p[1] != '/')
2945 : {
2946 35 : if(absolute)
2947 29 : prefix = 1;
2948 11 : else if(has_scheme() ||
2949 11 : ! it1.dereference().contains(':'))
2950 5 : prefix = 0;
2951 : else
2952 1 : prefix = 2;
2953 35 : break;
2954 : }
2955 : // empty
2956 : BOOST_FALLTHROUGH;
2957 : case 1:
2958 : // empty
2959 12 : BOOST_ASSERT(*p == '/');
2960 12 : prefix = 2 + absolute;
2961 12 : break;
2962 : }
2963 : }
2964 :
2965 : // append '/' to new segs
2966 : // if inserting at front.
2967 2315 : std::size_t const suffix =
2968 3234 : it1.index == 0 &&
2969 2494 : impl_.nseg_ > 0 &&
2970 179 : src.fast_nseg > 0;
2971 :
2972 : //------------------------------------------------
2973 : //
2974 : // Measure the number of encoded characters
2975 : // of output, and the number of inserted
2976 : // segments including internal separators.
2977 : //
2978 2315 : src.encode_colons = encode_colons;
2979 2315 : std::size_t nseg = 0;
2980 2315 : if(src.measure(nchar))
2981 : {
2982 1279 : src.encode_colons = false;
2983 : for(;;)
2984 : {
2985 1605 : ++nseg;
2986 1605 : if(! src.measure(nchar))
2987 1277 : break;
2988 326 : ++nchar;
2989 : }
2990 : }
2991 :
2992 2313 : switch(src.fast_nseg)
2993 : {
2994 1036 : case 0:
2995 1036 : BOOST_ASSERT(nseg == 0);
2996 1036 : break;
2997 1089 : case 1:
2998 1089 : BOOST_ASSERT(nseg == 1);
2999 1089 : break;
3000 188 : case 2:
3001 188 : BOOST_ASSERT(nseg >= 2);
3002 188 : break;
3003 : }
3004 :
3005 : //------------------------------------------------
3006 : //
3007 : // Calculate [pos0, pos1) to remove
3008 : //
3009 2313 : auto pos0 = it0.pos;
3010 2313 : if(it0.index == 0)
3011 : {
3012 : // patch pos for prefix
3013 1443 : pos0 = 0;
3014 : }
3015 2313 : auto pos1 = it1.pos;
3016 2313 : if(it1.index == 0)
3017 : {
3018 : // patch pos for prefix
3019 919 : pos1 = detail::path_prefix(
3020 : impl_.get(id_path));
3021 : }
3022 1394 : else if(
3023 1394 : it0.index == 0 &&
3024 524 : it1.index < impl_.nseg_ &&
3025 : nseg == 0)
3026 : {
3027 : // Remove the slash from segment it1
3028 : // if it is becoming the new first
3029 : // segment.
3030 47 : ++pos1;
3031 : }
3032 : // calc decoded size of old range
3033 : auto const dn0 =
3034 2313 : detail::decode_bytes_unsafe(
3035 : core::string_view(
3036 2313 : impl_.cs_ +
3037 2313 : impl_.offset(id_path) +
3038 : pos0,
3039 : pos1 - pos0));
3040 :
3041 : //------------------------------------------------
3042 : //
3043 : // Resize
3044 : //
3045 4626 : op_t op(*this, &src.s);
3046 : char* dest;
3047 : char const* end;
3048 : {
3049 2313 : auto const nremove = pos1 - pos0;
3050 : // check overflow
3051 4626 : if( nchar <= max_size() && (
3052 2313 : prefix + suffix <=
3053 2313 : max_size() - nchar))
3054 : {
3055 2313 : nchar = prefix + nchar + suffix;
3056 3484 : if( nchar <= nremove ||
3057 1171 : nchar - nremove <=
3058 1171 : max_size() - size())
3059 2313 : goto ok;
3060 : }
3061 : // too large
3062 MIS 0 : detail::throw_length_error();
3063 HIT 2313 : ok:
3064 : auto const new_size =
3065 2313 : size() + nchar - nremove;
3066 2313 : reserve_impl(new_size, op);
3067 2313 : dest = s_ + path_pos + pos0;
3068 2313 : op.move(
3069 2313 : dest + nchar,
3070 2313 : s_ + path_pos + pos1,
3071 2313 : size() - path_pos - pos1);
3072 4626 : impl_.set_size(
3073 : id_path,
3074 2313 : impl_.len(id_path) + nchar - nremove);
3075 2313 : BOOST_ASSERT(size() == new_size);
3076 2313 : end = dest + nchar;
3077 2313 : auto const nseg1 =
3078 2313 : static_cast<std::ptrdiff_t>(impl_.nseg_) +
3079 2313 : static_cast<std::ptrdiff_t>(nseg) -
3080 2313 : static_cast<std::ptrdiff_t>(it1.index) +
3081 2313 : static_cast<std::ptrdiff_t>(it0.index) -
3082 : static_cast<std::ptrdiff_t>(cp_src_prefix);
3083 2313 : BOOST_ASSERT(nseg1 >= 0);
3084 2313 : impl_.nseg_ = detail::to_size_type(nseg1);
3085 2313 : if(s_)
3086 2306 : s_[size()] = '\0';
3087 : }
3088 :
3089 : //------------------------------------------------
3090 : //
3091 : // Output segments and internal separators:
3092 : //
3093 : // prefix [ segment [ '/' segment ] ] suffix
3094 : //
3095 2313 : auto const dest0 = dest;
3096 2313 : switch(prefix)
3097 : {
3098 60 : case 3:
3099 60 : *dest++ = '/';
3100 60 : *dest++ = '.';
3101 60 : *dest++ = '/';
3102 60 : break;
3103 42 : case 2:
3104 42 : *dest++ = '.';
3105 : BOOST_FALLTHROUGH;
3106 1193 : case 1:
3107 1193 : *dest++ = '/';
3108 1193 : break;
3109 1060 : default:
3110 1060 : break;
3111 : }
3112 2313 : src.rewind();
3113 2313 : if(nseg > 0)
3114 : {
3115 1277 : src.encode_colons = encode_colons;
3116 : for(;;)
3117 : {
3118 1603 : src.copy(dest, end);
3119 1603 : if(--nseg == 0)
3120 1277 : break;
3121 326 : *dest++ = '/';
3122 326 : src.encode_colons = false;
3123 : }
3124 1277 : if(suffix)
3125 179 : *dest++ = '/';
3126 : }
3127 2313 : BOOST_ASSERT(dest == dest0 + nchar);
3128 :
3129 : // calc decoded size of new range,
3130 : auto const dn =
3131 2313 : detail::decode_bytes_unsafe(
3132 2313 : core::string_view(dest0, dest - dest0));
3133 2313 : if(dn >= dn0)
3134 1401 : impl_.decoded_[id_path] +=
3135 1401 : detail::to_size_type(dn - dn0);
3136 : else
3137 912 : impl_.decoded_[id_path] -=
3138 912 : detail::to_size_type(dn0 - dn);
3139 :
3140 : return detail::segments_iter_impl(
3141 4626 : impl_, pos0, it0.index);
3142 : }
3143 :
3144 : //------------------------------------------------
3145 :
3146 : inline
3147 : auto
3148 1701 : url_base::
3149 : edit_params(
3150 : detail::params_iter_impl const& it0,
3151 : detail::params_iter_impl const& it1,
3152 : detail::any_params_iter&& src) ->
3153 : detail::params_iter_impl
3154 : {
3155 1701 : auto pos0 = impl_.offset(id_query);
3156 1701 : auto pos1 = pos0 + it1.pos;
3157 1701 : pos0 = pos0 + it0.pos;
3158 :
3159 : // Iterators belong to this url
3160 1701 : BOOST_ASSERT(it0.ref.alias_of(impl_));
3161 1701 : BOOST_ASSERT(it1.ref.alias_of(impl_));
3162 :
3163 : // Iterators is in the right order
3164 1701 : BOOST_ASSERT(it0.index <= it1.index);
3165 :
3166 : // Iterators are within range
3167 1701 : BOOST_ASSERT(it0.index <= impl_.nparam_);
3168 1701 : BOOST_ASSERT(pos0 <= impl_.offset(id_frag));
3169 1701 : BOOST_ASSERT(it1.index <= impl_.nparam_);
3170 1701 : BOOST_ASSERT(pos1 <= impl_.offset(id_frag));
3171 :
3172 : // calc decoded size of old range,
3173 : // minus one for the leading '?' which is
3174 : // not counted in decoded_[id_query].
3175 : // dn0 may be -1 here when the old range is
3176 : // empty and the query was non-empty; the
3177 : // matching subtraction on dn below cancels
3178 : // that out when the delta is taken.
3179 : auto dn0 =
3180 : static_cast<std::ptrdiff_t>(
3181 1701 : detail::decode_bytes_unsafe(
3182 : core::string_view(
3183 1701 : impl_.cs_ + pos0,
3184 1701 : pos1 - pos0)));
3185 1701 : if(impl_.len(id_query) > 0)
3186 1162 : dn0 -= 1;
3187 :
3188 : //------------------------------------------------
3189 : //
3190 : // Measure the number of encoded characters
3191 : // of output, and the number of inserted
3192 : // segments including internal separators.
3193 : //
3194 :
3195 1701 : std::size_t nchar = 0;
3196 1701 : std::size_t nparam = 0;
3197 1701 : if(src.measure(nchar))
3198 : {
3199 1434 : ++nchar; // for '?' or '&'
3200 : for(;;)
3201 : {
3202 1656 : ++nparam;
3203 1656 : if(! src.measure(nchar))
3204 1434 : break;
3205 222 : ++nchar; // for '&'
3206 : }
3207 : }
3208 :
3209 : //------------------------------------------------
3210 : //
3211 : // Resize
3212 : //
3213 1696 : op_t op(*this, &src.s0, &src.s1);
3214 : char* dest;
3215 : char const* end;
3216 : {
3217 1696 : auto const nremove = pos1 - pos0;
3218 : // check overflow
3219 3005 : if( nchar > nremove &&
3220 1309 : nchar - nremove >
3221 1309 : max_size() - size())
3222 : {
3223 : // too large
3224 MIS 0 : detail::throw_length_error();
3225 : }
3226 HIT 1696 : auto const nparam1 =
3227 1696 : static_cast<std::ptrdiff_t>(impl_.nparam_) +
3228 1696 : static_cast<std::ptrdiff_t>(nparam) -
3229 1696 : static_cast<std::ptrdiff_t>(it1.index) +
3230 1696 : static_cast<std::ptrdiff_t>(it0.index);
3231 1696 : BOOST_ASSERT(nparam1 >= 0);
3232 1696 : reserve_impl(size() + nchar - nremove, op);
3233 1696 : dest = s_ + pos0;
3234 1696 : end = dest + nchar;
3235 1696 : if(impl_.nparam_ > 0)
3236 : {
3237 : // needed when we move
3238 : // the beginning of the query
3239 1157 : s_[impl_.offset(id_query)] = '&';
3240 : }
3241 1696 : op.move(
3242 1696 : dest + nchar,
3243 1696 : impl_.cs_ + pos1,
3244 1696 : size() - pos1);
3245 3392 : impl_.set_size(
3246 : id_query,
3247 1696 : impl_.len(id_query) +
3248 : nchar - nremove);
3249 1696 : impl_.nparam_ =
3250 1696 : detail::to_size_type(nparam1);
3251 1696 : if(nparam1 > 0)
3252 : {
3253 : // needed when we erase
3254 : // the beginning of the query
3255 1593 : s_[impl_.offset(id_query)] = '?';
3256 : }
3257 1696 : if(s_)
3258 1696 : s_[size()] = '\0';
3259 : }
3260 1696 : auto const dest0 = dest;
3261 :
3262 : //------------------------------------------------
3263 : //
3264 : // Output params and internal separators:
3265 : //
3266 : // [ '?' param ] [ '&' param ]
3267 : //
3268 1696 : if(nparam > 0)
3269 : {
3270 1434 : if(it0.index == 0)
3271 889 : *dest++ = '?';
3272 : else
3273 545 : *dest++ = '&';
3274 1434 : src.rewind();
3275 : for(;;)
3276 : {
3277 1656 : src.copy(dest, end);
3278 1656 : if(--nparam == 0)
3279 1434 : break;
3280 222 : *dest++ = '&';
3281 : }
3282 : }
3283 :
3284 : // calc decoded size of new range; see dn0.
3285 : auto dn =
3286 : static_cast<std::ptrdiff_t>(
3287 1696 : detail::decode_bytes_unsafe(
3288 1696 : core::string_view(dest0, dest - dest0)));
3289 1696 : if(impl_.len(id_query) > 0)
3290 1593 : dn -= 1;
3291 :
3292 1696 : if(dn >= dn0)
3293 1314 : impl_.decoded_[id_query] +=
3294 1314 : detail::to_size_type(dn - dn0);
3295 : else
3296 382 : impl_.decoded_[id_query] -=
3297 382 : detail::to_size_type(dn0 - dn);
3298 :
3299 : return detail::params_iter_impl(
3300 1696 : impl_,
3301 1696 : pos0 - impl_.offset_[id_query],
3302 3392 : it0.index);
3303 1696 : }
3304 :
3305 : //------------------------------------------------
3306 :
3307 : inline
3308 : void
3309 609 : url_base::
3310 : decoded_to_lower_impl(int id) noexcept
3311 : {
3312 609 : char* it = s_ + impl_.offset(id);
3313 609 : char const* const end = s_ + impl_.offset(id + 1);
3314 3660 : while(it < end)
3315 : {
3316 3051 : if (*it != '%')
3317 : {
3318 6072 : *it = grammar::to_lower(
3319 3036 : *it);
3320 3036 : ++it;
3321 3036 : continue;
3322 : }
3323 15 : it += 3;
3324 : }
3325 609 : }
3326 :
3327 : inline
3328 : void
3329 263 : url_base::
3330 : to_lower_impl(int id) noexcept
3331 : {
3332 263 : char* it = s_ + impl_.offset(id);
3333 263 : char const* const end = s_ + impl_.offset(id + 1);
3334 1163 : while(it < end)
3335 : {
3336 1800 : *it = grammar::to_lower(
3337 900 : *it);
3338 900 : ++it;
3339 : }
3340 263 : }
3341 :
3342 : } // urls
3343 : } // boost
3344 :
3345 : #endif
|