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