/src/http/src/uri/authority.rs
Line | Count | Source |
1 | | use std::convert::TryFrom; |
2 | | use std::hash::{Hash, Hasher}; |
3 | | use std::str::FromStr; |
4 | | use std::{cmp, fmt, str}; |
5 | | |
6 | | use bytes::Bytes; |
7 | | |
8 | | use super::{ErrorKind, InvalidUri, Port, URI_CHARS}; |
9 | | use crate::byte_str::ByteStr; |
10 | | |
11 | | /// Represents the authority component of a URI. |
12 | | #[derive(Clone)] |
13 | | pub struct Authority { |
14 | | pub(super) data: ByteStr, |
15 | | } |
16 | | |
17 | | impl Authority { |
18 | 6.35k | pub(super) fn empty() -> Self { |
19 | 6.35k | Authority { |
20 | 6.35k | data: ByteStr::new(), |
21 | 6.35k | } |
22 | 6.35k | } |
23 | | |
24 | | // Not public while `bytes` is unstable. |
25 | 1.09k | pub(super) fn from_shared(s: Bytes) -> Result<Self, InvalidUri> { |
26 | | // Precondition on create_authority: trivially satisfied by the |
27 | | // identity closure |
28 | 1.09k | create_authority(s, |s| s) |
29 | 1.09k | } |
30 | | |
31 | | /// Attempt to convert an `Authority` from a static string. |
32 | | /// |
33 | | /// This function will not perform any copying, and the string will be |
34 | | /// checked if it is empty or contains an invalid character. |
35 | | /// |
36 | | /// # Panics |
37 | | /// |
38 | | /// This function panics if the argument contains invalid characters or |
39 | | /// is empty. |
40 | | /// |
41 | | /// # Examples |
42 | | /// |
43 | | /// ``` |
44 | | /// # use http::uri::Authority; |
45 | | /// let authority = Authority::from_static("example.com"); |
46 | | /// assert_eq!(authority.host(), "example.com"); |
47 | | /// ``` |
48 | 0 | pub fn from_static(src: &'static str) -> Self { |
49 | 0 | Authority::from_shared(Bytes::from_static(src.as_bytes())) |
50 | 0 | .expect("static str is not valid authority") |
51 | 0 | } |
52 | | |
53 | | /// Attempt to convert a `Bytes` buffer to a `Authority`. |
54 | | /// |
55 | | /// This will try to prevent a copy if the type passed is the type used |
56 | | /// internally, and will copy the data if it is not. |
57 | | pub fn from_maybe_shared<T>(src: T) -> Result<Self, InvalidUri> |
58 | | where |
59 | | T: AsRef<[u8]> + 'static, |
60 | | { |
61 | | if_downcast_into!(T, Bytes, src, { |
62 | | return Authority::from_shared(src); |
63 | | }); |
64 | | |
65 | | Authority::try_from(src.as_ref()) |
66 | | } |
67 | | |
68 | | // Note: this may return an *empty* Authority. You might want `parse_non_empty`. |
69 | | // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where |
70 | | // ret is the return value. |
71 | 1.56k | pub(super) fn parse(s: &[u8]) -> Result<usize, InvalidUri> { |
72 | 1.56k | let mut colon_cnt = 0u32; |
73 | 1.56k | let mut start_bracket = false; |
74 | 1.56k | let mut end_bracket = false; |
75 | 1.56k | let mut has_percent = false; |
76 | 1.56k | let mut end = s.len(); |
77 | 1.56k | let mut at_sign_pos = None; |
78 | | const MAX_COLONS: u32 = 8; // e.g., [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80 |
79 | | |
80 | | // Among other things, this loop checks that every byte in s up to the |
81 | | // first '/', '?', or '#' is a valid URI character (or in some contexts, |
82 | | // a '%'). This means that each such byte is a valid single-byte UTF-8 |
83 | | // code point. |
84 | 501k | for (i, &b) in s.iter().enumerate() { |
85 | 501k | match URI_CHARS[b as usize] { |
86 | | b'/' | b'?' | b'#' => { |
87 | 124 | end = i; |
88 | 124 | break; |
89 | | } |
90 | | b':' => { |
91 | 385 | if colon_cnt >= MAX_COLONS { |
92 | 2 | return Err(ErrorKind::InvalidAuthority.into()); |
93 | 383 | } |
94 | 383 | colon_cnt += 1; |
95 | | } |
96 | | b'[' => { |
97 | 19 | if has_percent || start_bracket { |
98 | | // Something other than the userinfo has a `%`, so reject it. |
99 | 4 | return Err(ErrorKind::InvalidAuthority.into()); |
100 | 15 | } |
101 | 15 | start_bracket = true; |
102 | | } |
103 | | b']' => { |
104 | 6 | if (!start_bracket) || end_bracket { |
105 | 3 | return Err(ErrorKind::InvalidAuthority.into()); |
106 | 3 | } |
107 | 3 | end_bracket = true; |
108 | | |
109 | | // Those were part of an IPv6 hostname, so forget them... |
110 | 3 | colon_cnt = 0; |
111 | 3 | has_percent = false; |
112 | | } |
113 | 377 | b'@' => { |
114 | 377 | at_sign_pos = Some(i); |
115 | 377 | |
116 | 377 | // Those weren't a port colon, but part of the |
117 | 377 | // userinfo, so it needs to be forgotten. |
118 | 377 | colon_cnt = 0; |
119 | 377 | has_percent = false; |
120 | 377 | } |
121 | 487 | 0 if b == b'%' => { |
122 | 210 | // Per https://tools.ietf.org/html/rfc3986#section-3.2.1 and |
123 | 210 | // https://url.spec.whatwg.org/#authority-state |
124 | 210 | // the userinfo can have a percent-encoded username and password, |
125 | 210 | // so record that a `%` was found. If this turns out to be |
126 | 210 | // part of the userinfo, this flag will be cleared. |
127 | 210 | // Also per https://tools.ietf.org/html/rfc6874, percent-encoding can |
128 | 210 | // be used to indicate a zone identifier. |
129 | 210 | // If the flag hasn't been cleared at the end, that means this |
130 | 210 | // was part of the hostname (and not part of an IPv6 address), and |
131 | 210 | // will fail with an error. |
132 | 210 | has_percent = true; |
133 | 210 | } |
134 | | 0 => { |
135 | 277 | return Err(ErrorKind::InvalidUriChar.into()); |
136 | | } |
137 | 500k | _ => {} |
138 | | } |
139 | | } |
140 | | |
141 | 1.27k | if start_bracket ^ end_bracket { |
142 | 2 | return Err(ErrorKind::InvalidAuthority.into()); |
143 | 1.27k | } |
144 | | |
145 | 1.27k | if colon_cnt > 1 { |
146 | | // Things like 'localhost:8080:3030' are rejected. |
147 | 10 | return Err(ErrorKind::InvalidAuthority.into()); |
148 | 1.26k | } |
149 | | |
150 | 1.26k | if end > 0 && at_sign_pos == Some(end - 1) { |
151 | | // If there's nothing after an `@`, this is bonkers. |
152 | 9 | return Err(ErrorKind::InvalidAuthority.into()); |
153 | 1.25k | } |
154 | | |
155 | 1.25k | if has_percent { |
156 | | // Something after the userinfo has a `%`, so reject it. |
157 | 9 | return Err(ErrorKind::InvalidAuthority.into()); |
158 | 1.24k | } |
159 | | |
160 | 1.24k | Ok(end) |
161 | 1.56k | } |
162 | | |
163 | | // Parse bytes as an Authority, not allowing an empty string. |
164 | | // |
165 | | // This should be used by functions that allow a user to parse |
166 | | // an `Authority` by itself. |
167 | | // |
168 | | // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where |
169 | | // ret is the return value. |
170 | 1.09k | fn parse_non_empty(s: &[u8]) -> Result<usize, InvalidUri> { |
171 | 1.09k | if s.is_empty() { |
172 | 0 | return Err(ErrorKind::Empty.into()); |
173 | 1.09k | } |
174 | 1.09k | Authority::parse(s) |
175 | 1.09k | } |
176 | | |
177 | | /// Get the host of this `Authority`. |
178 | | /// |
179 | | /// The host subcomponent of authority is identified by an IP literal |
180 | | /// encapsulated within square brackets, an IPv4 address in dotted- decimal |
181 | | /// form, or a registered name. The host subcomponent is **case-insensitive**. |
182 | | /// |
183 | | /// ```notrust |
184 | | /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 |
185 | | /// |---------| |
186 | | /// | |
187 | | /// host |
188 | | /// ``` |
189 | | /// |
190 | | /// # Examples |
191 | | /// |
192 | | /// ``` |
193 | | /// # use http::uri::*; |
194 | | /// let authority: Authority = "example.org:80".parse().unwrap(); |
195 | | /// |
196 | | /// assert_eq!(authority.host(), "example.org"); |
197 | | /// ``` |
198 | | #[inline] |
199 | | pub fn host(&self) -> &str { |
200 | | host(self.as_str()) |
201 | | } |
202 | | |
203 | | /// Get the port part of this `Authority`. |
204 | | /// |
205 | | /// The port subcomponent of authority is designated by an optional port |
206 | | /// number following the host and delimited from it by a single colon (":") |
207 | | /// character. It can be turned into a decimal port number with the `as_u16` |
208 | | /// method or as a `str` with the `as_str` method. |
209 | | /// |
210 | | /// ```notrust |
211 | | /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 |
212 | | /// |-| |
213 | | /// | |
214 | | /// port |
215 | | /// ``` |
216 | | /// |
217 | | /// # Examples |
218 | | /// |
219 | | /// Authority with port |
220 | | /// |
221 | | /// ``` |
222 | | /// # use http::uri::Authority; |
223 | | /// let authority: Authority = "example.org:80".parse().unwrap(); |
224 | | /// |
225 | | /// let port = authority.port().unwrap(); |
226 | | /// assert_eq!(port.as_u16(), 80); |
227 | | /// assert_eq!(port.as_str(), "80"); |
228 | | /// ``` |
229 | | /// |
230 | | /// Authority without port |
231 | | /// |
232 | | /// ``` |
233 | | /// # use http::uri::Authority; |
234 | | /// let authority: Authority = "example.org".parse().unwrap(); |
235 | | /// |
236 | | /// assert!(authority.port().is_none()); |
237 | | /// ``` |
238 | 0 | pub fn port(&self) -> Option<Port<&str>> { |
239 | 0 | let bytes = self.as_str(); |
240 | 0 | bytes |
241 | 0 | .rfind(':') |
242 | 0 | .and_then(|i| Port::from_str(&bytes[i + 1..]).ok()) |
243 | 0 | } |
244 | | |
245 | | /// Get the port of this `Authority` as a `u16`. |
246 | | /// |
247 | | /// # Example |
248 | | /// |
249 | | /// ``` |
250 | | /// # use http::uri::Authority; |
251 | | /// let authority: Authority = "example.org:80".parse().unwrap(); |
252 | | /// |
253 | | /// assert_eq!(authority.port_u16(), Some(80)); |
254 | | /// ``` |
255 | 0 | pub fn port_u16(&self) -> Option<u16> { |
256 | 0 | self.port().map(|p| p.as_u16()) |
257 | 0 | } |
258 | | |
259 | | /// Return a str representation of the authority |
260 | | #[inline] |
261 | 0 | pub fn as_str(&self) -> &str { |
262 | 0 | &self.data[..] |
263 | 0 | } |
264 | | } |
265 | | |
266 | | // Purposefully not public while `bytes` is unstable. |
267 | | // impl TryFrom<Bytes> for Authority |
268 | | |
269 | | impl AsRef<str> for Authority { |
270 | 0 | fn as_ref(&self) -> &str { |
271 | 0 | self.as_str() |
272 | 0 | } |
273 | | } |
274 | | |
275 | | impl PartialEq for Authority { |
276 | 0 | fn eq(&self, other: &Authority) -> bool { |
277 | 0 | self.data.eq_ignore_ascii_case(&other.data) |
278 | 0 | } |
279 | | } |
280 | | |
281 | | impl Eq for Authority {} |
282 | | |
283 | | /// Case-insensitive equality |
284 | | /// |
285 | | /// # Examples |
286 | | /// |
287 | | /// ``` |
288 | | /// # use http::uri::Authority; |
289 | | /// let authority: Authority = "HELLO.com".parse().unwrap(); |
290 | | /// assert_eq!(authority, "hello.coM"); |
291 | | /// assert_eq!("hello.com", authority); |
292 | | /// ``` |
293 | | impl PartialEq<str> for Authority { |
294 | 0 | fn eq(&self, other: &str) -> bool { |
295 | 0 | self.data.eq_ignore_ascii_case(other) |
296 | 0 | } |
297 | | } |
298 | | |
299 | | impl PartialEq<Authority> for str { |
300 | 0 | fn eq(&self, other: &Authority) -> bool { |
301 | 0 | self.eq_ignore_ascii_case(other.as_str()) |
302 | 0 | } |
303 | | } |
304 | | |
305 | | impl<'a> PartialEq<Authority> for &'a str { |
306 | 0 | fn eq(&self, other: &Authority) -> bool { |
307 | 0 | self.eq_ignore_ascii_case(other.as_str()) |
308 | 0 | } |
309 | | } |
310 | | |
311 | | impl<'a> PartialEq<&'a str> for Authority { |
312 | 0 | fn eq(&self, other: &&'a str) -> bool { |
313 | 0 | self.data.eq_ignore_ascii_case(other) |
314 | 0 | } |
315 | | } |
316 | | |
317 | | impl PartialEq<String> for Authority { |
318 | 0 | fn eq(&self, other: &String) -> bool { |
319 | 0 | self.data.eq_ignore_ascii_case(other.as_str()) |
320 | 0 | } |
321 | | } |
322 | | |
323 | | impl PartialEq<Authority> for String { |
324 | 0 | fn eq(&self, other: &Authority) -> bool { |
325 | 0 | self.as_str().eq_ignore_ascii_case(other.as_str()) |
326 | 0 | } |
327 | | } |
328 | | |
329 | | /// Case-insensitive ordering |
330 | | /// |
331 | | /// # Examples |
332 | | /// |
333 | | /// ``` |
334 | | /// # use http::uri::Authority; |
335 | | /// let authority: Authority = "DEF.com".parse().unwrap(); |
336 | | /// assert!(authority < "ghi.com"); |
337 | | /// assert!(authority > "abc.com"); |
338 | | /// ``` |
339 | | impl PartialOrd for Authority { |
340 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
341 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
342 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
343 | 0 | left.partial_cmp(right) |
344 | 0 | } |
345 | | } |
346 | | |
347 | | impl PartialOrd<str> for Authority { |
348 | 0 | fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { |
349 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
350 | 0 | let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
351 | 0 | left.partial_cmp(right) |
352 | 0 | } |
353 | | } |
354 | | |
355 | | impl PartialOrd<Authority> for str { |
356 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
357 | 0 | let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
358 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
359 | 0 | left.partial_cmp(right) |
360 | 0 | } |
361 | | } |
362 | | |
363 | | impl<'a> PartialOrd<Authority> for &'a str { |
364 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
365 | 0 | let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
366 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
367 | 0 | left.partial_cmp(right) |
368 | 0 | } |
369 | | } |
370 | | |
371 | | impl<'a> PartialOrd<&'a str> for Authority { |
372 | 0 | fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> { |
373 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
374 | 0 | let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
375 | 0 | left.partial_cmp(right) |
376 | 0 | } |
377 | | } |
378 | | |
379 | | impl PartialOrd<String> for Authority { |
380 | 0 | fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { |
381 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
382 | 0 | let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
383 | 0 | left.partial_cmp(right) |
384 | 0 | } |
385 | | } |
386 | | |
387 | | impl PartialOrd<Authority> for String { |
388 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
389 | 0 | let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
390 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
391 | 0 | left.partial_cmp(right) |
392 | 0 | } |
393 | | } |
394 | | |
395 | | /// Case-insensitive hashing |
396 | | /// |
397 | | /// # Examples |
398 | | /// |
399 | | /// ``` |
400 | | /// # use http::uri::Authority; |
401 | | /// # use std::hash::{Hash, Hasher}; |
402 | | /// # use std::collections::hash_map::DefaultHasher; |
403 | | /// |
404 | | /// let a: Authority = "HELLO.com".parse().unwrap(); |
405 | | /// let b: Authority = "hello.coM".parse().unwrap(); |
406 | | /// |
407 | | /// let mut s = DefaultHasher::new(); |
408 | | /// a.hash(&mut s); |
409 | | /// let a = s.finish(); |
410 | | /// |
411 | | /// let mut s = DefaultHasher::new(); |
412 | | /// b.hash(&mut s); |
413 | | /// let b = s.finish(); |
414 | | /// |
415 | | /// assert_eq!(a, b); |
416 | | /// ``` |
417 | | impl Hash for Authority { |
418 | | fn hash<H>(&self, state: &mut H) |
419 | | where |
420 | | H: Hasher, |
421 | | { |
422 | | self.data.len().hash(state); |
423 | | for &b in self.data.as_bytes() { |
424 | | state.write_u8(b.to_ascii_lowercase()); |
425 | | } |
426 | | } |
427 | | } |
428 | | |
429 | | impl<'a> TryFrom<&'a [u8]> for Authority { |
430 | | type Error = InvalidUri; |
431 | | #[inline] |
432 | 0 | fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> { |
433 | | // parse first, and only turn into Bytes if valid |
434 | | |
435 | | // Preconditon on create_authority: copy_from_slice() copies all of |
436 | | // bytes from the [u8] parameter into a new Bytes |
437 | 0 | create_authority(s, Bytes::copy_from_slice) |
438 | 0 | } |
439 | | } |
440 | | |
441 | | impl<'a> TryFrom<&'a str> for Authority { |
442 | | type Error = InvalidUri; |
443 | | #[inline] |
444 | 0 | fn try_from(s: &'a str) -> Result<Self, Self::Error> { |
445 | 0 | TryFrom::try_from(s.as_bytes()) |
446 | 0 | } |
447 | | } |
448 | | |
449 | | impl TryFrom<Vec<u8>> for Authority { |
450 | | type Error = InvalidUri; |
451 | | |
452 | | #[inline] |
453 | | fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> { |
454 | | Authority::from_shared(vec.into()) |
455 | | } |
456 | | } |
457 | | |
458 | | impl TryFrom<String> for Authority { |
459 | | type Error = InvalidUri; |
460 | | |
461 | | #[inline] |
462 | | fn try_from(t: String) -> Result<Self, Self::Error> { |
463 | | Authority::from_shared(t.into()) |
464 | | } |
465 | | } |
466 | | |
467 | | impl FromStr for Authority { |
468 | | type Err = InvalidUri; |
469 | | |
470 | 0 | fn from_str(s: &str) -> Result<Self, InvalidUri> { |
471 | 0 | TryFrom::try_from(s) |
472 | 0 | } |
473 | | } |
474 | | |
475 | | impl fmt::Debug for Authority { |
476 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
477 | 0 | f.write_str(self.as_str()) |
478 | 0 | } |
479 | | } |
480 | | |
481 | | impl fmt::Display for Authority { |
482 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
483 | 0 | f.write_str(self.as_str()) |
484 | 0 | } |
485 | | } |
486 | | |
487 | 0 | fn host(auth: &str) -> &str { |
488 | 0 | let host_port = auth |
489 | 0 | .rsplit('@') |
490 | 0 | .next() |
491 | 0 | .expect("split always has at least 1 item"); |
492 | | |
493 | 0 | if host_port.as_bytes()[0] == b'[' { |
494 | 0 | let i = host_port |
495 | 0 | .find(']') |
496 | 0 | .expect("parsing should validate brackets"); |
497 | | // ..= ranges aren't available in 1.20, our minimum Rust version... |
498 | 0 | &host_port[0..i + 1] |
499 | | } else { |
500 | 0 | host_port |
501 | 0 | .split(':') |
502 | 0 | .next() |
503 | 0 | .expect("split always has at least 1 item") |
504 | | } |
505 | 0 | } |
506 | | |
507 | | // Precondition: f converts all of the bytes in the passed in B into the |
508 | | // returned Bytes. |
509 | 1.09k | fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri> |
510 | 1.09k | where |
511 | 1.09k | B: AsRef<[u8]>, |
512 | 1.09k | F: FnOnce(B) -> Bytes, |
513 | | { |
514 | 1.09k | let s = b.as_ref(); |
515 | 1.09k | let authority_end = Authority::parse_non_empty(s)?; |
516 | | |
517 | 1.06k | if authority_end != s.len() { |
518 | 2 | return Err(ErrorKind::InvalidUriChar.into()); |
519 | 1.06k | } |
520 | | |
521 | 1.06k | let bytes = f(b); |
522 | | |
523 | 1.06k | Ok(Authority { |
524 | 1.06k | // Safety: the postcondition on parse_non_empty() and the check against |
525 | 1.06k | // s.len() ensure that b is valid UTF-8. The precondition on f ensures |
526 | 1.06k | // that this is carried through to bytes. |
527 | 1.06k | data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, |
528 | 1.06k | }) |
529 | 1.09k | } http::uri::authority::create_authority::<bytes::bytes::Bytes, <http::uri::authority::Authority>::from_shared::{closure#0}> Line | Count | Source | 509 | 1.09k | fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri> | 510 | 1.09k | where | 511 | 1.09k | B: AsRef<[u8]>, | 512 | 1.09k | F: FnOnce(B) -> Bytes, | 513 | | { | 514 | 1.09k | let s = b.as_ref(); | 515 | 1.09k | let authority_end = Authority::parse_non_empty(s)?; | 516 | | | 517 | 1.06k | if authority_end != s.len() { | 518 | 2 | return Err(ErrorKind::InvalidUriChar.into()); | 519 | 1.06k | } | 520 | | | 521 | 1.06k | let bytes = f(b); | 522 | | | 523 | 1.06k | Ok(Authority { | 524 | 1.06k | // Safety: the postcondition on parse_non_empty() and the check against | 525 | 1.06k | // s.len() ensure that b is valid UTF-8. The precondition on f ensures | 526 | 1.06k | // that this is carried through to bytes. | 527 | 1.06k | data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, | 528 | 1.06k | }) | 529 | 1.09k | } |
Unexecuted instantiation: http::uri::authority::create_authority::<&[u8], <bytes::bytes::Bytes>::copy_from_slice> |
530 | | |
531 | | #[cfg(test)] |
532 | | mod tests { |
533 | | use super::*; |
534 | | |
535 | | #[test] |
536 | | fn parse_empty_string_is_error() { |
537 | | let err = Authority::parse_non_empty(b"").unwrap_err(); |
538 | | assert_eq!(err.0, ErrorKind::Empty); |
539 | | } |
540 | | |
541 | | #[test] |
542 | | fn equal_to_self_of_same_authority() { |
543 | | let authority1: Authority = "example.com".parse().unwrap(); |
544 | | let authority2: Authority = "EXAMPLE.COM".parse().unwrap(); |
545 | | assert_eq!(authority1, authority2); |
546 | | assert_eq!(authority2, authority1); |
547 | | } |
548 | | |
549 | | #[test] |
550 | | fn not_equal_to_self_of_different_authority() { |
551 | | let authority1: Authority = "example.com".parse().unwrap(); |
552 | | let authority2: Authority = "test.com".parse().unwrap(); |
553 | | assert_ne!(authority1, authority2); |
554 | | assert_ne!(authority2, authority1); |
555 | | } |
556 | | |
557 | | #[test] |
558 | | fn equates_with_a_str() { |
559 | | let authority: Authority = "example.com".parse().unwrap(); |
560 | | assert_eq!(&authority, "EXAMPLE.com"); |
561 | | assert_eq!("EXAMPLE.com", &authority); |
562 | | assert_eq!(authority, "EXAMPLE.com"); |
563 | | assert_eq!("EXAMPLE.com", authority); |
564 | | } |
565 | | |
566 | | #[test] |
567 | | fn from_static_equates_with_a_str() { |
568 | | let authority = Authority::from_static("example.com"); |
569 | | assert_eq!(authority, "example.com"); |
570 | | } |
571 | | |
572 | | #[test] |
573 | | fn not_equal_with_a_str_of_a_different_authority() { |
574 | | let authority: Authority = "example.com".parse().unwrap(); |
575 | | assert_ne!(&authority, "test.com"); |
576 | | assert_ne!("test.com", &authority); |
577 | | assert_ne!(authority, "test.com"); |
578 | | assert_ne!("test.com", authority); |
579 | | } |
580 | | |
581 | | #[test] |
582 | | fn equates_with_a_string() { |
583 | | let authority: Authority = "example.com".parse().unwrap(); |
584 | | assert_eq!(authority, "EXAMPLE.com".to_string()); |
585 | | assert_eq!("EXAMPLE.com".to_string(), authority); |
586 | | } |
587 | | |
588 | | #[test] |
589 | | fn equates_with_a_string_of_a_different_authority() { |
590 | | let authority: Authority = "example.com".parse().unwrap(); |
591 | | assert_ne!(authority, "test.com".to_string()); |
592 | | assert_ne!("test.com".to_string(), authority); |
593 | | } |
594 | | |
595 | | #[test] |
596 | | fn compares_to_self() { |
597 | | let authority1: Authority = "abc.com".parse().unwrap(); |
598 | | let authority2: Authority = "def.com".parse().unwrap(); |
599 | | assert!(authority1 < authority2); |
600 | | assert!(authority2 > authority1); |
601 | | } |
602 | | |
603 | | #[test] |
604 | | fn compares_with_a_str() { |
605 | | let authority: Authority = "def.com".parse().unwrap(); |
606 | | // with ref |
607 | | assert!(&authority < "ghi.com"); |
608 | | assert!("ghi.com" > &authority); |
609 | | assert!(&authority > "abc.com"); |
610 | | assert!("abc.com" < &authority); |
611 | | |
612 | | // no ref |
613 | | assert!(authority < "ghi.com"); |
614 | | assert!("ghi.com" > authority); |
615 | | assert!(authority > "abc.com"); |
616 | | assert!("abc.com" < authority); |
617 | | } |
618 | | |
619 | | #[test] |
620 | | fn compares_with_a_string() { |
621 | | let authority: Authority = "def.com".parse().unwrap(); |
622 | | assert!(authority < "ghi.com".to_string()); |
623 | | assert!("ghi.com".to_string() > authority); |
624 | | assert!(authority > "abc.com".to_string()); |
625 | | assert!("abc.com".to_string() < authority); |
626 | | } |
627 | | |
628 | | #[test] |
629 | | fn allows_percent_in_userinfo() { |
630 | | let authority_str = "a%2f:b%2f@example.com"; |
631 | | let authority: Authority = authority_str.parse().unwrap(); |
632 | | assert_eq!(authority, authority_str); |
633 | | } |
634 | | |
635 | | #[test] |
636 | | fn rejects_percent_in_hostname() { |
637 | | let err = Authority::parse_non_empty(b"example%2f.com").unwrap_err(); |
638 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
639 | | |
640 | | let err = Authority::parse_non_empty(b"a%2f:b%2f@example%2f.com").unwrap_err(); |
641 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
642 | | } |
643 | | |
644 | | #[test] |
645 | | fn allows_percent_in_ipv6_address() { |
646 | | let authority_str = "[fe80::1:2:3:4%25eth0]"; |
647 | | let result: Authority = authority_str.parse().unwrap(); |
648 | | assert_eq!(result, authority_str); |
649 | | } |
650 | | |
651 | | #[test] |
652 | | fn reject_obviously_invalid_ipv6_address() { |
653 | | let err = Authority::parse_non_empty(b"[0:1:2:3:4:5:6:7:8:9:10:11:12:13:14]").unwrap_err(); |
654 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
655 | | } |
656 | | |
657 | | #[test] |
658 | | fn rejects_percent_outside_ipv6_address() { |
659 | | let err = Authority::parse_non_empty(b"1234%20[fe80::1:2:3:4]").unwrap_err(); |
660 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
661 | | |
662 | | let err = Authority::parse_non_empty(b"[fe80::1:2:3:4]%20").unwrap_err(); |
663 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
664 | | } |
665 | | |
666 | | #[test] |
667 | | fn rejects_invalid_utf8() { |
668 | | let err = Authority::try_from([0xc0u8].as_ref()).unwrap_err(); |
669 | | assert_eq!(err.0, ErrorKind::InvalidUriChar); |
670 | | |
671 | | let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref())).unwrap_err(); |
672 | | assert_eq!(err.0, ErrorKind::InvalidUriChar); |
673 | | } |
674 | | |
675 | | #[test] |
676 | | fn rejects_invalid_use_of_brackets() { |
677 | | let err = Authority::parse_non_empty(b"[]@[").unwrap_err(); |
678 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
679 | | |
680 | | // reject tie-fighter |
681 | | let err = Authority::parse_non_empty(b"]o[").unwrap_err(); |
682 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
683 | | } |
684 | | } |