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 | pub(super) fn empty() -> Self { |
19 | Authority { |
20 | data: ByteStr::new(), |
21 | } |
22 | } |
23 | |
24 | // Not public while `bytes` is unstable. |
25 | pub(super) fn from_shared(s: Bytes) -> Result<Self, InvalidUri> { |
26 | // Precondition on create_authority: trivially satisfied by the |
27 | // identity clousre |
28 | create_authority(s, |s| s) |
29 | } |
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 | pub fn from_static(src: &'static str) -> Self { |
49 | Authority::from_shared(Bytes::from_static(src.as_bytes())) |
50 | .expect("static str is not valid authority" ) |
51 | } |
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 | pub(super) fn parse(s: &[u8]) -> Result<usize, InvalidUri> { |
72 | let mut colon_cnt = 0u32; |
73 | let mut start_bracket = false; |
74 | let mut end_bracket = false; |
75 | let mut has_percent = false; |
76 | let mut end = s.len(); |
77 | 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 | for (i, &b) in s.iter().enumerate() { |
85 | match URI_CHARS[b as usize] { |
86 | b'/' | b'?' | b'#' => { |
87 | end = i; |
88 | break; |
89 | } |
90 | b':' => { |
91 | if colon_cnt >= MAX_COLONS { |
92 | return Err(ErrorKind::InvalidAuthority.into()); |
93 | } |
94 | colon_cnt += 1; |
95 | } |
96 | b'[' => { |
97 | if has_percent || start_bracket { |
98 | // Something other than the userinfo has a `%`, so reject it. |
99 | return Err(ErrorKind::InvalidAuthority.into()); |
100 | } |
101 | start_bracket = true; |
102 | } |
103 | b']' => { |
104 | if (!start_bracket) || end_bracket { |
105 | return Err(ErrorKind::InvalidAuthority.into()); |
106 | } |
107 | end_bracket = true; |
108 | |
109 | // Those were part of an IPv6 hostname, so forget them... |
110 | colon_cnt = 0; |
111 | has_percent = false; |
112 | } |
113 | b'@' => { |
114 | at_sign_pos = Some(i); |
115 | |
116 | // Those weren't a port colon, but part of the |
117 | // userinfo, so it needs to be forgotten. |
118 | colon_cnt = 0; |
119 | has_percent = false; |
120 | } |
121 | 0 if b == b'%' => { |
122 | // Per https://tools.ietf.org/html/rfc3986#section-3.2.1 and |
123 | // https://url.spec.whatwg.org/#authority-state |
124 | // the userinfo can have a percent-encoded username and password, |
125 | // so record that a `%` was found. If this turns out to be |
126 | // part of the userinfo, this flag will be cleared. |
127 | // Also per https://tools.ietf.org/html/rfc6874, percent-encoding can |
128 | // be used to indicate a zone identifier. |
129 | // If the flag hasn't been cleared at the end, that means this |
130 | // was part of the hostname (and not part of an IPv6 address), and |
131 | // will fail with an error. |
132 | has_percent = true; |
133 | } |
134 | 0 => { |
135 | return Err(ErrorKind::InvalidUriChar.into()); |
136 | } |
137 | _ => {} |
138 | } |
139 | } |
140 | |
141 | if start_bracket ^ end_bracket { |
142 | return Err(ErrorKind::InvalidAuthority.into()); |
143 | } |
144 | |
145 | if colon_cnt > 1 { |
146 | // Things like 'localhost:8080:3030' are rejected. |
147 | return Err(ErrorKind::InvalidAuthority.into()); |
148 | } |
149 | |
150 | if end > 0 && at_sign_pos == Some(end - 1) { |
151 | // If there's nothing after an `@`, this is bonkers. |
152 | return Err(ErrorKind::InvalidAuthority.into()); |
153 | } |
154 | |
155 | if has_percent { |
156 | // Something after the userinfo has a `%`, so reject it. |
157 | return Err(ErrorKind::InvalidAuthority.into()); |
158 | } |
159 | |
160 | Ok(end) |
161 | } |
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 | fn parse_non_empty(s: &[u8]) -> Result<usize, InvalidUri> { |
171 | if s.is_empty() { |
172 | return Err(ErrorKind::Empty.into()); |
173 | } |
174 | Authority::parse(s) |
175 | } |
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 | pub fn port(&self) -> Option<Port<&str>> { |
239 | let bytes = self.as_str(); |
240 | bytes |
241 | .rfind(":" ) |
242 | .and_then(|i| Port::from_str(&bytes[i + 1..]).ok()) |
243 | } |
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 | pub fn port_u16(&self) -> Option<u16> { |
256 | self.port().and_then(|p| Some(p.as_u16())) |
257 | } |
258 | |
259 | /// Return a str representation of the authority |
260 | #[inline ] |
261 | pub fn as_str(&self) -> &str { |
262 | &self.data[..] |
263 | } |
264 | } |
265 | |
266 | // Purposefully not public while `bytes` is unstable. |
267 | // impl TryFrom<Bytes> for Authority |
268 | |
269 | impl AsRef<str> for Authority { |
270 | fn as_ref(&self) -> &str { |
271 | self.as_str() |
272 | } |
273 | } |
274 | |
275 | impl PartialEq for Authority { |
276 | fn eq(&self, other: &Authority) -> bool { |
277 | self.data.eq_ignore_ascii_case(&other.data) |
278 | } |
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 | fn eq(&self, other: &str) -> bool { |
295 | self.data.eq_ignore_ascii_case(other) |
296 | } |
297 | } |
298 | |
299 | impl PartialEq<Authority> for str { |
300 | fn eq(&self, other: &Authority) -> bool { |
301 | self.eq_ignore_ascii_case(other.as_str()) |
302 | } |
303 | } |
304 | |
305 | impl<'a> PartialEq<Authority> for &'a str { |
306 | fn eq(&self, other: &Authority) -> bool { |
307 | self.eq_ignore_ascii_case(other.as_str()) |
308 | } |
309 | } |
310 | |
311 | impl<'a> PartialEq<&'a str> for Authority { |
312 | fn eq(&self, other: &&'a str) -> bool { |
313 | self.data.eq_ignore_ascii_case(other) |
314 | } |
315 | } |
316 | |
317 | impl PartialEq<String> for Authority { |
318 | fn eq(&self, other: &String) -> bool { |
319 | self.data.eq_ignore_ascii_case(other.as_str()) |
320 | } |
321 | } |
322 | |
323 | impl PartialEq<Authority> for String { |
324 | fn eq(&self, other: &Authority) -> bool { |
325 | self.as_str().eq_ignore_ascii_case(other.as_str()) |
326 | } |
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 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
341 | let left: impl Iterator = self.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
342 | let right: impl Iterator = other.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
343 | left.partial_cmp(right) |
344 | } |
345 | } |
346 | |
347 | impl PartialOrd<str> for Authority { |
348 | fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { |
349 | let left: impl Iterator = self.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
350 | let right: impl Iterator = other.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
351 | left.partial_cmp(right) |
352 | } |
353 | } |
354 | |
355 | impl PartialOrd<Authority> for str { |
356 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
357 | let left: impl Iterator = self.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
358 | let right: impl Iterator = other.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
359 | left.partial_cmp(right) |
360 | } |
361 | } |
362 | |
363 | impl<'a> PartialOrd<Authority> for &'a str { |
364 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
365 | let left: impl Iterator = self.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
366 | let right: impl Iterator = other.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
367 | left.partial_cmp(right) |
368 | } |
369 | } |
370 | |
371 | impl<'a> PartialOrd<&'a str> for Authority { |
372 | fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> { |
373 | let left: impl Iterator = self.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
374 | let right: impl Iterator = other.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
375 | left.partial_cmp(right) |
376 | } |
377 | } |
378 | |
379 | impl PartialOrd<String> for Authority { |
380 | fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { |
381 | let left: impl Iterator = self.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
382 | let right: impl Iterator = other.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
383 | left.partial_cmp(right) |
384 | } |
385 | } |
386 | |
387 | impl PartialOrd<Authority> for String { |
388 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
389 | let left: impl Iterator = self.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
390 | let right: impl Iterator = other.data.as_bytes().iter().map(|b: &u8| b.to_ascii_lowercase()); |
391 | left.partial_cmp(right) |
392 | } |
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: u8 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 | 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 | create_authority(b:s, |s: &[u8]| Bytes::copy_from_slice(data:s)) |
438 | } |
439 | } |
440 | |
441 | impl<'a> TryFrom<&'a str> for Authority { |
442 | type Error = InvalidUri; |
443 | #[inline ] |
444 | fn try_from(s: &'a str) -> Result<Self, Self::Error> { |
445 | TryFrom::try_from(s.as_bytes()) |
446 | } |
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 | fn from_str(s: &str) -> Result<Self, InvalidUri> { |
471 | TryFrom::try_from(s) |
472 | } |
473 | } |
474 | |
475 | impl fmt::Debug for Authority { |
476 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
477 | f.write_str(self.as_str()) |
478 | } |
479 | } |
480 | |
481 | impl fmt::Display for Authority { |
482 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
483 | f.write_str(self.as_str()) |
484 | } |
485 | } |
486 | |
487 | fn host(auth: &str) -> &str { |
488 | let host_port: &str = auth |
489 | .rsplitn(2, '@' ) |
490 | .next() |
491 | .expect(msg:"split always has at least 1 item" ); |
492 | |
493 | if host_port.as_bytes()[0] == b'[' { |
494 | let i: usize = host_port |
495 | .find(']' ) |
496 | .expect(msg:"parsing should validate brackets" ); |
497 | // ..= ranges aren't available in 1.20, our minimum Rust version... |
498 | &host_port[0..i + 1] |
499 | } else { |
500 | host_port |
501 | .split(':' ) |
502 | .next() |
503 | .expect(msg:"split always has at least 1 item" ) |
504 | } |
505 | } |
506 | |
507 | // Precondition: f converts all of the bytes in the passed in B into the |
508 | // returned Bytes. |
509 | fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri> |
510 | where |
511 | B: AsRef<[u8]>, |
512 | F: FnOnce(B) -> Bytes, |
513 | { |
514 | let s: &[u8] = b.as_ref(); |
515 | let authority_end: usize = Authority::parse_non_empty(s)?; |
516 | |
517 | if authority_end != s.len() { |
518 | return Err(ErrorKind::InvalidUriChar.into()); |
519 | } |
520 | |
521 | let bytes: Bytes = f(b); |
522 | |
523 | Ok(Authority { |
524 | // Safety: the postcondition on parse_non_empty() and the check against |
525 | // s.len() ensure that b is valid UTF-8. The precondition on f ensures |
526 | // that this is carried through to bytes. |
527 | data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, |
528 | }) |
529 | } |
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 | } |
685 | |