http/uri/
authority.rs

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 closure
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().map(|p| 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
269impl AsRef<str> for Authority {
270    fn as_ref(&self) -> &str {
271        self.as_str()
272    }
273}
274
275impl PartialEq for Authority {
276    fn eq(&self, other: &Authority) -> bool {
277        self.data.eq_ignore_ascii_case(&other.data)
278    }
279}
280
281impl 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/// ```
293impl PartialEq<str> for Authority {
294    fn eq(&self, other: &str) -> bool {
295        self.data.eq_ignore_ascii_case(other)
296    }
297}
298
299impl PartialEq<Authority> for str {
300    fn eq(&self, other: &Authority) -> bool {
301        self.eq_ignore_ascii_case(other.as_str())
302    }
303}
304
305impl<'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
311impl<'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
317impl PartialEq<String> for Authority {
318    fn eq(&self, other: &String) -> bool {
319        self.data.eq_ignore_ascii_case(other.as_str())
320    }
321}
322
323impl 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/// ```
339impl PartialOrd for Authority {
340    fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> {
341        let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
342        let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
343        left.partial_cmp(right)
344    }
345}
346
347impl PartialOrd<str> for Authority {
348    fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
349        let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
350        let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase());
351        left.partial_cmp(right)
352    }
353}
354
355impl PartialOrd<Authority> for str {
356    fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> {
357        let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase());
358        let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
359        left.partial_cmp(right)
360    }
361}
362
363impl<'a> PartialOrd<Authority> for &'a str {
364    fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> {
365        let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase());
366        let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
367        left.partial_cmp(right)
368    }
369}
370
371impl<'a> PartialOrd<&'a str> for Authority {
372    fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> {
373        let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
374        let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase());
375        left.partial_cmp(right)
376    }
377}
378
379impl PartialOrd<String> for Authority {
380    fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
381        let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
382        let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase());
383        left.partial_cmp(right)
384    }
385}
386
387impl PartialOrd<Authority> for String {
388    fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> {
389        let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase());
390        let right = other.data.as_bytes().iter().map(|b| 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/// ```
417impl 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
429impl<'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(s, Bytes::copy_from_slice)
438    }
439}
440
441impl<'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
449impl 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
458impl 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
467impl 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
475impl fmt::Debug for Authority {
476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477        f.write_str(self.as_str())
478    }
479}
480
481impl fmt::Display for Authority {
482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483        f.write_str(self.as_str())
484    }
485}
486
487fn host(auth: &str) -> &str {
488    let host_port = auth
489        .rsplit('@')
490        .next()
491        .expect("split always has at least 1 item");
492
493    if host_port.as_bytes()[0] == b'[' {
494        let i = host_port
495            .find(']')
496            .expect("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("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.
509fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri>
510where
511    B: AsRef<[u8]>,
512    F: FnOnce(B) -> Bytes,
513{
514    let s = b.as_ref();
515    let authority_end = Authority::parse_non_empty(s)?;
516
517    if authority_end != s.len() {
518        return Err(ErrorKind::InvalidUriChar.into());
519    }
520
521    let 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)]
532mod 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}