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