rama_http_headers/forwarded/
x_forwarded_host.rs

1use crate::{Error, Header};
2use rama_http_types::header;
3use rama_http_types::{HeaderName, HeaderValue};
4use rama_net::address::Host;
5use rama_net::forwarded::{ForwardedAuthority, ForwardedElement};
6
7/// The X-Forwarded-Host (XFH) header is a de-facto standard header for identifying the
8/// original host requested by the client in the Host HTTP request header.
9///
10/// It is recommended to use the [`Forwarded`](super::Forwarded) header instead if you can.
11///
12/// More info can be found at <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host>.
13///
14/// # Syntax
15///
16/// ```text
17/// X-Forwarded-Host: <host>
18/// ```
19///
20/// # Example values
21///
22/// * `id42.example-cdn.com`
23/// * `id42.example-cdn.com:443`
24/// * `203.0.113.195`
25/// * `203.0.113.195:80`
26/// * `2001:db8:85a3:8d3:1319:8a2e:370:7348`
27/// * `[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080`
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct XForwardedHost(ForwardedAuthority);
30
31impl Header for XForwardedHost {
32    fn name() -> &'static HeaderName {
33        &header::X_FORWARDED_HOST
34    }
35
36    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
37        Ok(XForwardedHost(
38            values
39                .next()
40                .and_then(|value| value.to_str().ok().and_then(|s| s.parse().ok()))
41                .ok_or_else(Error::invalid)?,
42        ))
43    }
44
45    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
46        let s = self.0.to_string();
47        values.extend(Some(HeaderValue::from_str(&s).unwrap()))
48    }
49}
50
51impl XForwardedHost {
52    #[inline]
53    /// Get a reference to the [`Host`] of this [`XForwardedHost`].
54    pub fn host(&self) -> &Host {
55        self.0.host()
56    }
57
58    #[inline]
59    /// Get a copy of the `port` of this [`XForwardedHost`] if it is set.
60    pub fn port(&self) -> Option<u16> {
61        self.0.port()
62    }
63
64    /// Return a reference to the inner data of this [`Header`].
65    pub fn inner(&self) -> &ForwardedAuthority {
66        &self.0
67    }
68
69    /// Consume this [`Header`] into its inner data.
70    pub fn into_inner(self) -> ForwardedAuthority {
71        self.0
72    }
73}
74
75impl IntoIterator for XForwardedHost {
76    type Item = ForwardedElement;
77    type IntoIter = XForwardedHostIterator;
78
79    fn into_iter(self) -> Self::IntoIter {
80        XForwardedHostIterator(Some(self.0))
81    }
82}
83
84impl super::ForwardHeader for XForwardedHost {
85    fn try_from_forwarded<'a, I>(input: I) -> Option<Self>
86    where
87        I: IntoIterator<Item = &'a ForwardedElement>,
88    {
89        let el = input.into_iter().next()?;
90        let host = el.ref_forwarded_host().cloned()?;
91        Some(XForwardedHost(host))
92    }
93}
94
95#[derive(Debug, Clone)]
96/// An iterator over the `XForwardedHost` header's elements.
97pub struct XForwardedHostIterator(Option<ForwardedAuthority>);
98
99impl Iterator for XForwardedHostIterator {
100    type Item = ForwardedElement;
101
102    fn next(&mut self) -> Option<Self::Item> {
103        self.0.take().map(ForwardedElement::forwarded_host)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    use rama_http_types::HeaderValue;
112
113    macro_rules! test_header {
114        ($name: ident, $input: expr, $expected: expr) => {
115            #[test]
116            fn $name() {
117                assert_eq!(
118                    XForwardedHost::decode(
119                        &mut $input
120                            .into_iter()
121                            .map(|s| HeaderValue::from_bytes(s.as_bytes()).unwrap())
122                            .collect::<Vec<_>>()
123                            .iter()
124                    )
125                    .ok(),
126                    $expected,
127                );
128            }
129        };
130    }
131
132    // Tests from the Docs
133    test_header!(
134        test1,
135        vec!["id42.example-cdn.com"],
136        Some(XForwardedHost("id42.example-cdn.com".parse().unwrap()))
137    );
138    test_header!(
139        test2,
140        // 2nd one gets ignored
141        vec!["id42.example-cdn.com", "example.com"],
142        Some(XForwardedHost("id42.example-cdn.com".parse().unwrap()))
143    );
144    test_header!(
145        test3,
146        vec!["id42.example-cdn.com:443"],
147        Some(XForwardedHost("id42.example-cdn.com:443".parse().unwrap()))
148    );
149    test_header!(
150        test4,
151        vec!["203.0.113.195"],
152        Some(XForwardedHost("203.0.113.195".parse().unwrap()))
153    );
154    test_header!(
155        test5,
156        vec!["203.0.113.195:80"],
157        Some(XForwardedHost("203.0.113.195:80".parse().unwrap()))
158    );
159    test_header!(
160        test6,
161        vec!["2001:db8:85a3:8d3:1319:8a2e:370:7348"],
162        Some(XForwardedHost(
163            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap()
164        ))
165    );
166    test_header!(
167        test7,
168        vec!["[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"],
169        Some(XForwardedHost(
170            "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"
171                .parse()
172                .unwrap()
173        ))
174    );
175
176    #[test]
177    fn test_x_forwarded_host_symmetry_encode() {
178        for input in [
179            XForwardedHost("id42.example-cdn.com".parse().unwrap()),
180            XForwardedHost("id42.example-cdn.com:443".parse().unwrap()),
181            XForwardedHost("127.0.0.1".parse().unwrap()),
182        ] {
183            let mut values = Vec::new();
184            input.encode(&mut values);
185            assert_eq!(XForwardedHost::decode(&mut values.iter()).unwrap(), input);
186        }
187    }
188}