Upa URL C++ library
A WHATWG URL Standard implementation
Loading...
Searching...
No Matches
url.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// This file contains portions of modified code from:
6// https://cs.chromium.org/chromium/src/url/url_canon_etc.cc
7// Copyright 2013 The Chromium Authors. All rights reserved.
8//
9
10// URL Standard
11// https://url.spec.whatwg.org/
12//
13// Infra Standard - fundamental concepts upon which standards are built
14// https://infra.spec.whatwg.org/
15//
16
17#ifndef UPA_URL_H
18#define UPA_URL_H
19
20#include "buffer.h"
21#include "config.h" // IWYU pragma: export
22#include "str_arg.h" // IWYU pragma: export
23#include "url_host.h" // IWYU pragma: export
24#include "url_percent_encode.h" // IWYU pragma: export
25#include "url_result.h" // IWYU pragma: export
26#include "url_search_params.h" // IWYU pragma: export
27#include "url_version.h" // IWYU pragma: export
28#include "util.h"
29
30#ifndef UPA_MODULE
31# include <algorithm>
32# include <array>
33# include <cassert>
34# include <cstddef>
35# include <cstdint> // uint8_t
36# include <filesystem>
37# include <functional> // std::hash
38# include <iterator>
39# include <ostream>
40# include <string>
41# include <string_view>
42# include <type_traits>
43# include <utility>
44# include <vector>
45#endif // UPA_MODULE
46
47// not yet
48// #define UPA_URL_USE_ENCODING
49
50namespace upa {
51namespace detail {
52
53// Forward declarations
54class url_serializer;
55class url_setter;
56class url_parser;
57
58// Scheme info
59
60struct alignas(32) scheme_info {
61 std::string_view scheme;
62 int default_port; // -1 if none
63 unsigned is_special : 1; // "ftp", "file", "http", "https", "ws", "wss"
64 unsigned is_file : 1; // "file"
65 unsigned is_http : 1; // "http", "https"
66 unsigned is_ws : 1; // "ws", "wss"
67};
68
69UPA_API const scheme_info* get_scheme_info(std::string_view src);
70
71// Values of the what() function of url_error exception
72inline constexpr const char* kURLParseError = "URL parse error";
73inline constexpr const char* kBaseURLParseError = "Base URL parse error";
74
75} // namespace detail
76
77UPA_EXPORT_BEGIN
78
84class url {
85public:
102
106 url() = default;
107
111 url(const url& other) = default;
112
118 url(url&& other) noexcept;
119
124 url& operator=(const url& other) = default;
125
132 url& operator=(url&& other) noexcept;
133
141 url& safe_assign(url&& other);
142
149 template <class T, enable_if_str_arg_t<T> = 0>
150 inline explicit url(const T& str_url, const url* pbase = nullptr)
151 : url{ str_url, pbase, detail::kURLParseError }
152 {}
153
160 template <class T, enable_if_str_arg_t<T> = 0>
161 inline explicit url(const T& str_url, const url& base)
162 : url{ str_url, &base, detail::kURLParseError }
163 {}
164
171 template <class T, class TB, enable_if_str_arg_t<T> = 0, enable_if_str_arg_t<TB> = 0>
172 inline explicit url(const T& str_url, const TB& str_base)
173 : url{ str_url, url{ str_base, nullptr, detail::kBaseURLParseError } }
174 {}
175
177 ~url() = default;
178
179 // Operations
180
184 void clear();
185
189 void swap(url& other) noexcept;
190
191 // Parser
192
198 template <class T, enable_if_str_arg_t<T> = 0>
199 inline validation_errc parse(const T& str_url, const url* base = nullptr) {
200 const auto inp = make_str_arg(str_url);
201 return do_parse(inp.begin(), inp.end(), base);
202 }
203
209 template <class T, enable_if_str_arg_t<T> = 0>
210 inline validation_errc parse(const T& str_url, const url& base) {
211 return parse(str_url, &base);
212 }
213
219 template <class T, class TB, enable_if_str_arg_t<T> = 0, enable_if_str_arg_t<TB> = 0>
220 inline validation_errc parse(const T& str_url, const TB& str_base) {
221 upa::url base;
222 const auto res = base.parse(str_base, nullptr);
223 return res == validation_errc::ok
224 ? parse(str_url, &base)
225 : res;
226 }
227
236 template <class T, enable_if_str_arg_t<T> = 0>
237 [[nodiscard]] static inline bool can_parse(const T& str_url, const url* pbase = nullptr) {
239 return url.for_can_parse(str_url, pbase) == validation_errc::ok;
240 }
241
250 template <class T, enable_if_str_arg_t<T> = 0>
251 [[nodiscard]] static inline bool can_parse(const T& str_url, const url& base) {
252 return can_parse(str_url, &base);
253 }
254
264 template <class T, class TB, enable_if_str_arg_t<T> = 0, enable_if_str_arg_t<TB> = 0>
265 [[nodiscard]] static inline bool can_parse(const T& str_url, const TB& str_base) {
266 upa::url base;
267 return
268 base.for_can_parse(str_base, nullptr) == validation_errc::ok &&
269 can_parse(str_url, &base);
270 }
271
272 // Setters
273
281 template <class StrT, enable_if_str_arg_t<StrT> = 0>
282 bool href(const StrT& str);
284 template <class StrT, enable_if_str_arg_t<StrT> = 0>
285 inline bool set_href(const StrT& str) { return href(str); }
286
294 template <class StrT, enable_if_str_arg_t<StrT> = 0>
295 bool protocol(const StrT& str);
297 template <class StrT, enable_if_str_arg_t<StrT> = 0>
298 inline bool set_protocol(const StrT& str) { return protocol(str); }
299
307 template <class StrT, enable_if_str_arg_t<StrT> = 0>
308 bool username(const StrT& str);
310 template <class StrT, enable_if_str_arg_t<StrT> = 0>
311 inline bool set_username(const StrT& str) { return username(str); }
312
320 template <class StrT, enable_if_str_arg_t<StrT> = 0>
321 bool password(const StrT& str);
323 template <class StrT, enable_if_str_arg_t<StrT> = 0>
324 inline bool set_password(const StrT& str) { return password(str); }
325
333 template <class StrT, enable_if_str_arg_t<StrT> = 0>
334 bool host(const StrT& str);
336 template <class StrT, enable_if_str_arg_t<StrT> = 0>
337 inline bool set_host(const StrT& str) { return host(str); }
338
346 template <class StrT, enable_if_str_arg_t<StrT> = 0>
347 bool hostname(const StrT& str);
349 template <class StrT, enable_if_str_arg_t<StrT> = 0>
350 inline bool set_hostname(const StrT& str) { return hostname(str); }
351
359 template <class StrT, enable_if_str_arg_t<StrT> = 0>
360 bool port(const StrT& str);
362 template <class StrT, enable_if_str_arg_t<StrT> = 0>
363 inline bool set_port(const StrT& str) { return port(str); }
364
372 template <class StrT, enable_if_str_arg_t<StrT> = 0>
373 bool pathname(const StrT& str);
375 template <class StrT, enable_if_str_arg_t<StrT> = 0>
376 inline bool set_pathname(const StrT& str) { return pathname(str); }
377
385 template <class StrT, enable_if_str_arg_t<StrT> = 0>
386 bool search(const StrT& str);
388 template <class StrT, enable_if_str_arg_t<StrT> = 0>
389 inline bool set_search(const StrT& str) { return search(str); }
390
398 template <class StrT, enable_if_str_arg_t<StrT> = 0>
399 bool hash(const StrT& str);
401 template <class StrT, enable_if_str_arg_t<StrT> = 0>
402 inline bool set_hash(const StrT& str) { return hash(str); }
403
404 // Getters
405
411 [[nodiscard]] std::string_view href() const UPA_LIFETIMEBOUND;
413 [[nodiscard]] inline std::string_view get_href() const UPA_LIFETIMEBOUND { return href(); }
414
423 [[nodiscard]] std::string origin() const;
424
430 [[nodiscard]] std::string_view protocol() const UPA_LIFETIMEBOUND;
432 [[nodiscard]] inline std::string_view get_protocol() const UPA_LIFETIMEBOUND { return protocol(); }
433
439 [[nodiscard]] std::string_view username() const UPA_LIFETIMEBOUND;
441 [[nodiscard]] inline std::string_view get_username() const UPA_LIFETIMEBOUND { return username(); }
442
448 [[nodiscard]] std::string_view password() const UPA_LIFETIMEBOUND;
450 [[nodiscard]] inline std::string_view get_password() const UPA_LIFETIMEBOUND { return password(); }
451
457 [[nodiscard]] std::string_view host() const UPA_LIFETIMEBOUND;
459 [[nodiscard]] inline std::string_view get_host() const UPA_LIFETIMEBOUND { return host(); }
460
466 [[nodiscard]] std::string_view hostname() const UPA_LIFETIMEBOUND;
468 [[nodiscard]] inline std::string_view get_hostname() const UPA_LIFETIMEBOUND { return hostname(); }
469
473 [[nodiscard]] HostType host_type() const noexcept;
474
480 [[nodiscard]] std::string_view port() const UPA_LIFETIMEBOUND;
482 [[nodiscard]] inline std::string_view get_port() const UPA_LIFETIMEBOUND { return port(); }
483
486 [[nodiscard]] int port_int() const;
487
491 [[nodiscard]] int real_port_int() const;
492
496 [[nodiscard]] std::string_view path() const UPA_LIFETIMEBOUND;
498 [[nodiscard]] inline std::string_view get_path() const UPA_LIFETIMEBOUND { return path(); }
499
505 [[nodiscard]] std::string_view pathname() const UPA_LIFETIMEBOUND;
507 [[nodiscard]] inline std::string_view get_pathname() const UPA_LIFETIMEBOUND { return pathname(); }
508
514 [[nodiscard]] std::string_view search() const UPA_LIFETIMEBOUND;
516 [[nodiscard]] inline std::string_view get_search() const UPA_LIFETIMEBOUND { return search(); }
517
523 [[nodiscard]] std::string_view hash() const UPA_LIFETIMEBOUND;
525 [[nodiscard]] inline std::string_view get_hash() const UPA_LIFETIMEBOUND { return hash(); }
526
535 url_search_params& search_params()& UPA_LIFETIMEBOUND;
536
541 [[nodiscard]] url_search_params search_params()&&;
542
550 [[nodiscard]] std::string_view serialize(bool exclude_fragment = false) const UPA_LIFETIMEBOUND;
551
552 // Get url info
553
557 [[nodiscard]] bool empty() const noexcept;
558
564 [[nodiscard]] bool is_valid() const noexcept;
565
595 [[nodiscard]] UPA_API std::pair<std::size_t, std::size_t> get_part_pos(PartType t,
596 bool with_sep = false) const;
597
614 [[nodiscard]] std::string_view get_part_view(PartType t) const UPA_LIFETIMEBOUND;
615
620 [[nodiscard]] bool is_empty(PartType t) const;
621
633 [[nodiscard]] bool is_null(PartType t) const noexcept;
634
637 [[nodiscard]] bool is_special_scheme() const noexcept;
638
640 [[nodiscard]] bool is_file_scheme() const noexcept;
641
643 [[nodiscard]] bool is_http_scheme() const noexcept;
644
646 [[nodiscard]] bool has_credentials() const;
647
650 [[nodiscard]] bool has_opaque_path() const noexcept;
651
653 [[nodiscard]] std::string to_string() const;
654
655private:
656 enum UrlFlag : unsigned {
657 // not null flags
658 SCHEME_FLAG = (1u << SCHEME),
659 USERNAME_FLAG = (1u << USERNAME),
660 PASSWORD_FLAG = (1u << PASSWORD),
661 HOST_FLAG = (1u << HOST),
662 PORT_FLAG = (1u << PORT),
663 PATH_FLAG = (1u << PATH),
664 QUERY_FLAG = (1u << QUERY),
665 FRAGMENT_FLAG = (1u << FRAGMENT),
666 // other flags
667 OPAQUE_PATH_FLAG = (1u << (PART_COUNT + 0)),
668 VALID_FLAG = (1u << (PART_COUNT + 1)),
669 // host type
670 HOST_TYPE_SHIFT = (PART_COUNT + 2),
671 HOST_TYPE_MASK = (7u << HOST_TYPE_SHIFT),
672
673 // initial flags (empty (but not null) parts)
674 // https://url.spec.whatwg.org/#url-representation
675 INITIAL_FLAGS = SCHEME_FLAG | USERNAME_FLAG | PASSWORD_FLAG | PATH_FLAG,
676 };
677
678 // parsing constructor
679 template <class T, enable_if_str_arg_t<T> = 0>
680 explicit url(const T& str_url, const url* base, const char* what_arg);
681
682 // parser
683 template <typename CharT>
684 validation_errc do_parse(const CharT* first, const CharT* last, const url* base);
685
686 template <class T, enable_if_str_arg_t<T> = 0>
687 validation_errc for_can_parse(const T& str_url, const url* base);
688
689 // set scheme
690 void set_scheme_str(std::string_view str);
691 void set_scheme(const url& src);
692 void set_scheme(std::string_view str);
693 void set_scheme(std::size_t scheme_length);
694
695 // Get origin of special URL excluding file URL
696 std::string origin_of_special_url() const;
697
698 // path util
699 std::string_view get_path_first_string(std::size_t len) const UPA_LIFETIMEBOUND;
700 // path shortening
701 bool get_path_rem_last(std::size_t& path_end, std::size_t& path_segment_count) const;
702 bool get_shorten_path(std::size_t& path_end, std::size_t& path_segment_count) const;
703
704 // flags
705 void set_flag(UrlFlag flag) noexcept;
706
707 void set_has_opaque_path() noexcept;
708
709 void set_host_type(HostType ht) noexcept;
710
711 // info
712 bool canHaveUsernamePasswordPort() const;
713
714 // url record
715 void move_record(url& other) noexcept;
716
717 // search params
718 void clear_search_params() noexcept;
719 void parse_search_params();
720
721private:
722 std::string norm_url_;
723 std::array<std::size_t, PART_COUNT> part_end_ = {};
724 const detail::scheme_info* scheme_inf_ = nullptr;
725 unsigned flags_ = INITIAL_FLAGS;
726 std::size_t path_segment_count_ = 0;
727 detail::url_search_params_ptr search_params_ptr_;
728
729 friend bool operator==(const url& lhs, const url& rhs) noexcept;
730 friend std::ostream& operator<<(std::ostream& os, const url& url);
731 friend struct std::hash<url>;
732 friend class detail::url_serializer;
733 friend class detail::url_setter;
734 friend class detail::url_parser;
735 friend class url_search_params;
736};
737
738UPA_EXPORT_END
739
740namespace detail {
741
742class UPA_SO_VISIBLE url_serializer : public host_output {
743public:
744 url_serializer() = delete;
745 url_serializer(const url_serializer&) = delete;
746 url_serializer& operator=(const url_serializer&) = delete;
747
748 inline explicit url_serializer(url& dest_url, bool need_save = true)
749 : host_output(need_save)
750 , url_(dest_url)
751 , last_pt_(url::SCHEME)
752 {}
753
754 ~url_serializer() override = default;
755
756 inline void new_url() {
757 if (!url_.empty())
758 url_.clear();
759 }
760 inline virtual void reserve(std::size_t new_cap) {
761 util::reserve(url_.norm_url_, new_cap);
762 }
763
764 // set data
765 inline void set_scheme(const url& src) { url_.set_scheme(src); }
766 inline void set_scheme(std::string_view str) { url_.set_scheme(str); }
767 inline void set_scheme(std::size_t scheme_length) { url_.set_scheme(scheme_length); }
768
769 // set scheme
770 virtual std::string& start_scheme();
771 virtual void save_scheme();
772
773 // set url's part
774 void fill_parts_offset(url::PartType t1, url::PartType t2, std::size_t offset);
775 virtual std::string& start_part(url::PartType new_pt);
776 virtual void save_part();
777
778 inline virtual void clear_part(url::PartType /*pt*/) {}
779
780 // set empty host
781 void set_empty_host();
782
783 // empties not empty host
784 virtual void empty_host();
785
786 // host_output overrides
787 std::string& hostStart() override;
788 void hostDone(HostType ht) override;
789
790 // Path operations
791
792 // append the empty string to url’s path (list)
793 void append_empty_path_segment();
794 // append string to url's path (list)
795 virtual std::string& start_path_segment();
796 virtual void save_path_segment();
797 virtual void commit_path();
798 // if '/' not required:
799 std::string& start_path_string();
800 void save_path_string();
801
802 virtual void shorten_path();
803 //UNUSED// retunrs how many slashes are removed
804 //virtual std::size_t remove_leading_path_slashes();
805
806 using PathOpFn = bool (url::*)(std::size_t& path_end, std::size_t& segment_count) const;
807 void append_parts(const url& src, url::PartType t1, url::PartType t2, PathOpFn pathOpFn = nullptr);
808
809 // flags
810 inline void set_flag(const url::UrlFlag flag) { url_.set_flag(flag); }
811 inline void set_host_type(const HostType ht) { url_.set_host_type(ht); }
812 // IMPORTANT: has-an-opaque-path flag must be set before or just after
813 // SCHEME set; because other part's serialization depends on this flag
814 inline void set_has_opaque_path() {
815 assert(last_pt_ == url::SCHEME);
816 url_.set_has_opaque_path();
817 }
818
819 // get info
820 inline std::string_view get_part_view(url::PartType t) const { return url_.get_part_view(t); }
821 inline bool is_empty(const url::PartType t) const { return url_.is_empty(t); }
822 inline virtual bool is_empty_path() const {
823 assert(!url_.has_opaque_path());
824 // path_segment_count_ has meaning only if path is a list (path isn't opaque)
825 return url_.path_segment_count_ == 0;
826 }
827 inline bool is_null(const url::PartType t) const noexcept { return url_.is_null(t); }
828 inline bool is_special_scheme() const noexcept { return url_.is_special_scheme(); }
829 inline bool is_file_scheme() const noexcept { return url_.is_file_scheme(); }
830 inline bool has_credentials() const { return url_.has_credentials(); }
831 inline const detail::scheme_info* scheme_inf() const noexcept { return url_.scheme_inf_; }
832 inline int port_int() const { return url_.port_int(); }
833
834protected:
835 void adjust_path_prefix();
836
837 std::size_t get_part_pos(url::PartType pt) const;
838 std::size_t get_part_len(url::PartType pt) const;
839 void replace_part(url::PartType new_pt, const char* str, std::size_t len);
840 void replace_part(url::PartType last_pt, const char* str, std::size_t len,
841 url::PartType first_pt, std::size_t len0);
842
843protected:
844 url& url_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
845 // last serialized URL's part
846 url::PartType last_pt_;
847};
848
849
850class UPA_SO_VISIBLE url_setter : public url_serializer {
851public:
852 url_setter() = delete;
853 url_setter(const url_setter&) = delete;
854 url_setter& operator=(const url_setter&) = delete;
855
856 inline explicit url_setter(url& dest_url)
857 : url_serializer(dest_url)
858 , use_strp_(true)
859 , curr_pt_(url::SCHEME)
860 {}
861
862 ~url_setter() override = default;
863
864 //???
865 void reserve(std::size_t new_cap) override;
866
867 // set scheme
868 std::string& start_scheme() override;
869 void save_scheme() override;
870
871 // set/clear/empty url's part
872 std::string& start_part(url::PartType new_pt) override;
873 void save_part() override;
874
875 void clear_part(url::PartType pt) override;
876 void empty_part(url::PartType pt); // override
877
878 void empty_host() override;
879
880 // path
881 std::string& start_path_segment() override;
882 void save_path_segment() override;
883 void commit_path() override;
884
885 void shorten_path() override;
886 //UNUSED// retunrs how many slashes are removed
887 //std::size_t remove_leading_path_slashes() override;
888 bool is_empty_path() const override;
889
890protected:
891 url::PartType find_last_part(url::PartType pt) const;
892
893private:
894 bool use_strp_;
895 // buffer for URL's part
896 std::string strp_;
897 // path segment end positions in the strp_
898 std::vector<std::size_t> path_seg_end_;
899 // current URL's part
900 url::PartType curr_pt_;
901};
902
903
904class url_parser {
905public:
906 enum State {
907 not_set_state = 0,
908 scheme_start_state,
909 scheme_state,
910 no_scheme_state,
911 special_relative_or_authority_state,
912 path_or_authority_state,
913 relative_state,
914 relative_slash_state,
915 special_authority_slashes_state,
916 special_authority_ignore_slashes_state,
917 authority_state,
918 host_state,
919 hostname_state,
920 port_state,
921 file_state,
922 file_slash_state,
923 file_host_state,
924 path_start_state,
925 path_state,
926 opaque_path_state,
927 query_state,
928 fragment_state
929 };
930
931 template <typename CharT>
932 static validation_errc url_parse(url_serializer& urls, const CharT* first, const CharT* last, const url* base, State state_override);
933
934 template <typename CharT>
935 static validation_errc parse_host(url_serializer& urls, const CharT* first, const CharT* last);
936
937 template <typename CharT>
938 static void parse_path(url_serializer& urls, const CharT* first, const CharT* last);
939
940private:
941 template <typename CharT>
942 static void do_path_segment(const CharT* pointer, const CharT* last, std::string& output);
943
944 template <typename CharT>
945 static void do_opaque_path(const CharT* pointer, const CharT* last, std::string& output);
946};
947
948
949// Part start
950inline constexpr std::uint8_t kPartStart[url::PART_COUNT] = {
951 0, 0, 0,
952 1, // ':' PASSWORD
953 0, 0,
954 1, // ':' PORT
955 0, 0,
956 1, // '?' QUERY
957 1 // '#' FRAGMENT
958};
959
960constexpr int port_from_str(const char* first, const char* last) noexcept {
961 int port = 0;
962 for (auto it = first; it != last; ++it) {
963 port = port * 10 + (*it - '0');
964 }
965 return port;
966}
967
968// Removable URL chars
969
970// chars to trim (C0 control or space: U+0000 to U+001F or U+0020)
971template <typename CharT>
972constexpr bool is_trim_char(CharT ch) noexcept {
973 return util::to_unsigned(ch) <= ' ';
974}
975
976// chars what should be removed from the URL (ASCII tab or newline: U+0009, U+000A, U+000D)
977template <typename CharT>
978constexpr bool is_removable_char(CharT ch) noexcept {
979 return ch == '\r' || ch == '\n' || ch == '\t';
980}
981
982template <typename CharT>
983constexpr void do_trim(const CharT*& first, const CharT*& last) noexcept {
984 // remove leading C0 controls and space
985 while (first < last && is_trim_char(*first))
986 ++first;
987 // remove trailing C0 controls and space
988 while (first < last && is_trim_char(*(last-1)))
989 --last;
990}
991
992// DoRemoveURLWhitespace
993// https://cs.chromium.org/chromium/src/url/url_canon_etc.cc
994template <typename CharT>
995inline void do_remove_whitespace(const CharT*& first, const CharT*& last, simple_buffer<CharT>& buff) {
996 // Fast verification that there's nothing that needs removal. This is the 99%
997 // case, so we want it to be fast and don't care about impacting the speed
998 // when we do find whitespace.
999 for (auto it = first; it < last; ++it) {
1000 if (!is_removable_char(*it))
1001 continue;
1002 // copy non whitespace chars into the new buffer and return it
1003 buff.reserve(last - first);
1004 buff.append(first, it);
1005 for (; it < last; ++it) {
1006 if (!is_removable_char(*it))
1007 buff.push_back(*it);
1008 }
1009 first = buff.data();
1010 last = buff.data() + buff.size();
1011 break;
1012 }
1013}
1014
1015// reverse find
1016
1017template<class InputIt, class T>
1018constexpr InputIt find_last(InputIt first, InputIt last, const T& value) {
1019 for (auto it = last; it > first;) {
1020 --it;
1021 if (*it == value) return it;
1022 }
1023 return last;
1024}
1025
1026// special chars
1027
1028template <typename CharT>
1029constexpr bool is_slash(CharT ch) noexcept {
1030 return ch == '/' || ch == '\\';
1031}
1032
1033template <typename CharT>
1034constexpr bool is_posix_slash(CharT ch) noexcept {
1035 return ch == '/';
1036}
1037
1038template <typename CharT>
1039constexpr bool is_windows_slash(CharT ch) noexcept {
1040 return ch == '\\' || ch == '/';
1041}
1042
1043// Scheme chars
1044
1045template <typename CharT>
1046constexpr bool is_first_scheme_char(CharT ch) noexcept {
1047 return is_ascii_alpha(ch);
1048}
1049
1050template <typename CharT>
1051constexpr bool is_authority_end_char(CharT c) noexcept {
1052 return c == '/' || c == '?' || c == '#';
1053}
1054
1055template <typename CharT>
1056constexpr bool is_special_authority_end_char(CharT c) noexcept {
1057 return c == '/' || c == '?' || c == '#' || c == '\\';
1058}
1059
1060// Windows drive letter
1061
1062// https://url.spec.whatwg.org/#windows-drive-letter
1063template <typename CharT>
1064constexpr bool is_windows_drive(CharT c1, CharT c2) noexcept {
1065 return is_ascii_alpha(c1) && (c2 == ':' || c2 == '|');
1066}
1067
1068// https://url.spec.whatwg.org/#normalized-windows-drive-letter
1069template <typename CharT>
1070constexpr bool is_normalized_windows_drive(CharT c1, CharT c2) noexcept {
1071 return is_ascii_alpha(c1) && c2 == ':';
1072}
1073
1074// https://url.spec.whatwg.org/#start-with-a-windows-drive-letter
1075template <typename CharT>
1076constexpr bool starts_with_windows_drive(const CharT* pointer, const CharT* last) noexcept {
1077 const auto length = last - pointer;
1078 return
1079 (length == 2 || (length > 2 && detail::is_special_authority_end_char(pointer[2]))) &&
1080 detail::is_windows_drive(pointer[0], pointer[1]);
1081/*** alternative implementation ***
1082 return
1083 length >= 2 &&
1084 detail::is_windows_drive(pointer[0], pointer[1]) &&
1085 (length == 2 || detail::is_special_authority_end_char(pointer[2]));
1086***/
1087}
1088
1089// Windows drive letter in OS path
1090//
1091// NOTE: Windows OS supports only normalized Windows drive letters.
1092
1093// Check url's pathname has Windows drive, i.e. starts with "/C:/" or is "/C:"
1094// see also: detail::starts_with_windows_drive
1095constexpr bool pathname_has_windows_os_drive(std::string_view pathname) noexcept {
1096 return
1097 (pathname.length() == 3 || (pathname.length() > 3 && is_windows_slash(pathname[3]))) &&
1098 is_windows_slash(pathname[0]) &&
1099 is_normalized_windows_drive(pathname[1], pathname[2]);
1100}
1101
1105template <typename CharT>
1106constexpr const CharT* is_windows_os_drive_absolute_path(const CharT* pointer, const CharT* last) noexcept {
1107 return (last - pointer > 2 &&
1108 is_normalized_windows_drive(pointer[0], pointer[1]) &&
1109 is_windows_slash(pointer[2]))
1110 ? pointer + 3 : nullptr;
1111}
1112
1113} // namespace detail
1114
1115
1116// url class
1117
1118inline url::url(url&& other) noexcept
1119 : norm_url_(std::move(other.norm_url_))
1120 , part_end_(other.part_end_)
1121 , scheme_inf_(other.scheme_inf_)
1122 , flags_(other.flags_)
1123 , path_segment_count_(other.path_segment_count_)
1124 , search_params_ptr_(std::move(other.search_params_ptr_))
1125{
1126 search_params_ptr_.set_url_ptr(this);
1127}
1128
1129inline url& url::operator=(url&& other) noexcept {
1130 // move data
1131 move_record(other);
1132 search_params_ptr_ = std::move(other.search_params_ptr_);
1133
1134 // setup search params
1135 search_params_ptr_.set_url_ptr(this);
1136
1137 return *this;
1138}
1139
1140inline url& url::safe_assign(url&& other) {
1141 if (search_params_ptr_) {
1142 if (other.search_params_ptr_) {
1143 move_record(other);
1144 search_params_ptr_->move_params(std::move(*other.search_params_ptr_));
1145 } else {
1146 // parse search parameters before move assign for strong exception guarantee
1147 url_search_params params(&other);
1148 move_record(other);
1149 search_params_ptr_->move_params(std::move(params));
1150 }
1151 } else {
1152 move_record(other);
1153 }
1154 return *this;
1155}
1156
1157inline void url::move_record(url& other) noexcept {
1158 norm_url_ = std::move(other.norm_url_);
1159 part_end_ = other.part_end_;
1160 scheme_inf_ = other.scheme_inf_;
1161 flags_ = other.flags_;
1162 path_segment_count_ = other.path_segment_count_;
1163}
1164
1165// url getters
1166
1167inline std::string_view url::href() const UPA_LIFETIMEBOUND {
1168 return norm_url_;
1169}
1170
1171inline std::string url::to_string() const {
1172 return norm_url_;
1173}
1174
1175// Origin
1176// https://url.spec.whatwg.org/#concept-url-origin
1177
1178// ASCII serialization of an origin
1179// https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin
1180inline std::string url::origin() const {
1181 if (is_special_scheme()) {
1182 if (is_file_scheme())
1183 return "null"; // opaque origin
1184 return origin_of_special_url();
1185 }
1186 if (get_part_view(SCHEME) == std::string_view{ "blob", 4 }) {
1187 // Note: this library does not support blob URL store, so it allways assumes
1188 // URL's blob URL entry is null and retrieves origin from the URL's path.
1189 url path_url;
1190 if (path_url.parse(get_part_view(PATH)) == validation_errc::ok &&
1191 path_url.is_http_scheme())
1192 return path_url.origin_of_special_url();
1193 }
1194 return "null"; // opaque origin
1195}
1196
1197inline std::string url::origin_of_special_url() const {
1198 // "scheme://"
1199 std::string str_origin(norm_url_, 0, part_end_[SCHEME_SEP]);
1200 // "host:port"
1201 str_origin.append(norm_url_.data() + part_end_[HOST_START], norm_url_.data() + part_end_[PORT]);
1202 return str_origin;
1203}
1204
1205inline std::string_view url::protocol() const UPA_LIFETIMEBOUND {
1206 // "scheme:"
1207 return { norm_url_.data(), part_end_[SCHEME] ? part_end_[SCHEME] + 1 : 0 };
1208}
1209
1210inline std::string_view url::username() const UPA_LIFETIMEBOUND {
1211 return get_part_view(USERNAME);
1212}
1213
1214inline std::string_view url::password() const UPA_LIFETIMEBOUND {
1215 return get_part_view(PASSWORD);
1216}
1217
1218inline std::string_view url::host() const UPA_LIFETIMEBOUND {
1219 if (is_null(HOST))
1220 return {};
1221 // "hostname:port"
1222 const std::size_t b = part_end_[HOST_START];
1223 const std::size_t e = is_null(PORT) ? part_end_[HOST] : part_end_[PORT];
1224 return { norm_url_.data() + b, e - b };
1225}
1226
1227inline std::string_view url::hostname() const UPA_LIFETIMEBOUND {
1228 return get_part_view(HOST);
1229}
1230
1231inline HostType url::host_type() const noexcept {
1232 return static_cast<HostType>((flags_ & HOST_TYPE_MASK) >> HOST_TYPE_SHIFT);
1233}
1234
1235inline std::string_view url::port() const UPA_LIFETIMEBOUND {
1236 return get_part_view(PORT);
1237}
1238
1239inline int url::port_int() const {
1240 const auto vport = get_part_view(PORT);
1241 return !vport.empty() ? detail::port_from_str(vport.data(), vport.data() + vport.length()) : -1;
1242}
1243
1244inline int url::real_port_int() const {
1245 const auto vport = get_part_view(PORT);
1246 if (!vport.empty())
1247 return detail::port_from_str(vport.data(), vport.data() + vport.length());
1248 return scheme_inf_ ? scheme_inf_->default_port : -1;
1249}
1250
1251// pathname + search
1252inline std::string_view url::path() const UPA_LIFETIMEBOUND {
1253 // "pathname?query"
1254 const std::size_t b = part_end_[PATH - 1];
1255 const std::size_t e = part_end_[QUERY] ? part_end_[QUERY] : part_end_[PATH];
1256 return { norm_url_.data() + b, e ? e - b : 0 };
1257}
1258
1259inline std::string_view url::pathname() const UPA_LIFETIMEBOUND {
1260 // https://url.spec.whatwg.org/#dom-url-pathname
1261 // already serialized as needed
1262 return get_part_view(PATH);
1263}
1264
1265inline std::string_view url::search() const UPA_LIFETIMEBOUND {
1266 const std::size_t b = part_end_[QUERY - 1];
1267 const std::size_t e = part_end_[QUERY];
1268 // is empty?
1269 if (b + 1 >= e)
1270 return {};
1271 // return with '?'
1272 return { norm_url_.data() + b, e - b };
1273}
1274
1275inline std::string_view url::hash() const UPA_LIFETIMEBOUND {
1276 const std::size_t b = part_end_[FRAGMENT - 1];
1277 const std::size_t e = part_end_[FRAGMENT];
1278 // is empty?
1279 if (b + 1 >= e)
1280 return {};
1281 // return with '#'
1282 return { norm_url_.data() + b, e - b };
1283}
1284
1285inline url_search_params& url::search_params()& UPA_LIFETIMEBOUND {
1286 if (!search_params_ptr_)
1287 search_params_ptr_.init(this);
1288 return *search_params_ptr_;
1289}
1290
1292 if (search_params_ptr_)
1293 return std::move(*search_params_ptr_);
1294 return url_search_params{ search() };
1295}
1296
1297inline void url::clear_search_params() noexcept {
1298 if (search_params_ptr_)
1299 search_params_ptr_.clear_params();
1300}
1301
1302inline void url::parse_search_params() {
1303 if (search_params_ptr_)
1304 search_params_ptr_.parse_params(get_part_view(QUERY));
1305}
1306
1307inline std::string_view url::serialize(bool exclude_fragment) const UPA_LIFETIMEBOUND {
1308 if (exclude_fragment && part_end_[FRAGMENT])
1309 return { norm_url_.data(), part_end_[QUERY] };
1310 return norm_url_;
1311}
1312
1313// Get url info
1314
1315inline bool url::empty() const noexcept {
1316 return norm_url_.empty();
1317}
1318
1319inline bool url::is_valid() const noexcept {
1320 return !!(flags_ & VALID_FLAG);
1321}
1322
1323inline std::string_view url::get_part_view(PartType t) const UPA_LIFETIMEBOUND {
1324 if (t == SCHEME)
1325 return { norm_url_.data(), part_end_[SCHEME] };
1326 // begin & end offsets
1327 const std::size_t b = part_end_[t - 1] + detail::kPartStart[t];
1328 const std::size_t e = part_end_[t];
1329 return { norm_url_.data() + b, e > b ? e - b : 0 };
1330}
1331
1332inline bool url::is_empty(const PartType t) const {
1333 if (t == SCHEME)
1334 return part_end_[SCHEME] == 0;
1335 // begin & end offsets
1336 const std::size_t b = part_end_[t - 1] + detail::kPartStart[t];
1337 const std::size_t e = part_end_[t];
1338 return b >= e;
1339}
1340
1341inline bool url::is_null(const PartType t) const noexcept {
1342 return !(flags_ & (1u << t));
1343}
1344
1345inline bool url::is_special_scheme() const noexcept {
1346 return scheme_inf_ && scheme_inf_->is_special;
1347}
1348
1349inline bool url::is_file_scheme() const noexcept {
1350 return scheme_inf_ && scheme_inf_->is_file;
1351}
1352
1353inline bool url::is_http_scheme() const noexcept {
1354 return scheme_inf_ && scheme_inf_->is_http;
1355}
1356
1357inline bool url::has_credentials() const {
1358 return !is_empty(USERNAME) || !is_empty(PASSWORD);
1359}
1360
1361// set scheme
1362
1363inline void url::set_scheme_str(std::string_view str) {
1364 norm_url_.clear(); // clear all
1365 part_end_[SCHEME] = str.length();
1366 norm_url_.append(str);
1367 norm_url_ += ':';
1368}
1369
1370inline void url::set_scheme(const url& src) {
1371 set_scheme_str(src.get_part_view(SCHEME));
1372 scheme_inf_ = src.scheme_inf_;
1373}
1374
1375inline void url::set_scheme(std::string_view str) {
1376 set_scheme_str(str);
1377 scheme_inf_ = detail::get_scheme_info(str);
1378}
1379
1380inline void url::set_scheme(std::size_t scheme_length) {
1381 part_end_[SCHEME] = scheme_length;
1382 scheme_inf_ = detail::get_scheme_info(get_part_view(SCHEME));
1383}
1384
1385// flags
1386
1387inline void url::set_flag(const UrlFlag flag) noexcept {
1388 flags_ |= flag;
1389}
1390
1391inline bool url::has_opaque_path() const noexcept {
1392 return !!(flags_ & OPAQUE_PATH_FLAG);
1393}
1394
1395inline void url::set_has_opaque_path() noexcept {
1396 set_flag(OPAQUE_PATH_FLAG);
1397}
1398
1399inline void url::set_host_type(const HostType ht) noexcept {
1400 flags_ = (flags_ & ~HOST_TYPE_MASK) | HOST_FLAG | (static_cast<unsigned int>(ht) << HOST_TYPE_SHIFT);
1401}
1402
1403inline bool url::canHaveUsernamePasswordPort() const {
1404 return is_valid() && !(is_empty(url::HOST) || is_file_scheme());
1405}
1406
1407// Private parsing constructor
1408
1409template <class T, enable_if_str_arg_t<T>>
1410inline url::url(const T& str_url, const url* base, const char* what_arg) {
1411 const auto inp = make_str_arg(str_url);
1412 const auto res = do_parse(inp.begin(), inp.end(), base);
1413 if (res != validation_errc::ok)
1414 throw url_error(res, what_arg);
1415}
1416
1417// Operations
1418
1419inline void url::clear() {
1420 norm_url_.clear();
1421 part_end_.fill(0);
1422 scheme_inf_ = nullptr;
1423 flags_ = INITIAL_FLAGS;
1424 path_segment_count_ = 0;
1425 clear_search_params();
1426}
1427
1428inline void url::swap(url& other) noexcept {
1429 url tmp{ std::move(*this) };
1430 *this = std::move(other);
1431 other = std::move(tmp);
1432}
1433
1434// Parser
1435
1436// Implements "basic URL parser" https://url.spec.whatwg.org/#concept-basic-url-parser
1437// without encoding, url and state override parameters. It resets this url object to
1438// an empty value and then parses the input and modifies this url object.
1439// Returns validation_errc::ok on success, or an error value on parsing failure.
1440template <typename CharT>
1441inline validation_errc url::do_parse(const CharT* first, const CharT* last, const url* base) {
1442 const validation_errc res = [&]() {
1443 detail::url_serializer urls(*this);
1444
1445 // reset URL
1446 urls.new_url();
1447
1448 // is base URL valid?
1449 if (base && !base->is_valid())
1451
1452 // remove any leading and trailing C0 control or space:
1453 detail::do_trim(first, last);
1454 //TODO-WARN: validation error if trimmed
1455
1456 return detail::url_parser::url_parse(urls, first, last, base, detail::url_parser::not_set_state);
1457 }();
1458 if (res == validation_errc::ok) {
1459 set_flag(VALID_FLAG);
1460 parse_search_params();
1461 }
1462 return res;
1463}
1464
1465template <class T, enable_if_str_arg_t<T>>
1466validation_errc url::for_can_parse(const T& str_url, const url* base) {
1467 const auto inp = make_str_arg(str_url);
1468 const auto* first = inp.begin();
1469 const auto* last = inp.end();
1470 const validation_errc res = [&]() {
1471 detail::url_serializer urls(*this, false);
1472
1473 // reset URL
1474 urls.new_url();
1475
1476 // is base URL valid?
1477 if (base && !base->is_valid())
1479
1480 // remove any leading and trailing C0 control or space:
1481 detail::do_trim(first, last);
1482 //TODO-WARN: validation error if trimmed
1483
1484 return detail::url_parser::url_parse(urls, first, last, base, detail::url_parser::not_set_state);
1485 }();
1486 if (res == validation_errc::ok)
1487 set_flag(VALID_FLAG);
1488 return res;
1489}
1490
1491// Setters
1492
1493template <class StrT, enable_if_str_arg_t<StrT>>
1494inline bool url::href(const StrT& str) {
1495 url u; // parsedURL
1496
1497 const auto inp = make_str_arg(str);
1498 if (u.do_parse(inp.begin(), inp.end(), nullptr) == validation_errc::ok) {
1499 safe_assign(std::move(u));
1500 return true;
1501 }
1502 return false;
1503}
1504
1505template <class StrT, enable_if_str_arg_t<StrT>>
1506inline bool url::protocol(const StrT& str) {
1507 if (is_valid()) {
1508 detail::url_setter urls(*this);
1509
1510 const auto inp = make_str_arg(str);
1511 return detail::url_parser::url_parse(urls, inp.begin(), inp.end(), nullptr, detail::url_parser::scheme_start_state) == validation_errc::ok;
1512 }
1513 return false;
1514}
1515
1516template <class StrT, enable_if_str_arg_t<StrT>>
1517inline bool url::username(const StrT& str) {
1518 if (canHaveUsernamePasswordPort()) {
1519 detail::url_setter urls(*this);
1520
1521 const auto inp = make_str_arg(str);
1522
1523 std::string& str_username = urls.start_part(url::USERNAME);
1524 // UTF-8 percent encode it using the userinfo encode set
1525 detail::append_utf8_percent_encoded(inp.begin(), inp.end(), userinfo_no_encode_set, str_username);
1526 urls.save_part();
1527 return true;
1528 }
1529 return false;
1530}
1531
1532template <class StrT, enable_if_str_arg_t<StrT>>
1533inline bool url::password(const StrT& str) {
1534 if (canHaveUsernamePasswordPort()) {
1535 detail::url_setter urls(*this);
1536
1537 const auto inp = make_str_arg(str);
1538
1539 std::string& str_password = urls.start_part(url::PASSWORD);
1540 // UTF-8 percent encode it using the userinfo encode set
1541 detail::append_utf8_percent_encoded(inp.begin(), inp.end(), userinfo_no_encode_set, str_password);
1542 urls.save_part();
1543 return true;
1544 }
1545 return false;
1546}
1547
1548template <class StrT, enable_if_str_arg_t<StrT>>
1549inline bool url::host(const StrT& str) {
1550 if (!has_opaque_path() && is_valid()) {
1551 detail::url_setter urls(*this);
1552
1553 const auto inp = make_str_arg(str);
1554 return detail::url_parser::url_parse(urls, inp.begin(), inp.end(), nullptr, detail::url_parser::host_state) == validation_errc::ok;
1555 }
1556 return false;
1557}
1558
1559template <class StrT, enable_if_str_arg_t<StrT>>
1560inline bool url::hostname(const StrT& str) {
1561 if (!has_opaque_path() && is_valid()) {
1562 detail::url_setter urls(*this);
1563
1564 const auto inp = make_str_arg(str);
1565 return detail::url_parser::url_parse(urls, inp.begin(), inp.end(), nullptr, detail::url_parser::hostname_state) == validation_errc::ok;
1566 }
1567 return false;
1568}
1569
1570template <class StrT, enable_if_str_arg_t<StrT>>
1571inline bool url::port(const StrT& str) {
1572 if (canHaveUsernamePasswordPort()) {
1573 detail::url_setter urls(*this);
1574
1575 const auto inp = make_str_arg(str);
1576 const auto* first = inp.begin();
1577 const auto* last = inp.end();
1578
1579 if (first == last) {
1580 urls.clear_part(url::PORT);
1581 return true;
1582 }
1583 return detail::url_parser::url_parse(urls, first, last, nullptr, detail::url_parser::port_state) == validation_errc::ok;
1584 }
1585 return false;
1586}
1587
1588template <class StrT, enable_if_str_arg_t<StrT>>
1589inline bool url::pathname(const StrT& str) {
1590 if (!has_opaque_path() && is_valid()) {
1591 detail::url_setter urls(*this);
1592
1593 const auto inp = make_str_arg(str);
1594 return detail::url_parser::url_parse(urls, inp.begin(), inp.end(), nullptr, detail::url_parser::path_start_state) == validation_errc::ok;
1595 }
1596 return false;
1597}
1598
1599template <class StrT, enable_if_str_arg_t<StrT>>
1600inline bool url::search(const StrT& str) {
1601 bool res = false;
1602 if (is_valid()) {
1603 {
1604 detail::url_setter urls(*this);
1605
1606 const auto inp = make_str_arg(str);
1607 const auto* first = inp.begin();
1608 const auto* last = inp.end();
1609
1610 if (first == last) {
1611 urls.clear_part(url::QUERY);
1612 // empty context object's query object's list
1613 clear_search_params();
1614 return true;
1615 }
1616 if (*first == '?') ++first;
1617 res = detail::url_parser::url_parse(urls, first, last, nullptr, detail::url_parser::query_state) == validation_errc::ok;
1618 }
1619 // set context object's query object's list to the result of parsing input
1620 parse_search_params();
1621 }
1622 return res;
1623}
1624
1625template <class StrT, enable_if_str_arg_t<StrT>>
1626inline bool url::hash(const StrT& str) {
1627 if (is_valid()) {
1628 detail::url_setter urls(*this);
1629
1630 const auto inp = make_str_arg(str);
1631 const auto* first = inp.begin();
1632 const auto* last = inp.end();
1633
1634 if (first == last) {
1635 urls.clear_part(url::FRAGMENT);
1636 return true;
1637 }
1638 if (*first == '#') ++first;
1639 return detail::url_parser::url_parse(urls, first, last, nullptr, detail::url_parser::fragment_state) == validation_errc::ok;
1640 }
1641 return false;
1642}
1643
1644
1645namespace detail {
1646
1647// Implements "basic URL parser" https://url.spec.whatwg.org/#concept-basic-url-parser
1648// without 1 step. It modifies the URL stored in the urls object.
1649// Returns validation_errc::ok on success, or an error value on parsing failure.
1650template <typename CharT>
1651inline validation_errc url_parser::url_parse(url_serializer& urls, const CharT* first, const CharT* last, const url* base, State state_override)
1652{
1653 using UCharT = std::make_unsigned_t<CharT>;
1654
1655 // remove all ASCII tab or newline from URL
1656 simple_buffer<CharT> buff_no_ws;
1657 detail::do_remove_whitespace(first, last, buff_no_ws);
1658 //TODO-WARN: validation error if removed
1659
1660 if (urls.need_save()) {
1661 // reserve size (TODO: But what if `base` is used?)
1662 const auto length = std::distance(first, last);
1663 urls.reserve(length + 32);
1664 }
1665
1666#ifdef UPA_URL_USE_ENCODING
1667 const char* encoding = "UTF-8";
1668 // TODO: If encoding override is given, set encoding to the result of getting an output encoding from encoding override.
1669#endif
1670
1671 auto pointer = first;
1672 State state = state_override ? state_override : scheme_start_state;
1673
1674 // has scheme?
1675 if (state == scheme_start_state) {
1676 if (pointer != last && detail::is_first_scheme_char(*pointer)) {
1677 state = scheme_state; // this appends first char to buffer
1678 } else if (!state_override) {
1679 state = no_scheme_state;
1680 } else {
1681 // 3. Otherwise, return failure.
1683 }
1684 }
1685
1686 if (state == scheme_state) {
1687 // Deviation from URL stdandart's [ 2. ... if c is ":", run ... ] to
1688 // [ 2. ... if c is ":", or EOF and state override is given, run ... ]
1689 // This lets protocol setter to pass input without adding ':' to the end.
1690 // Similiar deviation exists in nodejs, see:
1691 // https://github.com/nodejs/node/pull/11917#pullrequestreview-28061847
1692
1693 // first scheme char has been checked in the scheme_start_state, so skip it
1694 const auto end_of_scheme = std::find_if_not(pointer + 1, last, detail::is_scheme_char<CharT>);
1695 const bool is_scheme = end_of_scheme != last
1696 ? *end_of_scheme == ':'
1697 : state_override != not_set_state;
1698
1699 if (is_scheme) {
1700 // start of scheme
1701 std::string& str_scheme = urls.start_scheme();
1702 // Append scheme chars: it is safe to set the 0x20 bit on all code points -
1703 // it lowercases ASCII alphas, while other code points allowed in a scheme
1704 // (0 - 9, +, -, .) already have this bit set.
1705 for (auto it = pointer; it != end_of_scheme; ++it)
1706 str_scheme.push_back(static_cast<char>(*it | 0x20));
1707
1708 if (state_override) {
1709 const auto* scheme_inf = detail::get_scheme_info(str_scheme);
1710 const bool is_special_old = urls.is_special_scheme();
1711 const bool is_special_new = scheme_inf && scheme_inf->is_special;
1712 if (is_special_old != is_special_new)
1714 // new URL("http://u:p@host:88/).protocol("file:");
1715 if (scheme_inf && scheme_inf->is_file && (urls.has_credentials() || !urls.is_null(url::PORT)))
1717 // new URL("file:///path).protocol("http:");
1718 if (urls.is_file_scheme() && urls.is_empty(url::HOST))
1720 // OR ursl.is_empty(url::HOST) && scheme_inf->no_empty_host
1721
1722 // set url's scheme
1723 urls.save_scheme();
1724
1725 // https://github.com/whatwg/url/pull/328
1726 // optimization: compare ports if scheme has the default port
1727 if (scheme_inf && scheme_inf->default_port >= 0 &&
1728 urls.port_int() == scheme_inf->default_port) {
1729 // set url's port to null
1730 urls.clear_part(url::PORT);
1731 }
1732
1733 // if state override is given, then return
1734 return validation_errc::ok;
1735 }
1736 urls.save_scheme();
1737
1738 pointer = end_of_scheme + 1; // skip ':'
1739 if (urls.is_file_scheme()) {
1740 // TODO-WARN: if remaining does not start with "//", validation error.
1741 state = file_state;
1742 } else {
1743 if (urls.is_special_scheme()) {
1744 if (base && urls.get_part_view(url::SCHEME) == base->get_part_view(url::SCHEME)) {
1745 assert(base->is_special_scheme()); // and therefore does not have an opaque path
1746 state = special_relative_or_authority_state;
1747 } else {
1748 state = special_authority_slashes_state;
1749 }
1750 } else if (pointer < last && *pointer == '/') {
1751 state = path_or_authority_state;
1752 ++pointer;
1753 } else {
1754 // set url’s path to the empty string (so path becomes opaque,
1755 // see: https://url.spec.whatwg.org/#url-opaque-path)
1756 urls.set_has_opaque_path();
1757 // To complete the set url's path to the empty string, following functions must be called:
1758 // urls.start_path_string();
1759 // urls.save_path_string();
1760 // but the same functions will be called in the opaque_path_state, so skip them here.
1761 state = opaque_path_state;
1762 }
1763 }
1764 } else if (!state_override) {
1765 state = no_scheme_state;
1766 } else {
1767 // 4. Otherwise, return failure.
1769 }
1770 }
1771
1772 if (state == no_scheme_state) {
1773 if (base) {
1774 if (base->has_opaque_path()) {
1775 if (pointer < last && *pointer == '#') {
1776 urls.set_scheme(*base);
1777 urls.append_parts(*base, url::PATH, url::QUERY);
1778 //TODO: url's fragment to the empty string
1779 state = fragment_state;
1780 ++pointer;
1781 } else {
1782 // 1. If ..., or base has an opaque path and c is not U+0023 (#),
1783 // missing-scheme-non-relative-URL validation error, return failure.
1785 }
1786 } else {
1787 state = base->is_file_scheme() ? file_state : relative_state;
1788 }
1789 } else {
1790 // 1. If base is null, ..., missing-scheme-non-relative-URL
1791 // validation error, return failure
1793 }
1794 }
1795
1796 if (state == special_relative_or_authority_state) {
1797 if (last - pointer > 1 && pointer[0] == '/' && pointer[1] == '/') {
1798 state = special_authority_ignore_slashes_state;
1799 pointer += 2; // skip "//"
1800 } else {
1801 //TODO-WARN: validation error
1802 state = relative_state;
1803 }
1804 }
1805
1806 if (state == path_or_authority_state) {
1807 if (pointer < last && pointer[0] == '/') {
1808 state = authority_state;
1809 ++pointer; // skip "/"
1810 } else {
1811 state = path_state;
1812 }
1813 }
1814
1815 if (state == relative_state) {
1816 // std::assert(base != nullptr);
1817 urls.set_scheme(*base);
1818 if (pointer == last) {
1819 // EOF code point
1820 // Set url's username to base's username, url's password to base's password, url's host to base's host,
1821 // url's port to base's port, url's path to base's path, and url's query to base's query
1822 urls.append_parts(*base, url::USERNAME, url::QUERY);
1823 return validation_errc::ok; // EOF
1824 }
1825 const CharT ch = *pointer++;
1826 switch (ch) {
1827 case '/':
1828 state = relative_slash_state;
1829 break;
1830 case '?':
1831 // Set url's username to base's username, url's password to base's password, url's host to base's host,
1832 // url's port to base's port, url's path to base's path, url's query to the empty string, and state to query state.
1833 urls.append_parts(*base, url::USERNAME, url::PATH);
1834 state = query_state; // sets query to the empty string
1835 break;
1836 case '#':
1837 // Set url's username to base's username, url's password to base's password, url's host to base's host,
1838 // url's port to base's port, url's path to base's path, url's query to base's query, url's fragment to the empty string
1839 urls.append_parts(*base, url::USERNAME, url::QUERY);
1840 state = fragment_state; // sets fragment to the empty string
1841 break;
1842 case '\\':
1843 if (urls.is_special_scheme()) {
1844 //TODO-WARN: validation error
1845 state = relative_slash_state;
1846 break;
1847 }
1848 [[fallthrough]];
1849 default:
1850 // Set url's username to base's username, url's password to base's password, url's host to base's host,
1851 // url's port to base's port, url's path to base's path, and then remove url's path's last entry, if any
1852 urls.append_parts(*base, url::USERNAME, url::PATH, &url::get_path_rem_last);
1853 state = path_state;
1854 --pointer;
1855 }
1856 }
1857
1858 if (state == relative_slash_state) {
1859 // EOF ==> 0 ==> default:
1860 switch (pointer != last ? *pointer : 0) {
1861 case '/':
1862 if (urls.is_special_scheme())
1863 state = special_authority_ignore_slashes_state;
1864 else
1865 state = authority_state;
1866 ++pointer;
1867 break;
1868 case '\\':
1869 if (urls.is_special_scheme()) {
1870 // TODO-WARN: validation error
1871 state = special_authority_ignore_slashes_state;
1872 ++pointer;
1873 break;
1874 }
1875 [[fallthrough]];
1876 default:
1877 // set url's username to base's username, url's password to base's password, url's host to base's host,
1878 // url's port to base's port
1879 urls.append_parts(*base, url::USERNAME, url::PORT);
1880 state = path_state;
1881 }
1882 }
1883
1884 if (state == special_authority_slashes_state) {
1885 if (last - pointer > 1 && pointer[0] == '/' && pointer[1] == '/') {
1886 state = special_authority_ignore_slashes_state;
1887 pointer += 2; // skip "//"
1888 } else {
1889 //TODO-WARN: validation error
1890 state = special_authority_ignore_slashes_state;
1891 }
1892 }
1893
1894 if (state == special_authority_ignore_slashes_state) {
1895 auto it = pointer;
1896 while (it < last && detail::is_slash(*it)) ++it;
1897 // if (it != pointer) // TODO-WARN: validation error
1898 pointer = it;
1899 state = authority_state;
1900 }
1901
1902 // TODO?: credentials serialization do after host parsing, because
1903 // if host is null, then no credentials serialization
1904 if (state == authority_state) {
1905 // TODO: saugoti end_of_authority ir naudoti kituose state
1906 const auto end_of_authority = urls.is_special_scheme() ?
1907 std::find_if(pointer, last, detail::is_special_authority_end_char<CharT>) :
1908 std::find_if(pointer, last, detail::is_authority_end_char<CharT>);
1909
1910 const auto it_eta = detail::find_last(pointer, end_of_authority, static_cast<CharT>('@'));
1911 if (it_eta != end_of_authority) {
1912 if (std::distance(it_eta, end_of_authority) == 1) {
1913 // 2.1. If atSignSeen is true and buffer is the empty string, host-missing
1914 // validation error, return failure.
1915 // Example: "http://u:p@/"
1917 }
1918 //TODO-WARN: validation error
1919 if (urls.need_save()) {
1920 const auto it_colon = std::find(pointer, it_eta, ':');
1921 // url includes credentials?
1922 const bool not_empty_password = std::distance(it_colon, it_eta) > 1;
1923 if (not_empty_password || std::distance(pointer, it_colon) > 0 /*not empty username*/) {
1924 // username
1925 std::string& str_username = urls.start_part(url::USERNAME);
1926 detail::append_utf8_percent_encoded(pointer, it_colon, userinfo_no_encode_set, str_username); // UTF-8 percent encode, @ -> %40
1927 urls.save_part();
1928 // password
1929 if (not_empty_password) {
1930 std::string& str_password = urls.start_part(url::PASSWORD);
1931 detail::append_utf8_percent_encoded(it_colon + 1, it_eta, userinfo_no_encode_set, str_password); // UTF-8 percent encode, @ -> %40
1932 urls.save_part();
1933 }
1934 }
1935 }
1936 // after '@'
1937 pointer = it_eta + 1;
1938 }
1939 state = host_state;
1940 }
1941
1942 if (state == host_state || state == hostname_state) {
1943 if (state_override && urls.is_file_scheme()) {
1944 state = file_host_state;
1945 } else {
1946 const auto end_of_authority = urls.is_special_scheme() ?
1947 std::find_if(pointer, last, detail::is_special_authority_end_char<CharT>) :
1948 std::find_if(pointer, last, detail::is_authority_end_char<CharT>);
1949
1950 bool in_square_brackets = false; // [] flag
1951 bool is_port = false;
1952 auto it_host_end = pointer;
1953 for (; it_host_end < end_of_authority; ++it_host_end) {
1954 const CharT ch = *it_host_end;
1955 if (ch == ':') {
1956 if (!in_square_brackets) {
1957 is_port = true;
1958 break;
1959 }
1960 } else if (ch == '[') {
1961 in_square_brackets = true;
1962 } else if (ch == ']') {
1963 in_square_brackets = false;
1964 }
1965 }
1966
1967 // if buffer is the empty string
1968 if (pointer == it_host_end) {
1969 // make sure that if port is present or scheme is special, host is non-empty
1970 if (is_port || urls.is_special_scheme()) {
1971 // host-missing validation error, return failure
1973 }
1974 // 3.2. if state override is given, buffer is the empty string, and either
1975 // url includes credentials or url’s port is non-null, then return failure.
1976 if (state_override && (urls.has_credentials() || !urls.is_null(url::PORT))) {
1977 return validation_errc::ignored; // failure: can not make host empty
1978 }
1979 }
1980
1981 // 2.2. If state override is given and state override is hostname state, then
1982 // return failure.
1983 if (is_port && state_override == hostname_state)
1984 return validation_errc::ignored; // failure: host with port not accepted
1985
1986 // parse and set host:
1987 const auto res = parse_host(urls, pointer, it_host_end);
1988 // 2.4, 3.4. If host is failure, then return failure.
1989 if (res != validation_errc::ok)
1990 return res;
1991
1992 if (is_port) {
1993 pointer = it_host_end + 1; // skip ':'
1994 state = port_state;
1995 } else {
1996 pointer = it_host_end;
1997 state = path_start_state;
1998 if (state_override)
1999 return validation_errc::ok;
2000 }
2001 }
2002 }
2003
2004 if (state == port_state) {
2005 const auto end_of_digits = std::find_if_not(pointer, last, detail::is_ascii_digit<CharT>);
2006
2007 const bool is_end_of_authority =
2008 end_of_digits == last || // EOF
2009 detail::is_authority_end_char(end_of_digits[0]) ||
2010 (end_of_digits[0] == '\\' && urls.is_special_scheme());
2011
2012 if (is_end_of_authority || state_override) {
2013 if (pointer < end_of_digits) {
2014 // url string contains port
2015 // skip the leading zeros except the last
2016 pointer = std::find_if(pointer, end_of_digits - 1, [](CharT c) { return c != '0'; });
2017 // check port <= 65535 (0xFFFF)
2018 if (std::distance(pointer, end_of_digits) > 5)
2020 // port length <= 5
2021 int port = 0;
2022 for (auto it = pointer; it < end_of_digits; ++it)
2023 port = port * 10 + (*it - '0');
2024 // 2.1.2. If port is greater than 2^16 − 1, port-out-of-range
2025 // validation error, return failure
2026 if (port > 0xFFFF)
2028 if (urls.need_save()) {
2029 // set port if not default
2030 if (urls.scheme_inf() == nullptr || urls.scheme_inf()->default_port != port) {
2031 util::append(urls.start_part(url::PORT), str_arg<CharT>{ pointer, end_of_digits });
2032 urls.save_part();
2033 urls.set_flag(url::PORT_FLAG);
2034 } else {
2035 // (2-1-3) Set url's port to null
2036 urls.clear_part(url::PORT);
2037 }
2038 }
2039 // 2.2. If state override is given, then return
2040 if (state_override)
2041 return validation_errc::ok;
2042 } else if (state_override)
2043 return validation_errc::ignored; // failure
2044 state = path_start_state;
2045 pointer = end_of_digits;
2046 } else {
2047 // 3. Otherwise, port-invalid validation error, return failure (contains non-digit)
2049 }
2050 }
2051
2052 if (state == file_state) {
2053 if (!urls.is_file_scheme())
2054 urls.set_scheme(std::string_view{ "file", 4 });
2055 // ensure file URL's host is not null
2056 urls.set_empty_host();
2057 // EOF ==> 0 ==> default:
2058 switch (pointer != last ? *pointer : 0) {
2059 case '\\':
2060 // TODO-WARN: validation error
2061 case '/':
2062 state = file_slash_state;
2063 ++pointer;
2064 break;
2065
2066 default:
2067 if (base && base->is_file_scheme()) {
2068 if (pointer == last) {
2069 // EOF code point
2070 // Set url's host to base's host, url's path to base's path, and url's query to base's query
2071 urls.append_parts(*base, url::HOST, url::QUERY);
2072 return validation_errc::ok; // EOF
2073 }
2074 switch (*pointer) {
2075 case '?':
2076 // Set url's host to base's host, url's path to base's path, url's query to the empty string
2077 urls.append_parts(*base, url::HOST, url::PATH);
2078 state = query_state; // sets query to the empty string
2079 ++pointer;
2080 break;
2081 case '#':
2082 // Set url's host to base's host, url's path to base's path, url's query to base's query, url's fragment to the empty string
2083 urls.append_parts(*base, url::HOST, url::QUERY);
2084 state = fragment_state; // sets fragment to the empty string
2085 ++pointer;
2086 break;
2087 default:
2088 if (!detail::starts_with_windows_drive(pointer, last)) {
2089 // set url's host to base's host, url's path to base's path, and then shorten url's path
2090 urls.append_parts(*base, url::HOST, url::PATH, &url::get_shorten_path);
2091 // Note: This is a (platform-independent) Windows drive letter quirk.
2092 } else {
2093 // TODO-WARN: validation error
2094 // set url's host to base's host
2095 urls.append_parts(*base, url::HOST, url::HOST);
2096 }
2097 state = path_state;
2098 }
2099 } else {
2100 state = path_state;
2101 }
2102 }
2103 }
2104
2105 if (state == file_slash_state) {
2106 // EOF ==> 0 ==> default:
2107 switch (pointer != last ? *pointer : 0) {
2108 case '\\':
2109 // TODO-WARN: validation error
2110 case '/':
2111 state = file_host_state;
2112 ++pointer;
2113 break;
2114
2115 default:
2116 if (base && base->is_file_scheme() && urls.need_save()) {
2117 // It is important to first set host, then path, otherwise serializer
2118 // will fail.
2119
2120 // set url's host to base's host
2121 urls.append_parts(*base, url::HOST, url::HOST);
2122 // path
2123 if (!detail::starts_with_windows_drive(pointer, last)) {
2124 const std::string_view base_path = base->get_path_first_string(2);
2125 // if base's path[0] is a normalized Windows drive letter
2126 if (base_path.length() == 2 &&
2127 detail::is_normalized_windows_drive(base_path[0], base_path[1])) {
2128 // append base's path[0] to url's path
2129 std::string& str_path = urls.start_path_segment();
2130 str_path.append(base_path.data(), 2); // "C:"
2131 urls.save_path_segment();
2132 // Note: This is a (platform - independent) Windows drive letter quirk.
2133 }
2134 }
2135 }
2136 state = path_state;
2137 }
2138 }
2139
2140 if (state == file_host_state) {
2141 const auto end_of_authority = std::find_if(pointer, last, detail::is_special_authority_end_char<CharT>);
2142
2143 if (pointer == end_of_authority) {
2144 // buffer is the empty string
2145 // set empty host
2146 urls.set_empty_host();
2147 // if state override is given, then return
2148 if (state_override)
2149 return validation_errc::ok;
2150 state = path_start_state;
2151 } else if (!state_override && end_of_authority - pointer == 2 &&
2152 detail::is_windows_drive(pointer[0], pointer[1])) {
2153 // buffer is a Windows drive letter
2154 // TODO-WARN: validation error
2155 state = path_state;
2156 // Note: This is a (platform - independent) Windows drive letter quirk.
2157 // buffer is not reset here and instead used in the path state.
2158 // TODO: buffer is not reset here and instead used in the path state
2159 } else {
2160 // parse and set host:
2161 const auto res = parse_host(urls, pointer, end_of_authority);
2162 if (res != validation_errc::ok || !urls.need_save())
2163 return res; // TODO-ERR: failure
2164 // if host is "localhost", then set host to the empty string
2165 if (urls.get_part_view(url::HOST) == std::string_view{ "localhost", 9 }) {
2166 // set empty host
2167 urls.empty_host();
2168 }
2169 // if state override is given, then return
2170 if (state_override)
2171 return validation_errc::ok;
2172 pointer = end_of_authority;
2173 state = path_start_state;
2174 }
2175 }
2176
2177 if (!urls.need_save())
2178 return validation_errc::ok;
2179
2180 if (state == path_start_state) {
2181 if (urls.is_special_scheme()) {
2182 if (pointer != last) {
2183 switch (*pointer) {
2184 case '\\':
2185 // TODO-WARN: validation error
2186 case '/':
2187 ++pointer;
2188 }
2189 }
2190 if (pointer == last) {
2191 // Optimization:
2192 // "ws://h", "ws://h\" and "ws://h/" parses to "ws://h/"
2193 // See: https://github.com/whatwg/url/pull/847
2194 urls.append_empty_path_segment();
2195 urls.commit_path();
2196 return validation_errc::ok;
2197 }
2198 state = path_state;
2199 } else if (pointer != last) {
2200 if (!state_override) {
2201 switch (pointer[0]) {
2202 case '?':
2203 // TODO: set url's query to the empty string
2204 state = query_state;
2205 ++pointer;
2206 break;
2207 case '#':
2208 // TODO: set url's fragment to the empty string
2209 state = fragment_state;
2210 ++pointer;
2211 break;
2212 case '/':
2213 ++pointer;
2214 [[fallthrough]];
2215 default:
2216 state = path_state;
2217 break;
2218 }
2219 } else {
2220 if (pointer[0] == '/') ++pointer;
2221 state = path_state;
2222 }
2223 } else {
2224 // EOF
2225 if (state_override && urls.is_null(url::HOST))
2226 urls.append_empty_path_segment();
2227 // otherwise path is empty
2228 urls.commit_path();
2229 return validation_errc::ok;
2230 }
2231 }
2232
2233 if (state == path_state) {
2234 const auto end_of_path = state_override ? last :
2235 std::find_if(pointer, last, [](CharT c) { return c == '?' || c == '#'; });
2236
2237 parse_path(urls, pointer, end_of_path);
2238 pointer = end_of_path;
2239
2240 // the end of path parse
2241 urls.commit_path();
2242
2243 if (pointer == last)
2244 return validation_errc::ok; // EOF
2245
2246 const CharT ch = *pointer++;
2247 if (ch == '?') {
2248 // TODO: set url's query to the empty string
2249 state = query_state;
2250 } else {
2251 // ch == '#'
2252 // TODO: set url's fragment to the empty string
2253 state = fragment_state;
2254 }
2255 }
2256
2257 if (state == opaque_path_state) {
2258 const auto end_of_path =
2259 std::find_if(pointer, last, [](CharT c) { return c == '?' || c == '#'; });
2260
2261 // UTF-8 percent encode using the C0 control percent-encode set,
2262 // and append the result to url's path string
2263 std::string& str_path = urls.start_path_string();
2264 do_opaque_path(pointer, end_of_path, str_path);
2265 urls.save_path_string();
2266 pointer = end_of_path;
2267
2268 if (pointer == last)
2269 return validation_errc::ok; // EOF
2270
2271 const CharT ch = *pointer++;
2272 if (ch == '?') {
2273 // TODO: set url's query to the empty string
2274 state = query_state;
2275 } else {
2276 // ch == '#'
2277 // TODO: set url's fragment to the empty string
2278 state = fragment_state;
2279 }
2280 }
2281
2282 if (state == query_state) {
2283 const auto end_of_query = state_override ? last : std::find(pointer, last, '#');
2284
2285 // TODO-WARN:
2286 //for (auto it = pointer; it < end_of_query; ++it) {
2287 // UCharT c = static_cast<UCharT>(*it);
2288 // // 1. If c is not a URL code point and not "%", validation error.
2289 // // 2. If c is "%" and remaining does not start with two ASCII hex digits, validation error.
2290 //}
2291
2292#ifdef UPA_URL_USE_ENCODING
2293 // scheme_inf_ == nullptr, if unknown scheme
2294 if (!urls.scheme_inf() || !urls.scheme_inf()->is_special || urls.scheme_inf()->is_ws)
2295 encoding = "UTF-8";
2296#endif
2297
2298 // Let query_cpset be the special-query percent-encode set if url is special;
2299 // otherwise the query percent-encode set.
2300 const auto& query_cpset = urls.is_special_scheme()
2303
2304 // Percent-encode after encoding, with encoding, buffer, and query_cpset, and append
2305 // the result to url’s query.
2306 // TODO: now supports UTF-8 encoding only, maybe later add other encodings
2307 std::string& str_query = urls.start_part(url::QUERY);
2308 // detail::append_utf8_percent_encoded(pointer, end_of_query, query_cpset, str_query);
2309 while (pointer != end_of_query) {
2310 // UTF-8 percent encode c using the fragment percent-encode set
2311 // and ignore '\0'
2312 const auto uch = static_cast<UCharT>(*pointer);
2313 if (uch >= 0x80) {
2314 // invalid utf-8/16/32 sequences will be replaced with kUnicodeReplacementCharacter
2315 detail::append_utf8_percent_encoded_char(pointer, end_of_query, str_query);
2316 } else {
2317 // Just append the 7-bit character, possibly percent encoding it
2318 const auto uc = static_cast<unsigned char>(uch);
2319 if (!detail::is_char_in_set(uc, query_cpset))
2320 detail::append_percent_encoded_byte(uc, str_query);
2321 else
2322 str_query.push_back(uc);
2323 ++pointer;
2324 }
2325 // TODO-WARN:
2326 // If c is not a URL code point and not "%", validation error.
2327 // If c is "%" and remaining does not start with two ASCII hex digits, validation error.
2328 // Let bytes be the result of encoding c using encoding ...
2329 }
2330 urls.save_part();
2331 urls.set_flag(url::QUERY_FLAG);
2332
2333 pointer = end_of_query;
2334 if (pointer == last)
2335 return validation_errc::ok; // EOF
2336 // *pointer == '#'
2337 //TODO: set url's fragment to the empty string
2338 state = fragment_state;
2339 ++pointer; // skip '#'
2340 }
2341
2342 if (state == fragment_state) {
2343 // https://url.spec.whatwg.org/#fragment-state
2344 std::string& str_frag = urls.start_part(url::FRAGMENT);
2345 while (pointer < last) {
2346 // UTF-8 percent encode c using the fragment percent-encode set
2347 const auto uch = static_cast<UCharT>(*pointer);
2348 if (uch >= 0x80) {
2349 // invalid utf-8/16/32 sequences will be replaced with kUnicodeReplacementCharacter
2350 detail::append_utf8_percent_encoded_char(pointer, last, str_frag);
2351 } else {
2352 // Just append the 7-bit character, possibly percent encoding it
2353 const auto uc = static_cast<unsigned char>(uch);
2354 if (detail::is_char_in_set(uc, fragment_no_encode_set)) {
2355 str_frag.push_back(uc);
2356 } else {
2357 // other characters are percent encoded
2358 detail::append_percent_encoded_byte(uc, str_frag);
2359 }
2360 ++pointer;
2361 }
2362 // TODO-WARN:
2363 // If c is not a URL code point and not "%", validation error.
2364 // If c is "%" and remaining does not start with two ASCII hex digits, validation error.
2365 }
2366 urls.save_part();
2367 urls.set_flag(url::FRAGMENT_FLAG);
2368 }
2369
2370 return validation_errc::ok;
2371}
2372
2373// internal functions
2374
2375template <typename CharT>
2376inline validation_errc url_parser::parse_host(url_serializer& urls, const CharT* first, const CharT* last) {
2377 return host_parser::parse_host(first, last, !urls.is_special_scheme(), urls);
2378}
2379
2380template <typename CharT>
2381inline void url_parser::parse_path(url_serializer& urls, const CharT* first, const CharT* last) {
2382 // path state; includes:
2383 // 1. [ (/,\‍) - 1, 2, 3, 4 - [ 1 (if first segment), 2 ] ]
2384 // 2. [ 1 ... 4 ]
2385 static constexpr auto escaped_dot = [](const CharT* const pointer) constexpr -> bool {
2386 // "%2e" or "%2E"
2387 return pointer[0] == '%' && pointer[1] == '2' && (pointer[2] | 0x20) == 'e';
2388 };
2389 static constexpr auto double_dot = [](const CharT* const pointer, const std::size_t len) constexpr -> bool {
2390 switch (len) {
2391 case 2: // ".."
2392 return pointer[0] == '.' && pointer[1] == '.';
2393 case 4: // ".%2e" or "%2e."
2394 return (pointer[0] == '.' && escaped_dot(pointer + 1)) ||
2395 (escaped_dot(pointer) && pointer[3] == '.');
2396 case 6: // "%2e%2e"
2397 return escaped_dot(pointer) && escaped_dot(pointer + 3);
2398 default:
2399 return false;
2400 }
2401 };
2402 static constexpr auto single_dot = [](const CharT* const pointer, const std::size_t len) constexpr -> bool {
2403 switch (len) {
2404 case 1: return pointer[0] == '.';
2405 case 3: return escaped_dot(pointer); // "%2e"
2406 default: return false;
2407 }
2408 };
2409
2410 // parse path's segments
2411 auto pointer = first;
2412 while (true) {
2413 const auto end_of_segment = urls.is_special_scheme()
2414 ? std::find_if(pointer, last, detail::is_slash<CharT>)
2415 : std::find(pointer, last, '/');
2416
2417 // end_of_segment >= pointer
2418 const std::size_t len = end_of_segment - pointer;
2419 const bool is_last = end_of_segment == last;
2420 // TODO-WARN: 1. If url is special and c is "\", validation error.
2421
2422 if (double_dot(pointer, len)) {
2423 urls.shorten_path();
2424 if (is_last) urls.append_empty_path_segment();
2425 } else if (single_dot(pointer, len)) {
2426 if (is_last) urls.append_empty_path_segment();
2427 } else {
2428 if (len == 2 &&
2429 urls.is_file_scheme() &&
2430 urls.is_empty_path() &&
2431 detail::is_windows_drive(pointer[0], pointer[1]))
2432 {
2433 // replace the second code point in buffer with ":"
2434 std::string& str_path = urls.start_path_segment();
2435 str_path += static_cast<char>(pointer[0]);
2436 str_path += ':';
2437 urls.save_path_segment();
2438 //Note: This is a (platform-independent) Windows drive letter quirk.
2439 } else {
2440 std::string& str_path = urls.start_path_segment();
2441 do_path_segment(pointer, end_of_segment, str_path);
2442 urls.save_path_segment();
2443 // end of segment
2444 pointer = end_of_segment;
2445 }
2446 }
2447 // next segment
2448 if (is_last) break;
2449 pointer = end_of_segment + 1; // skip '/' or '\'
2450 }
2451}
2452
2453template <typename CharT>
2454inline void url_parser::do_path_segment(const CharT* pointer, const CharT* last, std::string& output) {
2455 using UCharT = std::make_unsigned_t<CharT>;
2456
2457 // TODO-WARN: 2. [ 1 ... 2 ] validation error.
2458 while (pointer < last) {
2459 // UTF-8 percent encode c using the default encode set
2460 const auto uch = static_cast<UCharT>(*pointer);
2461 if (uch >= 0x80) {
2462 // invalid utf-8/16/32 sequences will be replaced with 0xfffd
2463 detail::append_utf8_percent_encoded_char(pointer, last, output);
2464 } else {
2465 // Just append the 7-bit character, possibly percent encoding it
2466 const auto uc = static_cast<unsigned char>(uch);
2467 if (!detail::is_char_in_set(uc, path_no_encode_set))
2468 detail::append_percent_encoded_byte(uc, output);
2469 else
2470 output.push_back(uc);
2471 ++pointer;
2472 }
2473 }
2474}
2475
2476template <typename CharT>
2477inline void url_parser::do_opaque_path(const CharT* pointer, const CharT* last, std::string& output) {
2478 using UCharT = std::make_unsigned_t<CharT>;
2479
2480 // TODO-WARN in the `opaque path state`:
2481 // 3. Otherwise, if c is U+0020 SPACE:
2482 // 1. Invalid-URL-unit validation error.
2483 // 4. Otherwise, if c is not the EOF code point:
2484 // 1. If c is not EOF code point, not a URL code point, and not "%", validation error.
2485 // 2. If c is "%" and remaining does not start with two ASCII hex digits, validation error.
2486
2487 if (pointer != last) {
2488 // If path ends with a space, the space is percent encoded and appended
2489 // to the output at the end of processing.
2490 const bool ends_with_space = *(last - 1) == ' ';
2491 if (ends_with_space)
2492 --last;
2493 while (pointer < last) {
2494 // UTF-8 percent encode c using the C0 control percent-encode set (U+0000 ... U+001F and >U+007E)
2495 const auto uch = static_cast<UCharT>(*pointer);
2496 if (uch >= 0x7f) {
2497 // invalid utf-8/16/32 sequences will be replaced with 0xfffd
2498 detail::append_utf8_percent_encoded_char(pointer, last, output);
2499 } else {
2500 // Just append the 7-bit character, percent encoding C0 control chars
2501 const auto uc = static_cast<unsigned char>(uch);
2502 if (uc <= 0x1f)
2503 detail::append_percent_encoded_byte(uc, output);
2504 else
2505 output.push_back(uc);
2506 ++pointer;
2507 }
2508 }
2509 // %20 - percent encoded space
2510 if (ends_with_space)
2511 output.append("%20");
2512 }
2513}
2514
2515} // namespace detail
2516
2517
2518// path util
2519
2520inline std::string_view url::get_path_first_string(std::size_t len) const UPA_LIFETIMEBOUND {
2521 std::string_view pathv = get_part_view(PATH);
2522 if (pathv.empty() || has_opaque_path())
2523 return pathv;
2524 // skip '/'
2525 pathv.remove_prefix(1);
2526 if (pathv.length() == len || (pathv.length() > len && pathv[len] == '/')) {
2527 return { pathv.data(), len };
2528 }
2529 return {};
2530}
2531
2532// path shortening
2533
2534inline bool url::get_path_rem_last(std::size_t& path_end, std::size_t& path_segment_count) const {
2535 if (path_segment_count_ > 0) {
2536 // Remove path's last item
2537 const char* const first = norm_url_.data() + part_end_[url::PATH-1];
2538 const char* const last = norm_url_.data() + part_end_[url::PATH];
2539 const char* it = detail::find_last(first, last, '/');
2540 if (it == last) it = first; // remove full path if '/' not found
2541 // shorten
2542 path_end = it - norm_url_.data();
2543 path_segment_count = path_segment_count_ - 1;
2544 return true;
2545 }
2546 return false;
2547}
2548
2549// https://url.spec.whatwg.org/#shorten-a-urls-path
2550
2551inline bool url::get_shorten_path(std::size_t& path_end, std::size_t& path_segment_count) const {
2552 assert(!has_opaque_path());
2553 if (path_segment_count_ == 0)
2554 return false;
2555 if (is_file_scheme() && path_segment_count_ == 1) {
2556 const std::string_view path1 = get_path_first_string(2);
2557 if (path1.length() == 2 &&
2558 detail::is_normalized_windows_drive(path1[0], path1[1]))
2559 return false;
2560 }
2561 // Remove path's last item
2562 return get_path_rem_last(path_end, path_segment_count);
2563}
2564
2565
2566namespace detail {
2567
2568// url_serializer class
2569
2570inline void url_serializer::shorten_path() {
2571 assert(last_pt_ <= url::PATH);
2572 if (url_.get_shorten_path(url_.part_end_[url::PATH], url_.path_segment_count_))
2573 url_.norm_url_.resize(url_.part_end_[url::PATH]);
2574}
2575
2576// set scheme
2577
2578inline std::string& url_serializer::start_scheme() {
2579 url_.norm_url_.clear(); // clear all
2580 return url_.norm_url_;
2581}
2582
2583inline void url_serializer::save_scheme() {
2584 set_scheme(url_.norm_url_.length());
2585 url_.norm_url_.push_back(':');
2586}
2587
2588// set url's part
2589
2590inline void url_serializer::fill_parts_offset(url::PartType t1, url::PartType t2, std::size_t offset) {
2591 for (int ind = t1; ind < t2; ++ind)
2592 url_.part_end_[ind] = offset;
2593}
2594
2595inline std::string& url_serializer::start_part(url::PartType new_pt) {
2596 // offsets of empty parts (until new_pt) are also filled
2597 auto fill_start_pt = static_cast<url::PartType>(static_cast<int>(last_pt_)+1);
2598 switch (last_pt_) {
2599 case url::SCHEME:
2600 // if host is non-null
2601 if (new_pt <= url::HOST)
2602 url_.norm_url_.append("//");
2603 break;
2604 case url::USERNAME:
2605 if (new_pt == url::PASSWORD) {
2606 url_.norm_url_ += ':';
2607 break;
2608 } else {
2609 url_.part_end_[url::PASSWORD] = url_.norm_url_.length();
2610 fill_start_pt = url::HOST_START; // (url::PASSWORD + 1)
2611 }
2612 [[fallthrough]];
2613 case url::PASSWORD:
2614 if (new_pt == url::HOST)
2615 url_.norm_url_ += '@';
2616 break;
2617 case url::HOST:
2618 case url::PORT:
2619 break;
2620 case url::PATH:
2621 if (new_pt == url::PATH) // continue on path
2622 return url_.norm_url_;
2623 break;
2624 default: break;
2625 }
2626
2627 fill_parts_offset(fill_start_pt, new_pt, url_.norm_url_.length());
2628
2629 switch (new_pt) {
2630 case url::PORT:
2631 url_.norm_url_ += ':';
2632 break;
2633 case url::QUERY:
2634 url_.norm_url_ += '?';
2635 break;
2636 case url::FRAGMENT:
2637 url_.norm_url_ += '#';
2638 break;
2639 default: break;
2640 }
2641
2642 assert(last_pt_ < new_pt || (last_pt_ == new_pt && is_empty(last_pt_)));
2643 // value to url_.part_end_[new_pt] will be assigned in the save_part()
2644 last_pt_ = new_pt;
2645 return url_.norm_url_;
2646}
2647
2648inline void url_serializer::save_part() {
2649 url_.part_end_[last_pt_] = url_.norm_url_.length();
2650}
2651
2652// The append_empty_path_segment() appends the empty string to url’s path (list);
2653// it is called from these places:
2654// 1) path_start_state -> [5.]
2655// 2) path_state -> [1.2.2. ".." ]
2656// 3) path_state -> [1.3. "." ]
2657inline void url_serializer::append_empty_path_segment() {
2658 start_path_segment();
2659 save_path_segment();
2660}
2661
2662inline std::string& url_serializer::start_path_segment() {
2663 // appends new segment to path: / seg1 / seg2 / ... / segN
2664 std::string& str_path = start_part(url::PATH);
2665 str_path += '/';
2666 return str_path;
2667}
2668
2669inline void url_serializer::save_path_segment() {
2670 save_part();
2671 url_.path_segment_count_++;
2672}
2673
2674inline void url_serializer::commit_path() {
2675 // "/." path prefix
2676 adjust_path_prefix();
2677}
2678
2679inline void url_serializer::adjust_path_prefix() {
2680 // "/." path prefix
2681 // https://url.spec.whatwg.org/#url-serializing (4.1.)
2682 std::string_view new_prefix;
2683 if (is_null(url::HOST) && url_.path_segment_count_ > 1) {
2684 const auto pathname = get_part_view(url::PATH);
2685 if (pathname.length() > 1 && pathname[0] == '/' && pathname[1] == '/')
2686 new_prefix = { "/.", 2 };
2687 }
2688 if (is_empty(url::PATH_PREFIX) != new_prefix.empty())
2689 replace_part(url::PATH_PREFIX, new_prefix.data(), new_prefix.length());
2690}
2691
2692inline std::string& url_serializer::start_path_string() {
2693 return start_part(url::PATH);
2694}
2695
2696inline void url_serializer::save_path_string() {
2697 assert(url_.path_segment_count_ == 0);
2698 save_part();
2699}
2700
2701
2702inline void url_serializer::set_empty_host() {
2703 start_part(url::HOST);
2704 save_part();
2705 set_host_type(HostType::Empty);
2706}
2707
2708inline void url_serializer::empty_host() {
2709 // It is called right after a host parsing
2710 assert(last_pt_ == url::HOST);
2711
2712 const std::size_t host_end = url_.part_end_[url::HOST_START];
2713 url_.part_end_[url::HOST] = host_end;
2714 url_.norm_url_.resize(host_end);
2715
2716 url_.set_host_type(HostType::Empty);
2717}
2718
2719// host_output overrides
2720
2721inline std::string& url_serializer::hostStart() {
2722 return start_part(url::HOST);
2723}
2724
2725inline void url_serializer::hostDone(HostType ht) {
2726 save_part();
2727 set_host_type(ht);
2728
2729 // non-null host
2730 if (!is_empty(url::PATH_PREFIX)) {
2731 // remove '/.' path prefix
2732 replace_part(url::PATH_PREFIX, nullptr, 0);
2733 }
2734}
2735
2736// append parts from other url
2737
2738inline void url_serializer::append_parts(const url& src, url::PartType t1, url::PartType t2, PathOpFn pathOpFn) {
2739 if (!need_save()) return;
2740
2741 // See URL serializing
2742 // https://url.spec.whatwg.org/#concept-url-serializer
2743 const url::PartType ifirst = [&]() {
2744 if (t1 <= url::HOST) {
2745 // authority, host
2746 if (!src.is_null(url::HOST)) {
2747 if (t1 == url::USERNAME && src.has_credentials())
2748 return url::USERNAME;
2749 return url::HOST;
2750 }
2751 return url::PATH_PREFIX;
2752 }
2753 // t1 == PATH
2754 return t1;
2755 }();
2756
2757 // part flag masks
2758 static constexpr unsigned kPartFlagMask[url::PART_COUNT] = {
2759 url::SCHEME_FLAG,
2760 0, // SCHEME_SEP
2761 url::USERNAME_FLAG,
2762 url::PASSWORD_FLAG,
2763 0, // HOST_START
2764 url::HOST_FLAG | url::HOST_TYPE_MASK,
2765 url::PORT_FLAG,
2766 0, // PATH_PREFIX
2767 url::PATH_FLAG | url::OPAQUE_PATH_FLAG,
2768 url::QUERY_FLAG,
2769 url::FRAGMENT_FLAG
2770 };
2771
2772 // copy flags; they can be used when copying / serializing url parts below
2773 unsigned mask = 0;
2774 for (int ind = t1; ind <= t2; ++ind) {
2775 mask |= kPartFlagMask[ind];
2776 }
2777 url_.flags_ = (url_.flags_ & ~mask) | (src.flags_ & mask);
2778
2779 // copy parts & str
2780 if (ifirst <= t2) {
2781 int ilast = t2;
2782 for (; ilast >= ifirst; --ilast) {
2783 if (src.part_end_[ilast])
2784 break;
2785 }
2786 if (ifirst <= ilast) {
2787 // prepare buffer to append data
2788 // IMPORTANT: do before any url_ members modifications!
2789 std::string& norm_url = start_part(ifirst);
2790
2791 // last part and url_.path_segment_count_
2792 std::size_t lastp_end = src.part_end_[ilast];
2793 if (pathOpFn && ilast == url::PATH) {
2794 std::size_t segment_count = src.path_segment_count_;
2795 // https://isocpp.org/wiki/faq/pointers-to-members
2796 // todo: use std::invoke (c++17)
2797 (src.*pathOpFn)(lastp_end, segment_count);
2798 url_.path_segment_count_ = segment_count;
2799 } else if (ifirst <= url::PATH && url::PATH <= ilast) {
2800 url_.path_segment_count_ = src.path_segment_count_;
2801 }
2802 // src
2803 const std::size_t offset = src.part_end_[ifirst - 1] + detail::kPartStart[ifirst];
2804 const char* const first = src.norm_url_.data() + offset;
2805 const char* const last = src.norm_url_.data() + lastp_end;
2806 // dest
2807 const auto delta = util::checked_diff<std::ptrdiff_t>(norm_url.length(), offset);
2808 // copy normalized url string from src
2809 norm_url.append(first, last);
2810 // adjust url_.part_end_
2811 for (int ind = ifirst; ind < ilast; ++ind) {
2812 // if (src.part_end_[ind]) // it is known, that src.part_end_[ind] has value, so check isn't needed
2813 url_.part_end_[ind] = src.part_end_[ind] + delta;
2814 }
2815 // ilast part from lastp
2816 url_.part_end_[ilast] = lastp_end + delta;
2817 last_pt_ = static_cast<url::PartType>(ilast);
2818 }
2819 }
2820}
2821
2822// replace part in url
2823
2824inline std::size_t url_serializer::get_part_pos(const url::PartType pt) const {
2825 return pt > url::SCHEME ? url_.part_end_[pt - 1] : 0;
2826}
2827
2828inline std::size_t url_serializer::get_part_len(const url::PartType pt) const {
2829 return url_.part_end_[pt] - url_.part_end_[pt - 1];
2830}
2831
2832inline void url_serializer::replace_part(const url::PartType new_pt, const char* str, const std::size_t len) {
2833 replace_part(new_pt, str, len, new_pt, 0);
2834}
2835
2836inline void url_serializer::replace_part(const url::PartType last_pt, const char* str, const std::size_t len,
2837 const url::PartType first_pt, const std::size_t len0)
2838{
2839 const std::size_t b = get_part_pos(first_pt);
2840 const std::size_t l = url_.part_end_[last_pt] - b;
2841 url_.norm_url_.replace(b, l, str, len);
2842 std::fill(std::begin(url_.part_end_) + first_pt, std::begin(url_.part_end_) + last_pt, b + len0);
2843 // adjust positions
2844 const auto diff = util::checked_diff<std::ptrdiff_t>(len, l);
2845 if (diff) {
2846 for (auto it = std::begin(url_.part_end_) + last_pt; it != std::end(url_.part_end_); ++it) {
2847 if (*it == 0) break;
2848 // perform arithmetics using signed type ptrdiff_t, because diff can be negative
2849 *it = static_cast<std::ptrdiff_t>(*it) + diff;
2850 }
2851 }
2852}
2853
2854
2855// url_setter class
2856
2857// inline url_setter::~url_setter() {}
2858
2859//???
2860inline void url_setter::reserve(std::size_t new_cap) {
2861 util::reserve(strp_, new_cap);
2862}
2863
2864// set scheme
2865
2866inline std::string& url_setter::start_scheme() {
2867 return strp_;
2868}
2869
2870inline void url_setter::save_scheme() {
2871 replace_part(url::SCHEME, strp_.data(), strp_.length());
2872 set_scheme(strp_.length());
2873}
2874
2875// set/clear/empty url's part
2876
2877inline std::string& url_setter::start_part(url::PartType new_pt) {
2878 assert(new_pt > url::SCHEME);
2879 curr_pt_ = new_pt;
2880 if (url_.part_end_[new_pt]) {
2881 // is there any part after new_pt?
2882 if (new_pt < url::FRAGMENT && url_.part_end_[new_pt] < url_.norm_url_.length()) {
2883 use_strp_ = true;
2884 switch (new_pt) {
2885 case url::HOST:
2886 if (get_part_len(url::SCHEME_SEP) < 3)
2887 strp_ = "://";
2888 else
2889 strp_.clear();
2890 break;
2891 case url::PASSWORD:
2892 case url::PORT:
2893 strp_ = ':';
2894 break;
2895 case url::QUERY:
2896 strp_ = '?';
2897 break;
2898 default:
2899 strp_.clear();
2900 break;
2901 }
2902 return strp_;
2903 }
2904 // Remove new_pt part
2905 last_pt_ = static_cast<url::PartType>(static_cast<int>(new_pt) - 1);
2906 url_.norm_url_.resize(url_.part_end_[last_pt_]);
2907 url_.part_end_[new_pt] = 0;
2908 // if there are empty parts after new_pt, then set their end positions to zero
2909 for (auto pt = static_cast<int>(new_pt) + 1; pt <= url::FRAGMENT && url_.part_end_[pt]; ++pt)
2910 url_.part_end_[pt] = 0;
2911 } else {
2912 last_pt_ = find_last_part(new_pt);
2913 }
2914
2915 use_strp_ = false;
2916 return url_serializer::start_part(new_pt);
2917}
2918
2919inline void url_setter::save_part() {
2920 if (use_strp_) {
2921 if (curr_pt_ == url::HOST) {
2922 if (get_part_len(url::SCHEME_SEP) < 3)
2923 // SCHEME_SEP, USERNAME, PASSWORD, HOST_START; HOST
2924 replace_part(url::HOST, strp_.data(), strp_.length(), url::SCHEME_SEP, 3);
2925 else
2926 replace_part(url::HOST, strp_.data(), strp_.length());
2927 } else {
2928 const bool empty_val = strp_.length() <= detail::kPartStart[curr_pt_];
2929 switch (curr_pt_) {
2930 case url::USERNAME:
2931 case url::PASSWORD:
2932 if (!empty_val && !has_credentials()) {
2933 strp_ += '@';
2934 // USERNAME, PASSWORD; HOST_START
2935 replace_part(url::HOST_START, strp_.data(), strp_.length(), curr_pt_, strp_.length() - 1);
2936 break;
2937 } else if (empty_val && is_empty(curr_pt_ == url::USERNAME ? url::PASSWORD : url::USERNAME)) {
2938 // both username and password will be empty, so also drop '@'
2939 replace_part(url::HOST_START, "", 0, curr_pt_, 0);
2940 break;
2941 }
2942 [[fallthrough]];
2943 default:
2944 if ((curr_pt_ == url::PASSWORD || curr_pt_ == url::PORT) && empty_val)
2945 strp_.clear(); // drop ':'
2946 replace_part(curr_pt_, strp_.data(), strp_.length());
2947 break;
2948 }
2949 }
2950 // cleanup
2951 strp_.clear();
2952 } else {
2953 url_serializer::save_part();
2954 }
2955}
2956
2957inline void url_setter::clear_part(const url::PartType pt) {
2958 if (url_.part_end_[pt]) {
2959 replace_part(pt, "", 0);
2960 url_.flags_ &= ~(1u << pt); // set to null
2961 }
2962}
2963
2964inline void url_setter::empty_part(const url::PartType pt) {
2965 if (url_.part_end_[pt]) {
2966 replace_part(pt, "", 0);
2967 }
2968}
2969
2970inline void url_setter::empty_host() {
2971 empty_part(url::HOST);
2972 url_.set_host_type(HostType::Empty);
2973}
2974
2975inline std::string& url_setter::start_path_segment() {
2976 //curr_pt_ = url::PATH; // not used
2977 strp_ += '/';
2978 return strp_;
2979}
2980
2981inline void url_setter::save_path_segment() {
2982 path_seg_end_.push_back(strp_.length());
2983}
2984
2985inline void url_setter::commit_path() {
2986 // fill part_end_ until url::PATH if not filled
2987 for (int ind = url::PATH; ind > 0; --ind) {
2988 if (url_.part_end_[ind]) break;
2989 url_.part_end_[ind] = url_.norm_url_.length();
2990 }
2991 // replace path part
2992 replace_part(url::PATH, strp_.data(), strp_.length());
2993 url_.path_segment_count_ = path_seg_end_.size();
2994
2995 // "/." path prefix
2996 adjust_path_prefix();
2997}
2998
2999// https://url.spec.whatwg.org/#shorten-a-urls-path
3000
3001inline void url_setter::shorten_path() {
3002 if (path_seg_end_.size() == 1) {
3003 if (is_file_scheme() && strp_.length() == 3 &&
3004 detail::is_normalized_windows_drive(strp_[1], strp_[2]))
3005 return;
3006 path_seg_end_.pop_back();
3007 strp_.clear();
3008 } else if (path_seg_end_.size() >= 2) {
3009 path_seg_end_.pop_back();
3010 strp_.resize(path_seg_end_.back());
3011 }
3012}
3013
3014inline bool url_setter::is_empty_path() const {
3015 assert(!url_.has_opaque_path());
3016 // path_seg_end_ has meaning only if path is a list (path isn't opaque)
3017 return path_seg_end_.empty();
3018}
3019
3020inline url::PartType url_setter::find_last_part(url::PartType pt) const {
3021 for (int ind = pt; ind > 0; --ind)
3022 if (url_.part_end_[ind])
3023 return static_cast<url::PartType>(ind);
3024 return url::SCHEME;
3025}
3026
3035template <typename CharT>
3036inline const CharT* is_unc_path(const CharT* first, const CharT* last)
3037{
3038 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dfsc/149a3039-98ce-491a-9268-2f5ddef08192
3039 std::size_t path_components_count = 0;
3040 const CharT* end_of_share_name = nullptr;
3041 const auto* start = first;
3042 while (start != last) {
3043 const auto* pcend = std::find_if(start, last, detail::is_windows_slash<CharT>);
3044 // path components MUST be at least one character in length
3045 if (start == pcend)
3046 return nullptr;
3047 // path components MUST NOT contain a backslash (\‍) or a null
3048 if (std::find(start, pcend, '\0') != pcend)
3049 return nullptr;
3050
3051 ++path_components_count;
3052
3053 switch (path_components_count) {
3054 case 1:
3055 // Check the first UNC path component (hostname)
3056 switch (pcend - start) {
3057 case 1:
3058 // Do not allow "?" and "." hostnames, because "\\?\" means Win32 file
3059 // namespace and "\\.\" means Win32 device namespace
3060 if (start[0] == '?' || start[0] == '.')
3061 return nullptr;
3062 break;
3063 case 2:
3064 // Do not allow Windows drive letter, because it is not a valid hostname
3065 if (detail::is_windows_drive(start[0], start[1]))
3066 return nullptr;
3067 break;
3068 }
3069 // Accept UNC path with hostname, even if it does not contain share-name
3070 end_of_share_name = pcend;
3071 break;
3072 case 2:
3073 // Check the second UNC path component (share name).
3074 // Do not allow "." and ".." as share names, because they have
3075 // a special meaning and are removed by the URL parser.
3076 switch (pcend - start) {
3077 case 1:
3078 if (start[0] == '.')
3079 return nullptr;
3080 break;
3081 case 2:
3082 if (start[0] == '.' && start[1] == '.')
3083 return nullptr;
3084 break;
3085 }
3086 // A valid UNC path MUST contain two or more path components
3087 end_of_share_name = pcend;
3088 break;
3089 default:;
3090 }
3091 if (pcend == last) break;
3092 start = pcend + 1; // skip '\'
3093 }
3094 return end_of_share_name;
3095}
3096
3103template <typename CharT, typename IsSlash>
3104constexpr bool has_dot_dot_segment(const CharT* first, const CharT* last, IsSlash is_slash) {
3105 if (last - first >= 2) {
3106 const auto* ptr = first;
3107 const auto* end = last - 1;
3108 while ((ptr = std::char_traits<CharT>::find(ptr, end - ptr, '.')) != nullptr) {
3109 if (ptr[1] == '.' &&
3110 (ptr == first || is_slash(*(ptr - 1))) &&
3111 (last - ptr == 2 || is_slash(ptr[2])))
3112 return true;
3113 // skip '.' and following char
3114 ptr += 2;
3115 if (ptr >= end)
3116 break;
3117 }
3118 }
3119 return false;
3120}
3121
3122} // namespace detail
3123
3124UPA_EXPORT_BEGIN
3125
3126// URL utilities (non-member functions)
3127
3135[[nodiscard]] inline bool equals(const url& lhs, const url& rhs, bool exclude_fragments = false) {
3136 return lhs.serialize(exclude_fragments) == rhs.serialize(exclude_fragments);
3137}
3138
3140[[nodiscard]] inline bool operator==(const url& lhs, const url& rhs) noexcept {
3141 return lhs.norm_url_ == rhs.norm_url_;
3142}
3143
3152inline std::ostream& operator<<(std::ostream& os, const url& url) {
3153 return os << url.norm_url_;
3154}
3155
3162inline void swap(url& lhs, url& rhs) noexcept {
3163 lhs.swap(rhs);
3164}
3165
3168 posix = 1,
3170#ifdef _WIN32
3171 native = windows
3172#else
3174#endif
3175};
3176
3201template <class StrT, enable_if_str_arg_t<StrT> = 0>
3202[[nodiscard]] inline url url_from_file_path(const StrT& str, file_path_format format = file_path_format::native) {
3203 using CharT = str_arg_char_t<StrT>;
3204 const auto inp = make_str_arg(str);
3205 const auto* first = inp.begin();
3206 const auto* last = inp.end();
3207
3208 if (first == last) {
3209 throw url_error(validation_errc::file_empty_path, "Empty file path");
3210 }
3211
3212 const auto* pointer = first;
3213 const auto* start_of_check = first;
3214 const code_point_set* no_encode_set = nullptr;
3215
3216 std::string str_url("file://");
3217
3218 if (format == file_path_format::posix) {
3219 if (!detail::is_posix_slash(*first))
3220 throw url_error(validation_errc::file_unsupported_path, "Non-absolute POSIX path");
3221 if (detail::has_dot_dot_segment(start_of_check, last, detail::is_posix_slash<CharT>))
3222 throw url_error(validation_errc::file_unsupported_path, "Unsupported file path");
3223 // Absolute POSIX path
3224 no_encode_set = &posix_path_no_encode_set;
3225 } else {
3226 // Windows path?
3227 bool is_unc = false;
3228
3229 // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
3230 // https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
3231 // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
3232 if (last - pointer >= 2 &&
3233 detail::is_windows_slash(pointer[0]) &&
3234 detail::is_windows_slash(pointer[1])) {
3235 pointer += 2; // skip '\\'
3236
3237 // It is Win32 namespace path or UNC path?
3238 if (last - pointer >= 2 &&
3239 (pointer[0] == '?' || pointer[0] == '.') &&
3240 detail::is_windows_slash(pointer[1])) {
3241 // Win32 File ("\\?\") or Device ("\\.\") namespace path
3242 pointer += 2; // skip "?\" or ".\"
3243 if (last - pointer >= 4 &&
3244 (pointer[0] | 0x20) == 'u' &&
3245 (pointer[1] | 0x20) == 'n' &&
3246 (pointer[2] | 0x20) == 'c' &&
3247 detail::is_windows_slash(pointer[3])) {
3248 pointer += 4; // skip "UNC\"
3249 is_unc = true;
3250 }
3251 } else {
3252 // UNC path
3253 is_unc = true;
3254 }
3255 }
3256 start_of_check = is_unc
3257 ? detail::is_unc_path(pointer, last)
3258 : detail::is_windows_os_drive_absolute_path(pointer, last);
3259 if (start_of_check == nullptr ||
3260 detail::has_dot_dot_segment(start_of_check, last, detail::is_windows_slash<CharT>))
3261 throw url_error(validation_errc::file_unsupported_path, "Unsupported file path");
3262 no_encode_set = &raw_path_no_encode_set;
3263 if (!is_unc) str_url.push_back('/'); // start path
3264 }
3265
3266 // Check for null characters
3267 if (util::contains_null(start_of_check, last))
3268 throw url_error(validation_errc::null_character, "Path contains null character");
3269
3270 // make URL
3271 detail::append_utf8_percent_encoded(pointer, last, *no_encode_set, str_url);
3272 return url(str_url);
3273}
3274
3281[[nodiscard]] inline url url_from_file_path(const std::filesystem::path& path) {
3282#ifdef _WIN32
3283 // On Windows, the native path is encoded in UTF-16
3284 return url_from_file_path(path.native());
3285#else
3286 // Ensure string input is UTF-8 encoded
3287 return url_from_file_path(path.u8string());
3288#endif
3289}
3290
3299[[nodiscard]] inline std::string path_from_file_url(const url& file_url, file_path_format format = file_path_format::native) {
3300 if (!file_url.is_file_scheme())
3301 throw url_error(validation_errc::not_file_url, "Not a file URL");
3302
3303 // source
3304 const auto hostname = file_url.hostname();
3305 const bool is_host = !hostname.empty();
3306
3307 // target
3308 std::string path;
3309
3310 if (format == file_path_format::posix) {
3311 if (is_host)
3312 throw url_error(validation_errc::file_url_cannot_have_host, "POSIX path cannot have host");
3313 // percent decode pathname
3314 detail::append_percent_decoded(file_url.pathname(), path);
3315 } else {
3316 // format == file_path_format::windows
3317 if (is_host) {
3318 // UNC path cannot have "." hostname, because "\\.\" means Win32 device namespace
3319 if (hostname == ".")
3320 throw url_error(validation_errc::file_url_unsupported_host, "UNC path cannot have \".\" hostname");
3321 // UNC path
3322 path.append("\\\\");
3323 if (file_url.host_type() == HostType::IPv6) {
3324 // Form an IPV6 address host-name by substituting hyphens for the colons and appending ".ipv6-literal.net"
3325 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc
3326 std::replace_copy(std::next(hostname.begin()), std::prev(hostname.end()),
3327 std::back_inserter(path), ':', '-');
3328 path.append(".ipv6-literal.net");
3329 } else {
3330 path.append(hostname);
3331 }
3332 }
3333
3334 // percent decode pathname and normalize slashes
3335 const auto start = static_cast<std::ptrdiff_t>(path.length());
3336 detail::append_percent_decoded(file_url.pathname(), path);
3337 std::replace(std::next(path.begin(), start), path.end(), '/', '\\');
3338
3339 if (is_host) {
3340 if (!detail::is_unc_path(path.data() + 2, path.data() + path.length()))
3341 throw url_error(validation_errc::file_url_invalid_unc, "Invalid UNC path");
3342 } else {
3343 if (detail::pathname_has_windows_os_drive(path)) {
3344 path.erase(0, 1); // remove leading '\\'
3345 if (path.length() == 2)
3346 path.push_back('\\'); // "C:" -> "C:\"
3347 } else {
3348 // https://datatracker.ietf.org/doc/html/rfc8089#appendix-E.3.2
3349 // Maybe a UNC path. Possible variants:
3350 // 1) file://///host/path -> \\\host\path
3351 // 2) file:////host/path -> \\host\path
3352 const auto count_leading_slashes = std::find_if(
3353 path.data(),
3354 path.data() + std::min(static_cast<std::size_t>(4), path.length()),
3355 [](char c) { return c != '\\'; }) - path.data();
3356 if (count_leading_slashes == 3)
3357 path.erase(0, 1); // remove leading '\\'
3358 else if (count_leading_slashes != 2)
3359 throw url_error(validation_errc::file_url_not_windows_path, "Not a Windows path");
3360 if (!detail::is_unc_path(path.data() + 2, path.data() + path.length()))
3361 throw url_error(validation_errc::file_url_invalid_unc, "Invalid UNC path");
3362 }
3363 }
3364 }
3365
3366 // Check for null characters
3367 if (util::contains_null(path.begin(), path.end()))
3368 throw url_error(validation_errc::null_character, "Path contains null character");
3369
3370 return path;
3371}
3372
3379[[nodiscard]] inline std::filesystem::path fs_path_from_file_url(const url& file_url) {
3380#ifdef UPA_CPP_20
3381 const std::string path_str = path_from_file_url(file_url);
3382 // the path_str is encoded in UTF-8
3383 return { util::to_string_view<char8_t>(path_str.data(), path_str.size()),
3384 std::filesystem::path::native_format };
3385#else
3386 // the u8path is deprecated in C++20
3387 return std::filesystem::u8path(path_from_file_url(file_url));
3388#endif
3389}
3390
3391// Upa URL version functions
3392
3398UPA_API std::uint32_t version_num();
3399
3406inline bool check_version() {
3407 constexpr auto sover_mask = static_cast<std::uint32_t>(-1) ^
3408 static_cast<std::uint32_t>(0xFF);
3409 return (version_num() & sover_mask) ==
3410 (static_cast<std::uint32_t>(UPA_URL_VERSION_NUM) & sover_mask);
3411}
3412
3413UPA_EXPORT_END
3414
3415} // namespace upa
3416
3417
3418namespace std {
3419
3421template<>
3422struct hash<upa::url> {
3423 [[nodiscard]] inline std::size_t operator()(const upa::url& url) const noexcept {
3424 return std::hash<std::string>{}(url.norm_url_);
3425 }
3426};
3427
3428} // namespace std
3429
3430// Includes that require the url class declaration
3431#include "url_search_params-inl.h" // IWYU pragma: export
3432
3433#endif // UPA_URL_H
Represents code point set.
URL exception class.
Definition url_result.h:103
URLSearchParams class.
bool empty() const noexcept
URL class.
Definition url.h:84
bool pathname(const StrT &str)
The pathname setter.
Definition url.h:1589
url(const T &str_url, const url &base)
Parsing constructor.
Definition url.h:161
~url()=default
destructor
bool is_empty(PartType t) const
Checks whether the URL's part (URL record member) is empty or null.
Definition url.h:1332
bool search(const StrT &str)
The search setter.
Definition url.h:1600
int real_port_int() const
Definition url.h:1244
validation_errc parse(const T &str_url, const url &base)
Parses given URL string against base URL.
Definition url.h:210
UPA_API std::pair< std::size_t, std::size_t > get_part_pos(PartType t, bool with_sep=false) const
Gets the start and end position of the specified URL part.
void clear()
Clears URL.
Definition url.h:1419
bool set_hash(const StrT &str)
Equivalent to hash(const StrT& str).
Definition url.h:402
bool set_href(const StrT &str)
Equivalent to href(const StrT& str).
Definition url.h:285
std::string_view serialize(bool exclude_fragment=false) const
URL serializer.
Definition url.h:1307
bool set_username(const StrT &str)
Equivalent to username(const StrT& str).
Definition url.h:311
std::string_view get_search() const
Equivalent to search() const .
Definition url.h:516
bool set_search(const StrT &str)
Equivalent to search(const StrT& str).
Definition url.h:389
bool is_valid() const noexcept
Returns whether the URL is valid.
Definition url.h:1319
url(const T &str_url, const TB &str_base)
Parsing constructor.
Definition url.h:172
std::string to_string() const
Definition url.h:1171
std::string_view href() const
The href getter.
Definition url.h:1167
std::string_view get_protocol() const
Equivalent to protocol() const .
Definition url.h:432
std::string_view get_href() const
Equivalent to href() const .
Definition url.h:413
static bool can_parse(const T &str_url, const url *pbase=nullptr)
Checks if a given URL string can be successfully parsed.
Definition url.h:237
url_search_params & search_params() &
The searchParams getter.
Definition url.h:1285
bool is_null(PartType t) const noexcept
Checks whether the URL's part (URL record member) is null.
Definition url.h:1341
bool hostname(const StrT &str)
The hostname setter.
Definition url.h:1560
std::string_view hash() const
The hash getter.
Definition url.h:1275
std::string_view get_host() const
Equivalent to host() const .
Definition url.h:459
std::string_view search() const
The search getter.
Definition url.h:1265
bool set_host(const StrT &str)
Equivalent to host(const StrT& str).
Definition url.h:337
HostType host_type() const noexcept
The host_type getter.
Definition url.h:1231
std::string_view path() const
The path getter.
Definition url.h:1252
std::string_view protocol() const
The protocol getter.
Definition url.h:1205
std::string_view get_password() const
Equivalent to password() const .
Definition url.h:450
bool has_credentials() const
Definition url.h:1357
PartType
Definition url.h:88
@ QUERY
Definition url.h:98
@ USERNAME
Definition url.h:91
@ SCHEME_SEP
Definition url.h:90
@ PORT
Definition url.h:95
@ HOST
Definition url.h:94
@ PATH
Definition url.h:97
@ SCHEME
Definition url.h:89
@ PATH_PREFIX
Definition url.h:96
@ FRAGMENT
Definition url.h:99
@ PART_COUNT
Definition url.h:100
@ HOST_START
Definition url.h:93
@ PASSWORD
Definition url.h:92
bool has_opaque_path() const noexcept
Definition url.h:1391
static bool can_parse(const T &str_url, const url &base)
Checks if a given URL string can be successfully parsed.
Definition url.h:251
int port_int() const
Definition url.h:1239
std::string_view get_hostname() const
Equivalent to hostname() const .
Definition url.h:468
friend std::ostream & operator<<(std::ostream &os, const url &url)
Performs stream output on URL.
Definition url.h:3152
std::string_view get_username() const
Equivalent to username() const .
Definition url.h:441
bool is_file_scheme() const noexcept
Definition url.h:1349
std::string_view get_path() const
Equivalent to path() const .
Definition url.h:498
bool set_pathname(const StrT &str)
Equivalent to pathname(const StrT& str).
Definition url.h:376
url()=default
Default constructor.
void swap(url &other) noexcept
Swaps the contents of two URLs.
Definition url.h:1428
url & operator=(const url &other)=default
Copy assignment.
std::string_view username() const
The username getter.
Definition url.h:1210
bool is_http_scheme() const noexcept
Definition url.h:1353
bool set_password(const StrT &str)
Equivalent to password(const StrT& str).
Definition url.h:324
std::string_view port() const
The port getter.
Definition url.h:1235
std::string_view get_pathname() const
Equivalent to pathname() const .
Definition url.h:507
validation_errc parse(const T &str_url, const TB &str_base)
Parses given URL string against base URL.
Definition url.h:220
url & safe_assign(url &&other)
Safe move assignment.
Definition url.h:1140
bool is_special_scheme() const noexcept
Definition url.h:1345
std::string_view password() const
The password getter.
Definition url.h:1214
bool set_port(const StrT &str)
Equivalent to port(const StrT& str).
Definition url.h:363
std::string_view hostname() const
The hostname getter.
Definition url.h:1227
friend class url_search_params
Definition url.h:735
static bool can_parse(const T &str_url, const TB &str_base)
Checks if a given URL string can be successfully parsed.
Definition url.h:265
std::string_view get_hash() const
Equivalent to hash() const .
Definition url.h:525
bool set_protocol(const StrT &str)
Equivalent to protocol(const StrT& str).
Definition url.h:298
friend bool operator==(const url &lhs, const url &rhs) noexcept
Lexicographically compares two URL's.
Definition url.h:3140
std::string_view get_port() const
Equivalent to port() const .
Definition url.h:482
url(const url &other)=default
Copy constructor.
std::string origin() const
The origin getter.
Definition url.h:1180
std::string_view pathname() const
The pathname getter.
Definition url.h:1259
bool set_hostname(const StrT &str)
Equivalent to hostname(const StrT& str).
Definition url.h:350
std::string_view host() const
The host getter.
Definition url.h:1218
bool empty() const noexcept
Checks whether the URL is empty.
Definition url.h:1315
validation_errc parse(const T &str_url, const url *base=nullptr)
Parses given URL string against base URL.
Definition url.h:199
friend class detail::url_serializer
Definition url.h:732
std::string_view get_part_view(PartType t) const
Gets URL's part (URL record member) as string.
Definition url.h:1323
url(const T &str_url, const url *pbase=nullptr)
Parsing constructor.
Definition url.h:150
Definition url.h:3418
Definition url.h:50
std::string path_from_file_url(const url &file_url, file_path_format format=file_path_format::native)
Get OS path from file URL.
Definition url.h:3299
url url_from_file_path(const StrT &str, file_path_format format=file_path_format::native)
Make URL from OS file path.
Definition url.h:3202
std::filesystem::path fs_path_from_file_url(const url &file_url)
Get OS path as std::filesystem::path from file URL.
Definition url.h:3379
constexpr code_point_set query_no_encode_set
HostType
Host representation.
Definition url_host.h:33
@ Empty
empty host is the empty string
Definition url_host.h:34
@ IPv6
host is an IPv6 address
Definition url_host.h:39
std::ostream & operator<<(std::ostream &os, const url &url)
Performs stream output on URL.
Definition url.h:3152
file_path_format
File path format.
Definition url.h:3167
@ windows
Windows file path format.
Definition url.h:3169
@ native
The file path format corresponds to the OS on which the code was compiled.
Definition url.h:3173
@ posix
POSIX file path format.
Definition url.h:3168
UPA_API std::uint32_t version_num()
Get library version encoded to one number.
bool equals(const url &lhs, const url &rhs, bool exclude_fragments=false)
URL equivalence.
Definition url.h:3135
bool check_version()
Check used library version.
Definition url.h:3406
validation_errc
URL validation and other error codes.
Definition url_result.h:22
@ invalid_base
Invalid base.
Definition url_result.h:83
@ file_url_not_windows_path
Not a Windows path in file URL.
Definition url_result.h:92
@ port_out_of_range
The input’s port is too big.
Definition url_result.h:77
@ file_url_cannot_have_host
POSIX path cannot have host.
Definition url_result.h:89
@ not_file_url
Not a file URL.
Definition url_result.h:88
@ null_character
Path contains null character.
Definition url_result.h:93
@ file_url_unsupported_host
UNC path cannot have "." hostname.
Definition url_result.h:90
@ file_url_invalid_unc
Invalid UNC path in file URL.
Definition url_result.h:91
@ host_missing
The input has a special scheme, but does not contain a host.
Definition url_result.h:76
@ file_unsupported_path
Unsupported file path (e.g. non-absolute).
Definition url_result.h:86
@ file_empty_path
Path cannot be empty.
Definition url_result.h:85
@ port_invalid
The input’s port is invalid.
Definition url_result.h:78
@ ignored
Setter ignored the value (internal).
Definition url_result.h:26
constexpr code_point_set special_query_no_encode_set
void swap(url &lhs, url &rhs) noexcept
Swaps the contents of two URLs.
Definition url.h:3162
constexpr code_point_set raw_path_no_encode_set
bool operator==(const url &lhs, const url &rhs) noexcept
Lexicographically compares two URL's.
Definition url.h:3140
constexpr code_point_set path_no_encode_set
constexpr code_point_set fragment_no_encode_set
constexpr code_point_set userinfo_no_encode_set
constexpr code_point_set posix_path_no_encode_set
std::size_t operator()(const upa::url &url) const noexcept
Definition url.h:3423
#define UPA_URL_VERSION_NUM
Version encoded to one number.
Definition url_version.h:21