rama_net/forwarded/element/
mod.rs

1use super::{ForwardedProtocol, ForwardedVersion, NodeId};
2use crate::address::{Authority, Host};
3use rama_core::error::{ErrorContext, OpaqueError};
4use std::fmt;
5use std::net::SocketAddr;
6use std::{collections::HashMap, net::IpAddr};
7
8#[cfg(feature = "http")]
9use rama_http_types::HeaderValue;
10
11mod parser;
12#[doc(inline)]
13pub(crate) use parser::{parse_one_plus_forwarded_elements, parse_single_forwarded_element};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16/// A single entry in the [`Forwarded`] chain.
17///
18/// [`Forwarded`]: crate::forwarded::Forwarded
19pub struct ForwardedElement {
20    by_node: Option<NodeId>,
21    for_node: Option<NodeId>,
22    authority: Option<ForwardedAuthority>,
23    proto: Option<ForwardedProtocol>,
24    proto_version: Option<ForwardedVersion>,
25
26    // not expected, but if used these parameters (keys)
27    // should be registered ideally also in
28    // <https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#forwarded>
29    extensions: Option<HashMap<String, ExtensionValue>>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33struct ExtensionValue {
34    value: String,
35    quoted: bool,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39/// Similar to [`Authority`] but with the port being optional.
40pub struct ForwardedAuthority {
41    host: Host,
42    port: Option<u16>,
43}
44
45impl ForwardedAuthority {
46    /// Create a new [`ForwardedAuthority`]
47    pub const fn new(host: Host, port: Option<u16>) -> Self {
48        Self { host, port }
49    }
50
51    /// Get a reference to the [`Host`] of this [`ForwardedAuthority`].
52    pub fn host(&self) -> &Host {
53        &self.host
54    }
55
56    /// Get a copy of the `port` of this [`ForwardedAuthority`] if it is set.
57    pub fn port(&self) -> Option<u16> {
58        self.port
59    }
60
61    /// Consume self and return the inner [`Host`] and `port` if it is set.
62    pub fn into_parts(self) -> (Host, Option<u16>) {
63        (self.host, self.port)
64    }
65}
66
67impl From<Host> for ForwardedAuthority {
68    fn from(value: Host) -> Self {
69        Self {
70            host: value,
71            port: None,
72        }
73    }
74}
75
76impl From<SocketAddr> for ForwardedAuthority {
77    fn from(value: SocketAddr) -> Self {
78        Self {
79            host: value.ip().into(),
80            port: Some(value.port()),
81        }
82    }
83}
84
85impl From<Authority> for ForwardedAuthority {
86    fn from(value: Authority) -> Self {
87        let (host, port) = value.into_parts();
88        Self {
89            host,
90            port: Some(port),
91        }
92    }
93}
94
95impl ForwardedElement {
96    /// Merge the properties of another [`ForwardedElement`] into this one.
97    pub fn merge(&mut self, other: ForwardedElement) -> &mut Self {
98        if let Some(by_node) = other.by_node {
99            self.by_node = Some(by_node);
100        }
101        if let Some(for_node) = other.for_node {
102            self.for_node = Some(for_node);
103        }
104        if let Some(authority) = other.authority {
105            self.authority = Some(authority);
106        }
107        if let Some(proto) = other.proto {
108            self.proto = Some(proto);
109        }
110        if let Some(extensions) = other.extensions {
111            match &mut self.extensions {
112                Some(map) => {
113                    map.extend(extensions);
114                }
115                None => {
116                    self.extensions = Some(extensions);
117                }
118            }
119        }
120        self
121    }
122
123    /// Return the host if one is defined.
124    pub fn authority(&self) -> Option<(Host, Option<u16>)> {
125        self.authority
126            .as_ref()
127            .map(|authority| (authority.host.clone(), authority.port))
128    }
129
130    /// Create a new [`ForwardedElement`] with the "host" parameter set
131    /// using the given [`Host`], [`Authority`] or [`SocketAddr`].
132    pub fn forwarded_host(authority: impl Into<ForwardedAuthority>) -> Self {
133        Self {
134            by_node: None,
135            for_node: None,
136            authority: Some(authority.into()),
137            proto: None,
138            proto_version: None,
139            extensions: None,
140        }
141    }
142
143    /// Sets the "host" parameter in this [`ForwardedElement`] using
144    /// the given [`Host`], [`Authority`] or [`SocketAddr`].
145    pub fn set_forwarded_host(&mut self, authority: impl Into<ForwardedAuthority>) -> &mut Self {
146        self.authority = Some(authority.into());
147        self
148    }
149
150    /// Get a reference to the "host" parameter if it is set.
151    pub fn ref_forwarded_host(&self) -> Option<&ForwardedAuthority> {
152        self.authority.as_ref()
153    }
154
155    /// Create a new [`ForwardedElement`] with the "for" parameter
156    /// set to the given valid node identifier. Examples are
157    /// an Ip Address or Domain, with or without a port.
158    pub fn forwarded_for(node_id: impl Into<NodeId>) -> Self {
159        Self {
160            by_node: None,
161            for_node: Some(node_id.into()),
162            authority: None,
163            proto: None,
164            proto_version: None,
165            extensions: None,
166        }
167    }
168
169    /// Sets the "for" parameter for this [`ForwardedElement`] using the given valid node identifier.
170    /// Examples are an Ip Address or Domain, with or without a port.
171    pub fn set_forwarded_for(&mut self, node_id: impl Into<NodeId>) -> &mut Self {
172        self.for_node = Some(node_id.into());
173        self
174    }
175
176    /// Get a reference to the "for" parameter if it is set.
177    pub fn ref_forwarded_for(&self) -> Option<&NodeId> {
178        self.for_node.as_ref()
179    }
180
181    /// Create a new [`ForwardedElement`] with the "by" parameter
182    /// set to the given valid node identifier. Examples are
183    /// an Ip Address or Domain, with or without a port.
184    pub fn forwarded_by(node_id: impl Into<NodeId>) -> Self {
185        Self {
186            by_node: Some(node_id.into()),
187            for_node: None,
188            authority: None,
189            proto: None,
190            proto_version: None,
191            extensions: None,
192        }
193    }
194
195    /// Sets the "by" parameter for this [`ForwardedElement`] using the given valid node identifier.
196    /// Examples are an Ip Address or Domain, with or without a port.
197    pub fn set_forwarded_by(&mut self, node_id: impl Into<NodeId>) -> &mut Self {
198        self.by_node = Some(node_id.into());
199        self
200    }
201
202    /// Get a reference to the "by" parameter if it is set.
203    pub fn ref_forwarded_by(&self) -> Option<&NodeId> {
204        self.by_node.as_ref()
205    }
206
207    /// Create a new [`ForwardedElement`] with the "proto" parameter
208    /// set to the given valid/recognised [`ForwardedProtocol`]
209    pub fn forwarded_proto(protocol: ForwardedProtocol) -> Self {
210        Self {
211            by_node: None,
212            for_node: None,
213            authority: None,
214            proto: Some(protocol),
215            proto_version: None,
216            extensions: None,
217        }
218    }
219
220    /// Set the "proto" parameter to the given valid/recognised [`ForwardedProtocol`].
221    pub fn set_forwarded_proto(&mut self, protocol: ForwardedProtocol) -> &mut Self {
222        self.proto = Some(protocol);
223        self
224    }
225
226    /// Get a reference to the "proto" parameter if it is set.
227    pub fn ref_forwarded_proto(&self) -> Option<ForwardedProtocol> {
228        self.proto.clone()
229    }
230
231    /// Create a new [`ForwardedElement`] with the "version" parameter
232    /// set to the given valid/recognised [`ForwardedVersion`].
233    pub fn forwarded_version(version: ForwardedVersion) -> Self {
234        Self {
235            by_node: None,
236            for_node: None,
237            authority: None,
238            proto: None,
239            proto_version: Some(version),
240            extensions: None,
241        }
242    }
243
244    /// Set the "version" parameter to the given valid/recognised [`ForwardedVersion`].
245    pub fn set_forwarded_version(&mut self, version: ForwardedVersion) -> &mut Self {
246        self.proto_version = Some(version);
247        self
248    }
249
250    /// Get a copy of the "version" parameter, if it is set.
251    pub fn ref_forwarded_version(&self) -> Option<ForwardedVersion> {
252        self.proto_version
253    }
254}
255
256impl fmt::Display for ForwardedAuthority {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        match self.port {
259            Some(port) => match &self.host {
260                Host::Address(IpAddr::V6(ip)) => write!(f, "[{ip}]:{port}"),
261                host => write!(f, "{host}:{port}"),
262            },
263            None => self.host.fmt(f),
264        }
265    }
266}
267
268impl fmt::Display for ForwardedElement {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        let mut separator = "";
271
272        if let Some(ref by_node) = self.by_node {
273            write!(f, "by=")?;
274            let quoted =
275                by_node.has_any_port() || by_node.ip().map(|ip| ip.is_ipv6()).unwrap_or_default();
276            if quoted {
277                write!(f, r##""{by_node}""##)?;
278            } else {
279                by_node.fmt(f)?;
280            }
281            separator = ";";
282        }
283
284        if let Some(ref for_node) = self.for_node {
285            write!(f, "{separator}for=")?;
286            let quoted =
287                for_node.has_any_port() || for_node.ip().map(|ip| ip.is_ipv6()).unwrap_or_default();
288            if quoted {
289                write!(f, r##""{for_node}""##)?;
290            } else {
291                for_node.fmt(f)?;
292            }
293            separator = ";";
294        }
295
296        if let Some(ref authority) = self.authority {
297            write!(f, "{separator}host=")?;
298            let quoted =
299                authority.port.is_some() || matches!(authority.host, Host::Address(IpAddr::V6(_)));
300            if quoted {
301                write!(f, r##""{authority}""##)?;
302            } else {
303                authority.fmt(f)?;
304            }
305            separator = ";";
306        }
307
308        if let Some(ref proto) = self.proto {
309            write!(f, "{separator}proto=")?;
310            proto.fmt(f)?;
311        }
312
313        Ok(())
314    }
315}
316
317impl std::str::FromStr for ForwardedElement {
318    type Err = OpaqueError;
319
320    fn from_str(s: &str) -> Result<Self, Self::Err> {
321        parse_single_forwarded_element(s.as_bytes())
322    }
323}
324
325impl TryFrom<String> for ForwardedElement {
326    type Error = OpaqueError;
327
328    fn try_from(s: String) -> Result<Self, Self::Error> {
329        parse_single_forwarded_element(s.as_bytes())
330    }
331}
332
333impl TryFrom<&str> for ForwardedElement {
334    type Error = OpaqueError;
335
336    fn try_from(s: &str) -> Result<Self, Self::Error> {
337        parse_single_forwarded_element(s.as_bytes())
338    }
339}
340
341#[cfg(feature = "http")]
342impl TryFrom<HeaderValue> for ForwardedElement {
343    type Error = OpaqueError;
344
345    fn try_from(header: HeaderValue) -> Result<Self, Self::Error> {
346        parse_single_forwarded_element(header.as_bytes())
347    }
348}
349
350#[cfg(feature = "http")]
351impl TryFrom<&HeaderValue> for ForwardedElement {
352    type Error = OpaqueError;
353
354    fn try_from(header: &HeaderValue) -> Result<Self, Self::Error> {
355        parse_single_forwarded_element(header.as_bytes())
356    }
357}
358
359impl TryFrom<Vec<u8>> for ForwardedElement {
360    type Error = OpaqueError;
361
362    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
363        parse_single_forwarded_element(bytes.as_ref())
364    }
365}
366
367impl TryFrom<&[u8]> for ForwardedElement {
368    type Error = OpaqueError;
369
370    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
371        parse_single_forwarded_element(bytes)
372    }
373}
374
375impl std::str::FromStr for ForwardedAuthority {
376    type Err = OpaqueError;
377
378    fn from_str(s: &str) -> Result<Self, Self::Err> {
379        if let Ok(host) = Host::try_from(s) {
380            // first try host alone, as it is most common,
381            // and also prevents IPv6 to be seen by default with port
382            return Ok(ForwardedAuthority { host, port: None });
383        }
384
385        let (s, port) = try_to_split_num_port_from_str(s);
386        let host = Host::try_from(s).context("parse forwarded host")?;
387
388        match host {
389            Host::Address(IpAddr::V6(_)) if port.is_some() && !s.starts_with('[') => Err(
390                OpaqueError::from_display("missing brackets for host IPv6 address with port"),
391            ),
392            _ => Ok(ForwardedAuthority { host, port }),
393        }
394    }
395}
396
397fn try_to_split_num_port_from_str(s: &str) -> (&str, Option<u16>) {
398    if let Some(colon) = s.as_bytes().iter().rposition(|c| *c == b':') {
399        match s[colon + 1..].parse() {
400            Ok(port) => (&s[..colon], Some(port)),
401            Err(_) => (s, None),
402        }
403    } else {
404        (s, None)
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411
412    #[test]
413    fn test_forwarded_element_parse_invalid() {
414        for s in [
415            "",
416            "foobar",
417            "127.0.0.1",
418            "⌨️",
419            "for=_foo;for=_bar",
420            "for=foo,proto=http",
421        ] {
422            if let Ok(el) = ForwardedElement::try_from(s) {
423                panic!("unexpected parse success: input {s}: {el:?}");
424            }
425        }
426    }
427
428    #[test]
429    fn test_forwarded_element_parse_happy_spec() {
430        for (s, expected) in [
431            (
432                r##"for="_gazonk""##,
433                ForwardedElement::forwarded_for(NodeId::try_from("_gazonk").unwrap()),
434            ),
435            (
436                r##"For="[2001:db8:cafe::17]:4711""##,
437                ForwardedElement::forwarded_for(
438                    NodeId::try_from("[2001:db8:cafe::17]:4711").unwrap(),
439                ),
440            ),
441            (
442                r##"For="[2001:db8:cafe::17]:4711";proto=http"##,
443                ForwardedElement {
444                    by_node: None,
445                    for_node: Some(NodeId::try_from("[2001:db8:cafe::17]:4711").unwrap()),
446                    authority: None,
447                    proto: Some(ForwardedProtocol::HTTP),
448                    proto_version: None,
449                    extensions: None,
450                },
451            ),
452            (
453                r##"For="[2001:db8:cafe::17]:4711";proto=http;foo=bar"##,
454                ForwardedElement {
455                    by_node: None,
456                    for_node: Some(NodeId::try_from("[2001:db8:cafe::17]:4711").unwrap()),
457                    authority: None,
458                    proto: Some(ForwardedProtocol::HTTP),
459                    proto_version: None,
460                    extensions: Some(
461                        [(
462                            "foo".to_owned(),
463                            ExtensionValue {
464                                value: "bar".to_owned(),
465                                quoted: false,
466                            },
467                        )]
468                        .into(),
469                    ),
470                },
471            ),
472            (
473                r##"for=192.0.2.60;proto=http;by=203.0.113.43"##,
474                ForwardedElement {
475                    by_node: Some(NodeId::try_from("203.0.113.43").unwrap()),
476                    for_node: Some(NodeId::try_from("192.0.2.60").unwrap()),
477                    authority: None,
478                    proto: Some(ForwardedProtocol::HTTP),
479                    proto_version: None,
480                    extensions: None,
481                },
482            ),
483        ] {
484            let element = match ForwardedElement::try_from(s) {
485                Ok(el) => el,
486                Err(err) => panic!("failed to parse happy spec el '{s}': {err}"),
487            };
488            assert_eq!(element, expected, "input: {}", s);
489        }
490    }
491}