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