1 // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
2 // the main distribution directory for license terms and copyright or visit
3 // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
4
5 #pragma once
6
7 #include <cstdint>
8 #include <vector>
9
10 #include "caf/detail/comparable.hpp"
11 #include "caf/detail/core_export.hpp"
12 #include "caf/detail/unordered_flat_map.hpp"
13 #include "caf/fwd.hpp"
14 #include "caf/hash/fnv.hpp"
15 #include "caf/inspector_access.hpp"
16 #include "caf/intrusive_cow_ptr.hpp"
17 #include "caf/intrusive_ptr.hpp"
18 #include "caf/ip_address.hpp"
19 #include "caf/make_counted.hpp"
20 #include "caf/string_view.hpp"
21 #include "caf/variant.hpp"
22
23 namespace caf {
24
25 /// A URI according to RFC 3986.
26 class CAF_CORE_EXPORT uri : detail::comparable<uri>,
27 detail::comparable<uri, string_view> {
28 public:
29 // -- friends -
30
31 template <class>
32 friend struct inspector_access;
33
34 // -- member types -----------------------------------------------------------
35
36 /// Host subcomponent of the authority component. Either an IP address or
37 /// an hostname as string.
38 using host_type = variant<std::string, ip_address>;
39
40 /// Bundles the authority component of the URI, i.e., userinfo, host, and
41 /// port.
42 struct authority_type {
43 std::string userinfo;
44 host_type host;
45 uint16_t port;
46
authority_typecaf::uri::authority_type47 authority_type() : port(0) {
48 // nop
49 }
50
51 /// Returns whether `host` is empty, i.e., the host is not an IP address
52 /// and the string is empty.
emptycaf::uri::authority_type53 bool empty() const noexcept {
54 auto str = get_if<std::string>(&host);
55 return str != nullptr && str->empty();
56 }
57 };
58
59 /// Separates the query component into key-value pairs.
60 using path_list = std::vector<string_view>;
61
62 /// Separates the query component into key-value pairs.
63 using query_map = detail::unordered_flat_map<std::string, std::string>;
64
65 class CAF_CORE_EXPORT impl_type {
66 public:
67 // -- constructors, destructors, and assignment operators ------------------
68
69 impl_type();
70
71 impl_type(const impl_type&) = delete;
72
73 impl_type& operator=(const impl_type&) = delete;
74
75 // -- member variables -----------------------------------------------------
76
77 /// Null-terminated buffer for holding the string-representation of the URI.
78 std::string str;
79
80 /// Scheme component.
81 std::string scheme;
82
83 /// Assembled authority component.
84 uri::authority_type authority;
85
86 /// Path component.
87 std::string path;
88
89 /// Query component as key-value pairs.
90 uri::query_map query;
91
92 /// The fragment component.
93 std::string fragment;
94
95 // -- properties -----------------------------------------------------------
96
valid() const97 bool valid() const noexcept {
98 return !scheme.empty() && (!authority.empty() || !path.empty());
99 }
100
unique() const101 bool unique() const noexcept {
102 return rc_.load() == 1;
103 }
104
105 // -- modifiers ------------------------------------------------------------
106
107 /// Assembles the human-readable string representation for this URI.
108 void assemble_str();
109
110 // -- friend functions -----------------------------------------------------
111
intrusive_ptr_add_ref(const impl_type * p)112 friend void intrusive_ptr_add_ref(const impl_type* p) {
113 p->rc_.fetch_add(1, std::memory_order_relaxed);
114 }
115
intrusive_ptr_release(const impl_type * p)116 friend void intrusive_ptr_release(const impl_type* p) {
117 if (p->rc_ == 1 || p->rc_.fetch_sub(1, std::memory_order_acq_rel) == 1)
118 delete p;
119 }
120
121 private:
122 // -- member variables -----------------------------------------------------
123
124 mutable std::atomic<size_t> rc_;
125 };
126
127 /// Pointer to implementation.
128 using impl_ptr = intrusive_ptr<impl_type>;
129
130 // -- constructors, destructors, and assignment operators --------------------
131
132 uri();
133
134 uri(uri&&) = default;
135
136 uri(const uri&) = default;
137
138 uri& operator=(uri&&) = default;
139
140 uri& operator=(const uri&) = default;
141
142 explicit uri(impl_ptr ptr);
143
144 // -- properties -------------------------------------------------------------
145
146 /// Returns whether all components of this URI are empty.
empty() const147 bool empty() const noexcept {
148 return str().empty();
149 }
150
151 /// Returns whether the URI contains valid content.
valid() const152 bool valid() const noexcept {
153 return !empty();
154 }
155
156 /// Returns the full URI as provided by the user.
str() const157 string_view str() const noexcept {
158 return impl_->str;
159 }
160
161 /// Returns the scheme component.
scheme() const162 string_view scheme() const noexcept {
163 return impl_->scheme;
164 }
165
166 /// Returns the authority component.
authority() const167 const authority_type& authority() const noexcept {
168 return impl_->authority;
169 }
170
171 /// Returns the path component as provided by the user.
path() const172 string_view path() const noexcept {
173 return impl_->path;
174 }
175
176 /// Returns the query component as key-value map.
query() const177 const query_map& query() const noexcept {
178 return impl_->query;
179 }
180
181 /// Returns the fragment component.
fragment() const182 string_view fragment() const noexcept {
183 return impl_->fragment;
184 }
185
186 /// Returns a hash code over all components.
187 size_t hash_code() const noexcept;
188
189 /// Returns a new URI with the `authority` component only.
190 /// @returns A new URI in the form `scheme://authority` if the authority
191 /// exists, otherwise `none`.`
192 optional<uri> authority_only() const;
193
194 // -- comparison -------------------------------------------------------------
195
compare(const uri & other) const196 auto compare(const uri& other) const noexcept {
197 return str().compare(other.str());
198 }
199
compare(string_view x) const200 auto compare(string_view x) const noexcept {
201 return str().compare(x);
202 }
203
204 // -- parsing ----------------------------------------------------------------
205
206 /// Returns whether `parse` would produce a valid URI.
207 static bool can_parse(string_view str) noexcept;
208
209 private:
210 impl_ptr impl_;
211 };
212
213 // -- related free functions ---------------------------------------------------
214
215 template <class Inspector>
inspect(Inspector & f,uri::authority_type & x)216 bool inspect(Inspector& f, uri::authority_type& x) {
217 return f.object(x).fields(f.field("userinfo", x.userinfo),
218 f.field("host", x.host), f.field("port", x.port));
219 }
220
221 template <class Inspector>
inspect(Inspector & f,uri::impl_type & x)222 bool inspect(Inspector& f, uri::impl_type& x) {
223 auto load_cb = [&] {
224 x.assemble_str();
225 return true;
226 };
227 return f.object(x)
228 .on_load(load_cb) //
229 .fields(f.field("str", x.str), f.field("scheme", x.scheme),
230 f.field("authority", x.authority), f.field("path", x.path),
231 f.field("query", x.query), f.field("fragment", x.fragment));
232 }
233
234 /// @relates uri
235 CAF_CORE_EXPORT std::string to_string(const uri& x);
236
237 /// @relates uri
238 CAF_CORE_EXPORT std::string to_string(const uri::authority_type& x);
239
240 /// @relates uri
241 CAF_CORE_EXPORT error parse(string_view str, uri& dest);
242
243 /// @relates uri
244 CAF_CORE_EXPORT expected<uri> make_uri(string_view str);
245
246 template <>
247 struct inspector_access<uri> : inspector_access_base<uri> {
248 template <class Inspector>
applycaf::inspector_access249 static bool apply(Inspector& f, uri& x) {
250 if (f.has_human_readable_format()) {
251 auto get = [&x] { return to_string(x); };
252 auto set = [&x](std::string str) {
253 auto err = parse(str, x);
254 return !err;
255 };
256 return f.apply(get, set);
257 } else {
258 if constexpr (Inspector::is_loading)
259 if (!x.impl_->unique())
260 x.impl_.reset(new uri::impl_type, false);
261 return inspect(f, *x.impl_);
262 }
263 }
264 };
265
266 } // namespace caf
267
268 namespace std {
269
270 template <>
271 struct hash<caf::uri> {
operator ()std::hash272 size_t operator()(const caf::uri& x) const noexcept {
273 return caf::hash::fnv<size_t>::compute(x);
274 }
275 };
276
277 } // namespace std
278