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 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 = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
338        let right = other.data.as_bytes().iter().map(|b| 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 = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
346        let right = other.as_bytes().iter().map(|b| 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 = self.as_bytes().iter().map(|b| b.to_ascii_lowercase());
354        let right = other.data.as_bytes().iter().map(|b| 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 = self.as_bytes().iter().map(|b| b.to_ascii_lowercase());
362        let right = other.data.as_bytes().iter().map(|b| 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 = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
370        let right = other.as_bytes().iter().map(|b| 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 = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase());
378        let right = other.as_bytes().iter().map(|b| 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 = self.as_bytes().iter().map(|b| b.to_ascii_lowercase());
386        let right = other.data.as_bytes().iter().map(|b| 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 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(s, |s| Bytes::copy_from_slice(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 FromStr for Authority {
446    type Err = InvalidUri;
447
448    fn from_str(s: &str) -> Result<Self, InvalidUri> {
449        TryFrom::try_from(s)
450    }
451}
452
453impl fmt::Debug for Authority {
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        f.write_str(self.as_str())
456    }
457}
458
459impl fmt::Display for Authority {
460    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461        f.write_str(self.as_str())
462    }
463}
464
465fn host(auth: &str) -> &str {
466    let host_port = auth
467        .rsplitn(2, '@')
468        .next()
469        .expect("split always has at least 1 item");
470
471    if host_port.as_bytes()[0] == b'[' {
472        let i = host_port
473            .find(']')
474            .expect("parsing should validate brackets");
475        // ..= ranges aren't available in 1.20, our minimum Rust version...
476        &host_port[0..i + 1]
477    } else {
478        host_port
479            .split(':')
480            .next()
481            .expect("split always has at least 1 item")
482    }
483}
484
485// Precondition: f converts all of the bytes in the passed in B into the
486// returned Bytes.
487fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri>
488where
489    B: AsRef<[u8]>,
490    F: FnOnce(B) -> Bytes,
491{
492    let s = b.as_ref();
493    let authority_end = Authority::parse_non_empty(s)?;
494
495    if authority_end != s.len() {
496        return Err(ErrorKind::InvalidUriChar.into());
497    }
498
499    let bytes = f(b);
500
501    Ok(Authority {
502        // Safety: the postcondition on parse_non_empty() and the check against
503        // s.len() ensure that b is valid UTF-8. The precondition on f ensures
504        // that this is carried through to bytes.
505        data: unsafe { ByteStr::from_utf8_unchecked(bytes) },
506    })
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512
513    #[test]
514    fn parse_empty_string_is_error() {
515        let err = Authority::parse_non_empty(b"").unwrap_err();
516        assert_eq!(err.0, ErrorKind::Empty);
517    }
518
519    #[test]
520    fn equal_to_self_of_same_authority() {
521        let authority1: Authority = "example.com".parse().unwrap();
522        let authority2: Authority = "EXAMPLE.COM".parse().unwrap();
523        assert_eq!(authority1, authority2);
524        assert_eq!(authority2, authority1);
525    }
526
527    #[test]
528    fn not_equal_to_self_of_different_authority() {
529        let authority1: Authority = "example.com".parse().unwrap();
530        let authority2: Authority = "test.com".parse().unwrap();
531        assert_ne!(authority1, authority2);
532        assert_ne!(authority2, authority1);
533    }
534
535    #[test]
536    fn equates_with_a_str() {
537        let authority: Authority = "example.com".parse().unwrap();
538        assert_eq!(&authority, "EXAMPLE.com");
539        assert_eq!("EXAMPLE.com", &authority);
540        assert_eq!(authority, "EXAMPLE.com");
541        assert_eq!("EXAMPLE.com", authority);
542    }
543
544    #[test]
545    fn from_static_equates_with_a_str() {
546        let authority = Authority::from_static("example.com");
547        assert_eq!(authority, "example.com");
548    }
549
550    #[test]
551    fn not_equal_with_a_str_of_a_different_authority() {
552        let authority: Authority = "example.com".parse().unwrap();
553        assert_ne!(&authority, "test.com");
554        assert_ne!("test.com", &authority);
555        assert_ne!(authority, "test.com");
556        assert_ne!("test.com", authority);
557    }
558
559    #[test]
560    fn equates_with_a_string() {
561        let authority: Authority = "example.com".parse().unwrap();
562        assert_eq!(authority, "EXAMPLE.com".to_string());
563        assert_eq!("EXAMPLE.com".to_string(), authority);
564    }
565
566    #[test]
567    fn equates_with_a_string_of_a_different_authority() {
568        let authority: Authority = "example.com".parse().unwrap();
569        assert_ne!(authority, "test.com".to_string());
570        assert_ne!("test.com".to_string(), authority);
571    }
572
573    #[test]
574    fn compares_to_self() {
575        let authority1: Authority = "abc.com".parse().unwrap();
576        let authority2: Authority = "def.com".parse().unwrap();
577        assert!(authority1 < authority2);
578        assert!(authority2 > authority1);
579    }
580
581    #[test]
582    fn compares_with_a_str() {
583        let authority: Authority = "def.com".parse().unwrap();
584        // with ref
585        assert!(&authority < "ghi.com");
586        assert!("ghi.com" > &authority);
587        assert!(&authority > "abc.com");
588        assert!("abc.com" < &authority);
589
590        // no ref
591        assert!(authority < "ghi.com");
592        assert!("ghi.com" > authority);
593        assert!(authority > "abc.com");
594        assert!("abc.com" < authority);
595    }
596
597    #[test]
598    fn compares_with_a_string() {
599        let authority: Authority = "def.com".parse().unwrap();
600        assert!(authority < "ghi.com".to_string());
601        assert!("ghi.com".to_string() > authority);
602        assert!(authority > "abc.com".to_string());
603        assert!("abc.com".to_string() < authority);
604    }
605
606    #[test]
607    fn allows_percent_in_userinfo() {
608        let authority_str = "a%2f:b%2f@example.com";
609        let authority: Authority = authority_str.parse().unwrap();
610        assert_eq!(authority, authority_str);
611    }
612
613    #[test]
614    fn rejects_percent_in_hostname() {
615        let err = Authority::parse_non_empty(b"example%2f.com").unwrap_err();
616        assert_eq!(err.0, ErrorKind::InvalidAuthority);
617
618        let err = Authority::parse_non_empty(b"a%2f:b%2f@example%2f.com").unwrap_err();
619        assert_eq!(err.0, ErrorKind::InvalidAuthority);
620    }
621
622    #[test]
623    fn allows_percent_in_ipv6_address() {
624        let authority_str = "[fe80::1:2:3:4%25eth0]";
625        let result: Authority = authority_str.parse().unwrap();
626        assert_eq!(result, authority_str);
627    }
628
629    #[test]
630    fn rejects_percent_outside_ipv6_address() {
631        let err = Authority::parse_non_empty(b"1234%20[fe80::1:2:3:4]").unwrap_err();
632        assert_eq!(err.0, ErrorKind::InvalidAuthority);
633
634        let err = Authority::parse_non_empty(b"[fe80::1:2:3:4]%20").unwrap_err();
635        assert_eq!(err.0, ErrorKind::InvalidAuthority);
636    }
637
638    #[test]
639    fn rejects_invalid_utf8() {
640        let err = Authority::try_from([0xc0u8].as_ref()).unwrap_err();
641        assert_eq!(err.0, ErrorKind::InvalidUriChar);
642
643        let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref()))
644            .unwrap_err();
645        assert_eq!(err.0, ErrorKind::InvalidUriChar);
646    }
647
648    #[test]
649    fn rejects_invalid_use_of_brackets() {
650        let err = Authority::parse_non_empty(b"[]@[").unwrap_err();
651        assert_eq!(err.0, ErrorKind::InvalidAuthority);
652    }
653}