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