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