rama_http/headers/forwarded/
x_forwarded_for.rs

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