rama_net/forwarded/
mod.rs

1//! rama support for the "Forwarded HTTP Extension"
2//!
3//! RFC: <https://datatracker.ietf.org/doc/html/rfc7239>
4
5use rama_core::error::OpaqueError;
6use std::net::IpAddr;
7use std::{fmt, net::SocketAddr};
8
9#[cfg(feature = "http")]
10use rama_http_types::HeaderValue;
11
12mod obfuscated;
13#[doc(inline)]
14use obfuscated::{ObfNode, ObfPort};
15
16mod node;
17#[doc(inline)]
18pub use node::NodeId;
19
20mod element;
21#[doc(inline)]
22pub use element::{ForwardedAuthority, ForwardedElement};
23
24mod proto;
25#[doc(inline)]
26pub use proto::ForwardedProtocol;
27
28mod version;
29#[doc(inline)]
30pub use version::ForwardedVersion;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33/// Forwarding information stored as a chain.
34///
35/// This extension (which can be stored and modified via the [`Context`])
36/// allows to keep track of the forward information. E.g. what was the original
37/// host used by the user, by which proxy it was forwarded, what was the intended
38/// protocol (e.g. https), etc...
39///
40/// RFC: <https://datatracker.ietf.org/doc/html/rfc7239>
41///
42/// [`Context`]: rama_core::Context
43pub struct Forwarded {
44    first: ForwardedElement,
45    others: Vec<ForwardedElement>,
46}
47
48impl Forwarded {
49    /// Create a new [`Forwarded`] extension for the given [`ForwardedElement`]
50    /// as the client Element (the first element).
51    pub const fn new(element: ForwardedElement) -> Self {
52        Self {
53            first: element,
54            others: Vec::new(),
55        }
56    }
57
58    /// Return the client host of this [`Forwarded`] context,
59    /// if there is one defined.
60    ///
61    /// It is assumed that only the first element can be
62    /// described as client information.
63    pub fn client_host(&self) -> Option<&ForwardedAuthority> {
64        self.first.ref_forwarded_host()
65    }
66
67    /// Return the client [`SocketAddr`] of this [`Forwarded`] context,
68    /// if both an Ip and a port are defined.
69    ///
70    /// You can try to fallback to [`Self::client_ip`],
71    /// in case this method returns `None`.
72    pub fn client_socket_addr(&self) -> Option<SocketAddr> {
73        self.first
74            .ref_forwarded_for()
75            .and_then(|node| match (node.ip(), node.port()) {
76                (Some(ip), Some(port)) => Some((ip, port).into()),
77                _ => None,
78            })
79    }
80
81    /// Return the client port of this [`Forwarded`] context,
82    /// if there is one defined.
83    pub fn client_port(&self) -> Option<u16> {
84        self.first.ref_forwarded_for().and_then(|node| node.port())
85    }
86
87    /// Return the client Ip of this [`Forwarded`] context,
88    /// if there is one defined.
89    ///
90    /// This method may return None because there is no forwarded "for"
91    /// information for the client element or because the IP is obfuscated.
92    ///
93    /// It is assumed that only the first element can be
94    /// described as client information.
95    pub fn client_ip(&self) -> Option<IpAddr> {
96        self.first.ref_forwarded_for().and_then(|node| node.ip())
97    }
98
99    /// Return the client protocol of this [`Forwarded`] context,
100    /// if there is one defined.
101    pub fn client_proto(&self) -> Option<ForwardedProtocol> {
102        self.first.ref_forwarded_proto()
103    }
104
105    /// Return the client protocol version of this [`Forwarded`] context,
106    /// if there is one defined.
107    pub fn client_version(&self) -> Option<ForwardedVersion> {
108        self.first.ref_forwarded_version()
109    }
110
111    /// Append a [`ForwardedElement`] to this [`Forwarded`] context.
112    pub fn append(&mut self, element: ForwardedElement) -> &mut Self {
113        self.others.push(element);
114        self
115    }
116
117    /// Extend this [`Forwarded`] context with the given [`ForwardedElement`]s.
118    pub fn extend(&mut self, elements: impl IntoIterator<Item = ForwardedElement>) -> &mut Self {
119        self.others.extend(elements);
120        self
121    }
122
123    /// Iterate over the [`ForwardedElement`]s in this [`Forwarded`] context.
124    pub fn iter(&self) -> impl Iterator<Item = &ForwardedElement> {
125        std::iter::once(&self.first).chain(self.others.iter())
126    }
127}
128
129impl IntoIterator for Forwarded {
130    type Item = ForwardedElement;
131    type IntoIter =
132        std::iter::Chain<std::iter::Once<ForwardedElement>, std::vec::IntoIter<ForwardedElement>>;
133
134    fn into_iter(self) -> Self::IntoIter {
135        let iter = self.others.into_iter();
136        std::iter::once(self.first).chain(iter)
137    }
138}
139
140impl From<ForwardedElement> for Forwarded {
141    #[inline]
142    fn from(value: ForwardedElement) -> Self {
143        Self::new(value)
144    }
145}
146
147impl fmt::Display for Forwarded {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        self.first.fmt(f)?;
150        for other in &self.others {
151            write!(f, ",{other}")?;
152        }
153        Ok(())
154    }
155}
156
157impl std::str::FromStr for Forwarded {
158    type Err = OpaqueError;
159
160    fn from_str(s: &str) -> Result<Self, Self::Err> {
161        let (first, others) = element::parse_one_plus_forwarded_elements(s.as_bytes())?;
162        Ok(Forwarded { first, others })
163    }
164}
165
166impl TryFrom<String> for Forwarded {
167    type Error = OpaqueError;
168
169    fn try_from(s: String) -> Result<Self, Self::Error> {
170        let (first, others) = element::parse_one_plus_forwarded_elements(s.as_bytes())?;
171        Ok(Forwarded { first, others })
172    }
173}
174
175impl TryFrom<&str> for Forwarded {
176    type Error = OpaqueError;
177
178    fn try_from(s: &str) -> Result<Self, Self::Error> {
179        let (first, others) = element::parse_one_plus_forwarded_elements(s.as_bytes())?;
180        Ok(Forwarded { first, others })
181    }
182}
183
184#[cfg(feature = "http")]
185impl TryFrom<HeaderValue> for Forwarded {
186    type Error = OpaqueError;
187
188    fn try_from(header: HeaderValue) -> Result<Self, Self::Error> {
189        let (first, others) = element::parse_one_plus_forwarded_elements(header.as_bytes())?;
190        Ok(Forwarded { first, others })
191    }
192}
193
194#[cfg(feature = "http")]
195impl TryFrom<&HeaderValue> for Forwarded {
196    type Error = OpaqueError;
197
198    fn try_from(header: &HeaderValue) -> Result<Self, Self::Error> {
199        let (first, others) = element::parse_one_plus_forwarded_elements(header.as_bytes())?;
200        Ok(Forwarded { first, others })
201    }
202}
203
204impl TryFrom<Vec<u8>> for Forwarded {
205    type Error = OpaqueError;
206
207    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
208        let (first, others) = element::parse_one_plus_forwarded_elements(bytes.as_ref())?;
209        Ok(Forwarded { first, others })
210    }
211}
212
213impl TryFrom<&[u8]> for Forwarded {
214    type Error = OpaqueError;
215
216    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
217        let (first, others) = element::parse_one_plus_forwarded_elements(bytes)?;
218        Ok(Forwarded { first, others })
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::address::Host;
226
227    #[test]
228    fn test_forwarded_parse_invalid() {
229        for s in [
230            "",
231            "foobar",
232            "127.0.0.1",
233            "⌨️",
234            "for=_foo;for=_bar",
235            ",",
236            "for=127.0.0.1,",
237            "for=127.0.0.1,foobar",
238            "for=127.0.0.1,127.0.0.1",
239            "for=127.0.0.1,⌨️",
240            "for=127.0.0.1,for=_foo;for=_bar",
241            "foobar,for=127.0.0.1",
242            "127.0.0.1,for=127.0.0.1",
243            "⌨️,for=127.0.0.1",
244            "for=_foo;for=_bar,for=127.0.0.1",
245        ] {
246            if let Ok(el) = Forwarded::try_from(s) {
247                panic!("unexpected parse success: input {s}: {el:?}");
248            }
249        }
250    }
251
252    #[test]
253    fn test_forwarded_parse_happy_spec() {
254        for (s, expected) in [
255            (
256                r##"for="_gazonk""##,
257                Forwarded {
258                    first: ForwardedElement::forwarded_for(NodeId::try_from("_gazonk").unwrap()),
259                    others: Vec::new(),
260                },
261            ),
262            (
263                r##"for=192.0.2.43, for=198.51.100.17"##,
264                Forwarded {
265                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
266                    others: vec![ForwardedElement::forwarded_for(
267                        NodeId::try_from("198.51.100.17").unwrap(),
268                    )],
269                },
270            ),
271            (
272                r##"for=192.0.2.43,for=198.51.100.17"##,
273                Forwarded {
274                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
275                    others: vec![ForwardedElement::forwarded_for(
276                        NodeId::try_from("198.51.100.17").unwrap(),
277                    )],
278                },
279            ),
280            (
281                r##"for=192.0.2.43,for=198.51.100.17,for=127.0.0.1"##,
282                Forwarded {
283                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
284                    others: vec![
285                        ForwardedElement::forwarded_for(NodeId::try_from("198.51.100.17").unwrap()),
286                        ForwardedElement::forwarded_for(NodeId::try_from("127.0.0.1").unwrap()),
287                    ],
288                },
289            ),
290            (
291                r##"for=192.0.2.43,for=198.51.100.17,for=unknown"##,
292                Forwarded {
293                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
294                    others: vec![
295                        ForwardedElement::forwarded_for(NodeId::try_from("198.51.100.17").unwrap()),
296                        ForwardedElement::forwarded_for(NodeId::try_from("unknown").unwrap()),
297                    ],
298                },
299            ),
300            (
301                r##"for=192.0.2.43,for="[2001:db8:cafe::17]",for=unknown"##,
302                Forwarded {
303                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
304                    others: vec![
305                        ForwardedElement::forwarded_for(
306                            NodeId::try_from("[2001:db8:cafe::17]").unwrap(),
307                        ),
308                        ForwardedElement::forwarded_for(NodeId::try_from("unknown").unwrap()),
309                    ],
310                },
311            ),
312            (
313                r##"for=192.0.2.43, for="[2001:db8:cafe::17]", for=unknown"##,
314                Forwarded {
315                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
316                    others: vec![
317                        ForwardedElement::forwarded_for(
318                            NodeId::try_from("[2001:db8:cafe::17]").unwrap(),
319                        ),
320                        ForwardedElement::forwarded_for(NodeId::try_from("unknown").unwrap()),
321                    ],
322                },
323            ),
324            (
325                r##"for=192.0.2.43, for="[2001:db8:cafe::17]:4000", for=unknown"##,
326                Forwarded {
327                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
328                    others: vec![
329                        ForwardedElement::forwarded_for(
330                            NodeId::try_from("[2001:db8:cafe::17]:4000").unwrap(),
331                        ),
332                        ForwardedElement::forwarded_for(NodeId::try_from("unknown").unwrap()),
333                    ],
334                },
335            ),
336            (
337                r##"for=192.0.2.43,for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com"##,
338                Forwarded {
339                    first: ForwardedElement::forwarded_for(NodeId::try_from("192.0.2.43").unwrap()),
340                    others: vec![
341                        ForwardedElement::try_from(
342                            "for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com",
343                        )
344                        .unwrap(),
345                    ],
346                },
347            ),
348            (
349                r##"for="192.0.2.43:4000",for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com"##,
350                Forwarded {
351                    first: ForwardedElement::forwarded_for(
352                        NodeId::try_from("192.0.2.43:4000").unwrap(),
353                    ),
354                    others: vec![
355                        ForwardedElement::try_from(
356                            "for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com",
357                        )
358                        .unwrap(),
359                    ],
360                },
361            ),
362        ] {
363            let element = match Forwarded::try_from(s) {
364                Ok(el) => el,
365                Err(err) => panic!("failed to parse happy spec el '{s}': {err}"),
366            };
367            assert_eq!(element, expected, "input: {}", s);
368        }
369    }
370
371    #[test]
372    fn test_forwarded_client_authority() {
373        for (s, expected) in [
374            (
375                r##"for=192.0.2.43,for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com"##,
376                None,
377            ),
378            (
379                r##"host=example.com,for=195.2.34.12"##,
380                Some((Host::try_from("example.com").unwrap(), None)),
381            ),
382            (
383                r##"host="example.com:443",for=195.2.34.12"##,
384                Some((Host::try_from("example.com").unwrap(), Some(443))),
385            ),
386        ] {
387            let forwarded = Forwarded::try_from(s).unwrap();
388            assert_eq!(
389                forwarded
390                    .iter()
391                    .next()
392                    .and_then(|el| el.ref_forwarded_host())
393                    .map(|host| host.clone().into_parts()),
394                expected
395            );
396        }
397    }
398
399    #[test]
400    fn test_forwarded_client_protoy() {
401        for (s, expected) in [
402            (
403                r##"for=192.0.2.43,for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com"##,
404                None,
405            ),
406            (
407                r##"proto=http,for=195.2.34.12"##,
408                Some(ForwardedProtocol::HTTP),
409            ),
410        ] {
411            let forwarded = Forwarded::try_from(s).unwrap();
412            assert_eq!(
413                forwarded
414                    .iter()
415                    .next()
416                    .and_then(|el| el.ref_forwarded_proto()),
417                expected
418            );
419        }
420    }
421}