rama_http_headers/forwarded/
x_forwarded_for.rs

1use crate::{Error, Header, util};
2use rama_http_types::header;
3use rama_http_types::{HeaderName, HeaderValue};
4use rama_net::forwarded::ForwardedElement;
5use std::iter::FromIterator;
6use std::net::IpAddr;
7
8/// The `X-Forwarded-For` (XFF) request header is a de-facto standard header for
9/// identifying the originating IP address of a client connecting to a web server through a proxy server.
10///
11/// It is recommended to use the [`Forwarded`](super::Forwarded) header instead if you can.
12///
13/// More info can be found at <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For>.
14///
15/// # Syntax
16///
17/// ```text
18/// X-Forwarded-For: <client>, <proxy1>, <proxy2>
19/// ```
20///
21/// # Example values
22///
23/// * `2001:db8:85a3:8d3:1319:8a2e:370:7348`
24/// * `203.0.113.195`
25/// * `203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348,198.51.100.178`
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct XForwardedFor(Vec<IpAddr>);
28
29impl Header for XForwardedFor {
30    fn name() -> &'static HeaderName {
31        &header::X_FORWARDED_FOR
32    }
33
34    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
35        util::csv::from_comma_delimited(values).map(XForwardedFor)
36    }
37
38    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
39        use std::fmt;
40        struct Format<F>(F);
41        impl<F> fmt::Display for Format<F>
42        where
43            F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
44        {
45            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46                (self.0)(f)
47            }
48        }
49        let s = format!(
50            "{}",
51            Format(|f: &mut fmt::Formatter<'_>| {
52                util::csv::fmt_comma_delimited(&mut *f, self.0.iter())
53            })
54        );
55        values.extend(Some(HeaderValue::from_str(&s).unwrap()))
56    }
57}
58
59impl FromIterator<IpAddr> for XForwardedFor {
60    fn from_iter<T>(iter: T) -> Self
61    where
62        T: IntoIterator<Item = IpAddr>,
63    {
64        XForwardedFor(iter.into_iter().collect())
65    }
66}
67
68impl super::ForwardHeader for XForwardedFor {
69    fn try_from_forwarded<'a, I>(input: I) -> Option<Self>
70    where
71        I: IntoIterator<Item = &'a ForwardedElement>,
72    {
73        let vec: Vec<_> = input
74            .into_iter()
75            .filter_map(|el| el.ref_forwarded_for()?.ip())
76            .collect();
77        if vec.is_empty() {
78            None
79        } else {
80            Some(XForwardedFor(vec))
81        }
82    }
83}
84
85impl XForwardedFor {
86    /// Returns an iterator over the defined [`IpAddr`].
87    pub fn iter(&self) -> impl Iterator<Item = &IpAddr> {
88        self.0.iter()
89    }
90}
91
92impl IntoIterator for XForwardedFor {
93    type Item = ForwardedElement;
94    type IntoIter = XForwardedForIterator;
95
96    fn into_iter(self) -> Self::IntoIter {
97        XForwardedForIterator(self.0.into_iter())
98    }
99}
100
101#[derive(Debug, Clone)]
102/// An iterator over the `XForwardedFor` header's elements.
103pub struct XForwardedForIterator(std::vec::IntoIter<IpAddr>);
104
105impl Iterator for XForwardedForIterator {
106    type Item = ForwardedElement;
107
108    fn next(&mut self) -> Option<Self::Item> {
109        self.0.next().map(ForwardedElement::forwarded_for)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    use rama_http_types::HeaderValue;
118
119    macro_rules! test_header {
120        ($name: ident, $input: expr, $expected: expr) => {
121            #[test]
122            fn $name() {
123                assert_eq!(
124                    XForwardedFor::decode(
125                        &mut $input
126                            .into_iter()
127                            .map(|s| HeaderValue::from_bytes(s.as_bytes()).unwrap())
128                            .collect::<Vec<_>>()
129                            .iter()
130                    )
131                    .ok(),
132                    $expected,
133                );
134            }
135        };
136    }
137
138    // Tests from the Docs
139    test_header!(
140        test1,
141        vec!["2001:db8:85a3:8d3:1319:8a2e:370:7348"],
142        Some(XForwardedFor(vec![
143            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap(),
144        ]))
145    );
146    test_header!(
147        test2,
148        vec!["203.0.113.195"],
149        Some(XForwardedFor(vec!["203.0.113.195".parse().unwrap(),]))
150    );
151    test_header!(
152        test3,
153        vec!["203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348"],
154        Some(XForwardedFor(vec![
155            "203.0.113.195".parse().unwrap(),
156            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap()
157        ]))
158    );
159    test_header!(
160        test4,
161        vec!["203.0.113.195", "2001:db8:85a3:8d3:1319:8a2e:370:7348"],
162        Some(XForwardedFor(vec![
163            "203.0.113.195".parse().unwrap(),
164            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap()
165        ]))
166    );
167    test_header!(
168        test5,
169        vec![
170            "203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348",
171            "198.51.100.178"
172        ],
173        Some(XForwardedFor(vec![
174            "203.0.113.195".parse().unwrap(),
175            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap(),
176            "198.51.100.178".parse().unwrap()
177        ]))
178    );
179    test_header!(
180        test6,
181        vec![
182            "203.0.113.195",
183            "2001:db8:85a3:8d3:1319:8a2e:370:7348",
184            "198.51.100.178",
185        ],
186        Some(XForwardedFor(vec![
187            "203.0.113.195".parse().unwrap(),
188            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap(),
189            "198.51.100.178".parse().unwrap()
190        ]))
191    );
192    test_header!(
193        test7,
194        vec![
195            "203.0.113.195",
196            "2001:db8:85a3:8d3:1319:8a2e:370:7348,198.51.100.178",
197        ],
198        Some(XForwardedFor(vec![
199            "203.0.113.195".parse().unwrap(),
200            "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap(),
201            "198.51.100.178".parse().unwrap()
202        ]))
203    );
204
205    #[test]
206    fn test_x_forwarded_for_symmetric_encoder() {
207        for input in [
208            XForwardedFor(vec!["203.0.113.195".parse().unwrap()]),
209            XForwardedFor(vec![
210                "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap(),
211                "203.0.113.195".parse().unwrap(),
212            ]),
213        ] {
214            let mut values = Vec::new();
215            input.encode(&mut values);
216            assert_eq!(XForwardedFor::decode(&mut values.iter()).ok(), Some(input),);
217        }
218    }
219}