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