no_std_http/uri/
authority.rs

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