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