9
9
#include < variant>
10
10
#include < string>
11
11
#include < cassert>
12
+ #include < algorithm>
12
13
#include < tl/expected.hpp>
14
+ #include < range/v3/algorithm/find_if.hpp>
15
+ #include < range/v3/range/access.hpp>
13
16
#include < skyr/v1/core/errors.hpp>
14
17
#include < skyr/v1/network/ipv4_address.hpp>
15
18
#include < skyr/v1/network/ipv6_address.hpp>
19
+ #include < skyr/v1/percent_encoding/percent_encoded_char.hpp>
20
+ #include < skyr/v1/percent_encoding/percent_decode.hpp>
21
+ #include < skyr/v1/domain/domain.hpp>
22
+
16
23
17
24
namespace skyr {
18
25
inline namespace v1 {
19
- // / Represents a domain in a [URL host](https://url.spec.whatwg.org/#host-representation)
20
- struct domain {
26
+ // / Represents a domain name in a [URL host](https://url.spec.whatwg.org/#host-representation)
27
+ struct domain_name {
21
28
std::string name;
22
29
};
23
30
@@ -35,7 +42,7 @@ class host {
35
42
using host_types = std::variant<
36
43
skyr::v1::ipv4_address,
37
44
skyr::v1::ipv6_address,
38
- skyr::v1::domain ,
45
+ skyr::v1::domain_name ,
39
46
skyr::v1::opaque_host,
40
47
skyr::v1::empty_host
41
48
>;
@@ -54,13 +61,13 @@ class host {
54
61
55
62
// / Constructor
56
63
// / \param host A domain name
57
- explicit host (skyr::v1::domain host)
58
- : host_(host) {}
64
+ explicit host (skyr::v1::domain_name host)
65
+ : host_(std::move( host) ) {}
59
66
60
67
// / Constructor
61
68
// / \param host An opaque host string
62
69
explicit host (skyr::v1::opaque_host host)
63
- : host_(host) {}
70
+ : host_(std::move( host) ) {}
64
71
65
72
// / Constructor
66
73
// / \param hsost An empty host
@@ -79,7 +86,7 @@ class host {
79
86
else if constexpr (std::is_same_v<T, skyr::v1::ipv6_address>) {
80
87
return " [" + host.serialize () + " ]" ;
81
88
}
82
- else if constexpr (std::is_same_v<T, skyr::v1::domain > ||
89
+ else if constexpr (std::is_same_v<T, skyr::v1::domain_name > ||
83
90
std::is_same_v<T, skyr::v1::opaque_host>) {
84
91
return host.name ;
85
92
}
@@ -93,14 +100,14 @@ class host {
93
100
94
101
// /
95
102
// / \return \c true if the host is a domain, \c false otherwise
96
- [[nodiscard]] auto is_domain () const noexcept {
97
- return std::holds_alternative<skyr::v1::domain >(host_);
103
+ [[nodiscard]] auto is_domain_name () const noexcept {
104
+ return std::holds_alternative<skyr::v1::domain_name >(host_);
98
105
}
99
106
100
107
// /
101
108
// / \return
102
- [[nodiscard]] auto domain () const noexcept -> std::optional<std::string> {
103
- return is_domain () ? std::make_optional (std::get<skyr::v1::domain >(host_).name ) : std::nullopt;
109
+ [[nodiscard]] auto domain_name () const noexcept -> std::optional<std::string> {
110
+ return is_domain_name () ? std::make_optional (std::get<skyr::v1::domain_name >(host_).name ) : std::nullopt;
104
111
}
105
112
106
113
// /
@@ -150,16 +157,105 @@ class host {
150
157
host_types host_;
151
158
};
152
159
160
+ namespace details {
161
+ constexpr static auto is_forbidden_host_point = [](auto byte) {
162
+ return
163
+ (byte == ' \0 ' ) || (byte == ' \t ' ) || (byte == ' \n ' ) || (byte == ' \r ' ) || (byte == ' ' ) || (byte == ' #' ) ||
164
+ (byte == ' %' ) || (byte == ' /' ) || (byte == ' :' ) || (byte == ' <' ) || (byte == ' >' ) || (byte == ' ?' ) ||
165
+ (byte == ' @' ) || (byte == ' [' ) || (byte == ' \\ ' ) || (byte == ' ]' ) || (byte == ' ^' );
166
+ };
167
+
168
+ inline auto parse_opaque_host (std::string_view input,
169
+ bool *validation_error) -> tl::expected<skyr::v1::opaque_host, url_parse_errc> {
170
+ constexpr static auto is_forbidden = [] (auto byte) -> bool {
171
+ return (byte != ' %' ) && is_forbidden_host_point (byte);
172
+ };
173
+
174
+ if (std::cend (input) != ranges::find_if (input, is_forbidden)) {
175
+ *validation_error |= true ;
176
+ return tl::make_unexpected (url_parse_errc::forbidden_host_point);
177
+ }
178
+
179
+ auto output = std::string ();
180
+ for (auto c : input) {
181
+ auto pct_encoded = percent_encode_byte (std::byte (c), percent_encoding::encode_set::c0_control);
182
+ output += pct_encoded.to_string ();
183
+ }
184
+ return skyr::v1::opaque_host{std::move (output)};
185
+ }
186
+ } // namespace details
187
+
153
188
// / Parses a string to either a domain, IPv4 address or IPv6 address according to
154
189
// / https://url.spec.whatwg.org/#host-parsing
155
190
// / \param input An input string
156
191
// / \param is_not_special \c true to process only non-special hosts, \c false otherwise
157
192
// / \param validation_error Set to \c true if there was a validation error
158
193
// / \return A host as a domain (std::string), ipv4_address or ipv6_address, or an error code
159
- auto parse_host (
194
+ inline auto parse_host (
160
195
std::string_view input,
161
196
bool is_not_special,
162
- bool *validation_error) -> tl::expected<host, url_parse_errc>;
197
+ bool *validation_error) -> tl::expected<host, url_parse_errc> {
198
+ if (input.empty ()) {
199
+ return host{empty_host{}};
200
+ }
201
+
202
+ if (input.front () == ' [' ) {
203
+ if (input.back () != ' ]' ) {
204
+ *validation_error |= true ;
205
+ return tl::make_unexpected (url_parse_errc::invalid_ipv6_address);
206
+ }
207
+
208
+ auto view = std::string_view (input);
209
+ view.remove_prefix (1 );
210
+ view.remove_suffix (1 );
211
+ bool ipv6_validation_error = false ;
212
+ auto ipv6_address = parse_ipv6_address (view, &ipv6_validation_error);
213
+ if (ipv6_address) {
214
+ *validation_error = ipv6_validation_error;
215
+ return skyr::v1::host{ipv6_address.value ()};
216
+ }
217
+ else {
218
+ return tl::make_unexpected (url_parse_errc::invalid_ipv6_address);
219
+ }
220
+ }
221
+
222
+ if (is_not_special) {
223
+ return details::parse_opaque_host (input, validation_error).and_then (
224
+ [] (auto &&h) -> tl::expected<host, url_parse_errc> { return host{h}; });
225
+ }
226
+
227
+ auto domain_name = std::string{};
228
+ auto range = percent_encoding::percent_decode_range{input};
229
+ for (auto it = std::cbegin (range); it != std::cend (range); ++it) {
230
+ if (!*it) {
231
+ return tl::make_unexpected (url_parse_errc::cannot_decode_host_point);
232
+ }
233
+ domain_name.push_back ((*it).value ());
234
+ }
235
+
236
+ auto ascii_domain = std::string{};
237
+ if (!domain_to_ascii (domain_name, &ascii_domain)) {
238
+ return tl::make_unexpected (url_parse_errc::domain_error);
239
+ }
240
+
241
+ if (ranges::cend (ascii_domain) != ranges::find_if (ascii_domain, details::is_forbidden_host_point)) {
242
+ *validation_error |= true ;
243
+ return tl::make_unexpected (url_parse_errc::domain_error);
244
+ }
245
+
246
+ bool ipv4_validation_error = false ;
247
+ auto host = parse_ipv4_address (ascii_domain, &ipv4_validation_error);
248
+ if (!host) {
249
+ if (host.error () == ipv4_address_errc::overflow) {
250
+ return tl::make_unexpected (url_parse_errc::invalid_ipv4_address);
251
+ }
252
+ else {
253
+ return skyr::v1::host{skyr::v1::domain_name{std::move (ascii_domain)}};
254
+ }
255
+ }
256
+ *validation_error = ipv4_validation_error;
257
+ return skyr::v1::host{host.value ()};
258
+ }
163
259
164
260
// / Parses a string to either a domain, IPv4 address or IPv6 address according to
165
261
// / https://url.spec.whatwg.org/#host-parsing
0 commit comments