Upa URL C++ library
A WHATWG URL Standard implementation
Loading...
Searching...
No Matches
url_search_params.h
Go to the documentation of this file.
1// Copyright 2016-2026 Rimas Misevičius
2// Distributed under the BSD-style license that can be
3// found in the LICENSE file.
4//
5
6#ifndef UPA_URL_SEARCH_PARAMS_H
7#define UPA_URL_SEARCH_PARAMS_H
8
9#include "config.h" // IWYU pragma: export
10#include "str_arg.h"
11#include "url_percent_encode.h"
12#include "url_utf.h"
13
14#ifndef UPA_MODULE
15# include <cassert>
16# include <list>
17# include <memory>
18# include <ostream>
19# include <string>
20# include <string_view>
21# include <type_traits>
22# include <utility>
23#endif // UPA_MODULE
24
25namespace upa {
26
27namespace detail {
28
29// is key value pair
30template <typename>
31constexpr bool is_pair_v = false;
32
33template<class T1, class T2>
34constexpr bool is_pair_v<std::pair<T1, T2>> = true;
35
36// is iterable over the std::pair values
37template<class T, typename = void>
38constexpr bool is_iterable_pairs_v = false;
39
40template<class T>
41constexpr bool is_iterable_pairs_v<T, std::void_t<decltype(
42 // https://stackoverflow.com/a/29634934
43 std::begin(std::declval<T&>()) != std::end(std::declval<T&>()), // begin/end and operator !=
44 ++std::declval<decltype(std::begin(std::declval<T&>()))&>(), // operator ++
45 *std::begin(std::declval<T&>()) // operator *
46 )>> = is_pair_v<remove_cvref_t<decltype(*std::begin(std::declval<T&>()))>>;
47
48// enable if `Base` is not the base class of `T`
49template<class Base, class T>
50using enable_if_not_base_of_t = std::enable_if_t<
51 !std::is_base_of_v<Base, std::decay_t<T>>, int>;
52
53} // namespace detail
54
55
56// forward declarations
57
58namespace detail {
59 class url_search_params_ptr;
60} // namespace detail
61
62UPA_EXPORT_BEGIN
63
64class url;
65
72{
73public:
74 // types
75 using name_value_pair = std::pair<std::string, std::string>;
76 using name_value_list = std::list<name_value_pair>;
77 using const_iterator = name_value_list::const_iterator;
78 using const_reverse_iterator = name_value_list::const_reverse_iterator;
81 using size_type = name_value_list::size_type;
83
84 // Constructors
85
89 url_search_params() = default;
90
95
103 noexcept(std::is_nothrow_move_constructible_v<name_value_list>);
104
110 template <class StrT, enable_if_str_arg_t<StrT> = 0>
111 inline explicit url_search_params(StrT&& query)
112 : params_(do_parse(true, std::forward<StrT>(query)))
113 {}
114
118 template<class ConT,
119 // do not hide the copy and move constructors:
120 detail::enable_if_not_base_of_t<url_search_params, ConT> = 0,
121 std::enable_if_t<detail::is_iterable_pairs_v<ConT>, int> = 0
122 >
123 inline explicit url_search_params(ConT&& cont) {
124 for (const auto& p : cont) {
125 params_.emplace_back(make_string(p.first), make_string(p.second));
126 }
127 }
128
131
132 // Assignment
133
144
155
166
167 // Operations
168
170 void clear();
171
178 void swap(url_search_params& other) noexcept;
179
183 template <class StrT, enable_if_str_arg_t<StrT> = 0>
184 void parse(StrT&& query);
185
192 template <class TN, class TV>
193 void append(TN&& name, TV&& value);
194
200 template <class TN>
201 void del(const TN& name);
202
209 template <class TN, class TV>
210 void del(const TN& name, const TV& value);
211
218 template <class TN>
219 size_type remove(const TN& name);
220
228 template <class TN, class TV>
229 size_type remove(const TN& name, const TV& value);
230
238 template <class UnaryPredicate>
239 size_type remove_if(UnaryPredicate p);
240
248 template <class TN>
249 [[nodiscard]] const std::string* get(const TN& name) const;
250
257 template <class TN>
258 [[nodiscard]] std::list<std::string> get_all(const TN& name) const;
259
266 template <class TN>
267 [[nodiscard]] bool has(const TN& name) const;
268
276 template <class TN, class TV>
277 [[nodiscard]] bool has(const TN& name, const TV& value) const;
278
285 template <class TN, class TV>
286 void set(TN&& name, TV&& value);
287
294 void sort();
295
299 void serialize(std::string& query) const;
300
306 [[nodiscard]] std::string to_string() const;
307
308 // Iterators
309
311 [[nodiscard]] inline const_iterator begin() const noexcept { return params_.begin(); }
312
314 [[nodiscard]] inline const_iterator cbegin() const noexcept { return params_.cbegin(); }
315
317 [[nodiscard]] inline const_iterator end() const noexcept { return params_.end(); }
318
320 [[nodiscard]] inline const_iterator cend() const noexcept { return params_.cend(); }
321
323 [[nodiscard]] inline const_reverse_iterator rbegin() const noexcept { return params_.rbegin(); }
324
326 [[nodiscard]] inline const_reverse_iterator crbegin() const noexcept { return params_.crbegin(); }
327
329 [[nodiscard]] inline const_reverse_iterator rend() const noexcept { return params_.rend(); }
330
332 [[nodiscard]] inline const_reverse_iterator crend() const noexcept { return params_.crend(); }
333
334 // Capacity
335
339 [[nodiscard]] inline bool empty() const noexcept { return params_.empty(); }
340
342 [[nodiscard]] inline size_type size() const noexcept { return params_.size(); }
343
344 // Utils
345
351 template <class StrT, enable_if_str_arg_t<StrT> = 0>
352 [[nodiscard]] static name_value_list do_parse(bool rem_qmark, StrT&& query);
353
359 template <class StrT, enable_if_str_arg_t<StrT> = 0>
360 static void urlencode(std::string& encoded, StrT&& value);
361
362private:
363 explicit url_search_params(url* url_ptr);
364
365 void clear_params() noexcept;
366 void copy_params(const url_search_params& other);
367 void move_params(url_search_params&& other) noexcept;
368 void parse_params(std::string_view query);
369
370 void update();
371
372 static UPA_API void urlencode_sv(std::string& encoded, std::string_view value);
373
374 friend class url;
375 friend class detail::url_search_params_ptr;
376 friend std::ostream& operator<<(std::ostream& os, const url_search_params& usp);
377
378private:
379 name_value_list params_;
380 bool is_sorted_ = false;
381 url* url_ptr_ = nullptr;
382};
383
384UPA_EXPORT_END
385
386namespace detail {
387
389{
390public:
391 constexpr url_search_params_ptr() noexcept = default;
392
393 // copy constructor initializes to nullptr
394 constexpr url_search_params_ptr(const url_search_params_ptr&) noexcept {}
396
397 // move constructor/assignment
398 UPA_CONSTEXPR_23 url_search_params_ptr(url_search_params_ptr&& other) noexcept = default;
399 UPA_CONSTEXPR_23 url_search_params_ptr& operator=(url_search_params_ptr&& other) noexcept = default;
400
401 // destructor
402 UPA_CONSTEXPR_23 ~url_search_params_ptr() = default;
403
404 inline void init(url* url_ptr) {
405 ptr_.reset(new url_search_params(url_ptr)); // NOLINT(cppcoreguidelines-owning-memory)
406 }
407
408 inline void set_url_ptr(url* url_ptr) noexcept {
409 if (ptr_)
410 ptr_->url_ptr_ = url_ptr;
411 }
412
413 inline void clear_params() noexcept {
414 assert(ptr_);
415 ptr_->clear_params();
416 }
417 inline void parse_params(std::string_view query) {
418 assert(ptr_);
419 ptr_->parse_params(query);
420 }
421
422 UPA_CONSTEXPR_23 explicit operator bool() const noexcept {
423 return static_cast<bool>(ptr_);
424 }
425 UPA_CONSTEXPR_23 url_search_params& operator*() const {
426 return *ptr_;
427 }
428 UPA_CONSTEXPR_23 url_search_params* operator->() const noexcept {
429 return ptr_.get();
430 }
431private:
432 std::unique_ptr<url_search_params> ptr_;
433};
434
435} // namespace detail
436
437
438// url_search_params inline
439
440// Copy constructor
441
443 : params_(other.params_)
444 , is_sorted_(other.is_sorted_)
445{}
446
447// Move constructor
448
450 noexcept(std::is_nothrow_move_constructible_v<name_value_list>)
451 : params_(std::move(other.params_))
452 , is_sorted_(other.is_sorted_)
453{}
454
455// Assignment
456
458 if (this != std::addressof(other)) {
459 copy_params(other);
460 update();
461 }
462 return *this;
463}
464
466 assert(url_ptr_ == nullptr);
467 move_params(std::move(other));
468 return *this;
469}
470
472 move_params(std::move(other));
473 update();
474 return *this;
475}
476
477// Operations
478
480 params_.clear();
481 is_sorted_ = true;
482 update();
483}
484
485inline void url_search_params::swap(url_search_params& other) noexcept {
486 assert(url_ptr_ == nullptr && other.url_ptr_ == nullptr);
487
488 using std::swap;
489
490 swap(params_, other.params_);
491 swap(is_sorted_, other.is_sorted_);
492}
493
494inline void url_search_params::clear_params() noexcept {
495 params_.clear();
496 is_sorted_ = true;
497}
498
499inline void url_search_params::copy_params(const url_search_params& other) {
500 params_ = other.params_;
501 is_sorted_ = other.is_sorted_;
502}
503
504inline void url_search_params::move_params(url_search_params&& other) noexcept {
505 params_ = std::move(other.params_);
506 is_sorted_ = other.is_sorted_;
507}
508
509inline void url_search_params::parse_params(std::string_view query) {
510 params_ = do_parse(false, query);
511 is_sorted_ = false;
512}
513
514template <class StrT, enable_if_str_arg_t<StrT>>
515inline void url_search_params::parse(StrT&& query) {
516 params_ = do_parse(true, std::forward<StrT>(query));
517 is_sorted_ = false;
518 update();
519}
520
521template <class TN, class TV>
522inline void url_search_params::append(TN&& name, TV&& value) {
523 params_.emplace_back(
524 make_string(std::forward<TN>(name)),
525 make_string(std::forward<TV>(value))
526 );
527 is_sorted_ = false;
528 update();
529}
530
531template <class TN>
532inline void url_search_params::del(const TN& name) {
533 const auto str_name = make_string(name);
534
535 params_.remove_if([&](const value_type& item) {
536 return item.first == str_name;
537 });
538 update();
539}
540
541template <class TN, class TV>
542inline void url_search_params::del(const TN& name, const TV& value) {
543 const auto str_name = make_string(name);
544 const auto str_value = make_string(value);
545
546 params_.remove_if([&](const value_type& item) {
547 return item.first == str_name && item.second == str_value;
548 });
549 update();
550}
551
552template <class TN>
554 const auto str_name = make_string(name);
555
556 return remove_if([&](const value_type& item) {
557 return item.first == str_name;
558 });
559}
560
561template <class TN, class TV>
562inline url_search_params::size_type url_search_params::remove(const TN& name, const TV& value) {
563 const auto str_name = make_string(name);
564 const auto str_value = make_string(value);
565
566 return remove_if([&](const value_type& item) {
567 return item.first == str_name && item.second == str_value;
568 });
569}
570
571template <class UnaryPredicate>
573#ifdef __cpp_lib_list_remove_return_type
574 const size_type count = params_.remove_if(p);
575#else
576 const size_type old_size = params_.size();
577 params_.remove_if(p);
578 const size_type count = old_size - params_.size();
579#endif
580 if (count) update();
581 return count;
582}
583
584template <class TN>
585inline const std::string* url_search_params::get(const TN& name) const {
586 const auto str_name = make_string(name);
587 for (const auto& p : params_) {
588 if (p.first == str_name)
589 return &p.second;
590 }
591 return nullptr;
592}
593
594template <class TN>
595inline std::list<std::string> url_search_params::get_all(const TN& name) const {
596 std::list<std::string> lst;
597 const auto str_name = make_string(name);
598 for (const auto& p : params_) {
599 if (p.first == str_name)
600 lst.push_back(p.second);
601 }
602 return lst;
603}
604
605template <class TN>
606inline bool url_search_params::has(const TN& name) const {
607 const auto str_name = make_string(name);
608 for (const auto& p : params_) {
609 if (p.first == str_name)
610 return true;
611 }
612 return false;
613}
614
615template <class TN, class TV>
616inline bool url_search_params::has(const TN& name, const TV& value) const {
617 const auto str_name = make_string(name);
618 const auto str_value = make_string(value);
619
620 for (const auto& p : params_) {
621 if (p.first == str_name && p.second == str_value)
622 return true;
623 }
624 return false;
625}
626
627template <class TN, class TV>
628inline void url_search_params::set(TN&& name, TV&& value) {
629 auto str_name = make_string(std::forward<TN>(name));
630 auto str_value = make_string(std::forward<TV>(value));
631
632 bool is_match = false;
633 for (auto it = params_.begin(); it != params_.end(); ) {
634 if (it->first == str_name) {
635 if (is_match) {
636 it = params_.erase(it);
637 continue;
638 }
639 it->second = std::move(str_value);
640 is_match = true;
641 }
642 ++it;
643 }
644 if (!is_match)
645 append(std::move(str_name), std::move(str_value));
646 else
647 update();
648}
649
651 // https://url.spec.whatwg.org/#dom-urlsearchparams-sort
652 // Sorting must be done by comparison of code units. The relative order
653 // between name-value pairs with equal names must be preserved.
654 if (!is_sorted_) {
655 // https://en.cppreference.com/w/cpp/container/list/sort
656 // std::list::sort preserves the order of equal elements.
657 params_.sort([](const name_value_pair& a, const name_value_pair& b) {
658 //return a.first < b.first;
659 return url_utf::compare_by_code_units(
660 a.first.data(), a.first.data() + a.first.size(),
661 b.first.data(), b.first.data() + b.first.size()) < 0;
662 });
663 is_sorted_ = true;
664 }
665 update();
666}
667
668template <class StrT, enable_if_str_arg_t<StrT>>
670 name_value_list lst;
671
672 const auto str_query = make_string(std::forward<StrT>(query));
673 auto b = str_query.begin();
674 const auto e = str_query.end();
675
676 // remove leading question-mark?
677 if (rem_qmark && b != e && *b == '?')
678 ++b;
679
680 std::string name;
681 std::string value;
682 std::string* pval = &name;
683 auto start = b;
684 for (auto it = b; it != e; ++it) {
685 switch (*it) {
686 case '=':
687 if (pval != &value)
688 pval = &value;
689 else
690 pval->push_back(*it);
691 break;
692 case '&':
693 if (start != it) {
694 url_utf::check_fix_utf8(name);
695 url_utf::check_fix_utf8(value);
696 lst.emplace_back(std::move(name), std::move(value));
697 // clear after move
698 name.clear();
699 value.clear();
700 }
701 pval = &name;
702 start = it + 1; // skip '&'
703 break;
704 case '+':
705 pval->push_back(' ');
706 break;
707 case '%':
708 if (std::distance(it, e) > 2) {
709 auto itc = it;
710 const auto uc1 = static_cast<unsigned char>(*(++itc));
711 const auto uc2 = static_cast<unsigned char>(*(++itc));
712 if (detail::is_hex_char(uc1) && detail::is_hex_char(uc2)) {
713 const char c = static_cast<char>((detail::hex_char_to_num(uc1) << 4) + detail::hex_char_to_num(uc2));
714 pval->push_back(c);
715 it = itc;
716 break;
717 }
718 }
719 [[fallthrough]];
720 default:
721 pval->push_back(*it);
722 break;
723 }
724 }
725 if (start != e) {
726 url_utf::check_fix_utf8(name);
727 url_utf::check_fix_utf8(value);
728 lst.emplace_back(std::move(name), std::move(value));
729 }
730 return lst;
731}
732
733template <class StrT, enable_if_str_arg_t<StrT>>
734inline void url_search_params::urlencode(std::string& encoded, StrT&& value) {
735 const auto str_value = make_string(std::forward<StrT>(value));
736 urlencode_sv(encoded, str_value);
737}
738
739inline void url_search_params::serialize(std::string& query) const {
740 auto it = params_.begin();
741 if (it != params_.end()) {
742 while (true) {
743 urlencode_sv(query, it->first); // name
744 query.push_back('=');
745 urlencode_sv(query, it->second); // value
746 if (++it == params_.end())
747 break;
748 query.push_back('&');
749 }
750 }
751}
752
753inline std::string url_search_params::to_string() const {
754 std::string query;
755 serialize(query);
756 return query;
757}
758
759UPA_EXPORT_BEGIN
760
761// Non-member functions
762
771inline std::ostream& operator<<(std::ostream& os, const url_search_params& usp) {
772 return os << usp.to_string();
773}
774
784inline void swap(url_search_params& lhs, url_search_params& rhs) noexcept {
785 lhs.swap(rhs);
786}
787
788UPA_EXPORT_END
789
790} // namespace upa
791
792#endif // UPA_URL_SEARCH_PARAMS_H
URLSearchParams class.
const_iterator begin() const noexcept
url_search_params()=default
Default constructor.
url_search_params & operator=(const url_search_params &other)
Copy assignment.
bool empty() const noexcept
void set(TN &&name, TV &&value)
const_reverse_iterator rend() const noexcept
void append(TN &&name, TV &&value)
const_iterator cbegin() const noexcept
const_iterator cend() const noexcept
static void urlencode(std::string &encoded, StrT &&value)
bool has(const TN &name) const
url_search_params & safe_assign(url_search_params &&other)
Safe move assignment.
url_search_params(StrT &&query)
Parsing constructor.
std::pair< std::string, std::string > name_value_pair
void del(const TN &name)
const_reverse_iterator crend() const noexcept
const_iterator end() const noexcept
~url_search_params()=default
destructor
name_value_list::size_type size_type
std::list< std::string > get_all(const TN &name) const
void clear()
Clears parameters.
const std::string * get(const TN &name) const
const_reverse_iterator crbegin() const noexcept
void parse(StrT &&query)
size_type size() const noexcept
friend class detail::url_search_params_ptr
void swap(url_search_params &other) noexcept
Swaps the contents of two url_search_params.
static name_value_list do_parse(bool rem_qmark, StrT &&query)
size_type remove(const TN &name)
name_value_list::const_iterator const_iterator
size_type remove_if(UnaryPredicate p)
name_value_list::const_reverse_iterator const_reverse_iterator
std::string to_string() const
const_reverse_iterator rbegin() const noexcept
void serialize(std::string &query) const
const_reverse_iterator reverse_iterator
std::list< name_value_pair > name_value_list
URL class.
Definition url.h:84
Definition url.h:3418
Definition url.h:50
std::ostream & operator<<(std::ostream &os, const url &url)
Performs stream output on URL.
Definition url.h:3152
void swap(url &lhs, url &rhs) noexcept
Swaps the contents of two URLs.
Definition url.h:3162