Skip to main content

rama_http_headers/forwarded/
x_forwarded_for.rs

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