1use std::convert::TryFrom;
2use std::hash::{Hash, Hasher};
3use std::str::FromStr;
4use std::{cmp, fmt, str};
5
6use bytes::Bytes;
7
8use super::{ErrorKind, InvalidUri, Port, URI_CHARS};
9use crate::byte_str::ByteStr;
10
11/// Represents the authority component of a URI.
12#[derive(Clone)]
13pub struct Authority {
14 pub(super) data: ByteStr,
15}
16
17impl 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
265impl AsRef<str> for Authority {
266 fn as_ref(&self) -> &str {
267 self.as_str()
268 }
269}
270
271impl PartialEq for Authority {
272 fn eq(&self, other: &Authority) -> bool {
273 self.data.eq_ignore_ascii_case(&other.data)
274 }
275}
276
277impl 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/// ```
289impl PartialEq<str> for Authority {
290 fn eq(&self, other: &str) -> bool {
291 self.data.eq_ignore_ascii_case(other)
292 }
293}
294
295impl PartialEq<Authority> for str {
296 fn eq(&self, other: &Authority) -> bool {
297 self.eq_ignore_ascii_case(other.as_str())
298 }
299}
300
301impl<'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
307impl<'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
313impl PartialEq<String> for Authority {
314 fn eq(&self, other: &String) -> bool {
315 self.data.eq_ignore_ascii_case(other.as_str())
316 }
317}
318
319impl 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/// ```
335impl 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
343impl 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
351impl 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
359impl<'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
367impl<'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
375impl 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
383impl 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/// ```
413impl 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
425impl<'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
437impl<'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
445impl 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
454impl 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
463impl 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
471impl fmt::Debug for Authority {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 f.write_str(self.as_str())
474 }
475}
476
477impl fmt::Display for Authority {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 f.write_str(self.as_str())
480 }
481}
482
483fn 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.
505fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri>
506where
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)]
528mod 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