Skip to main content

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