Skip to main content

rama_http_headers/forwarded/
x_forwarded_proto.rs

1use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
2use rama_core::telemetry::tracing;
3use rama_http_types::{HeaderName, HeaderValue, header};
4use rama_net::forwarded::{ForwardedElement, ForwardedProtocol};
5
6/// The X-Forwarded-Proto (XFP) header is a de-facto standard header for
7/// identifying the protocol (HTTP or HTTPS) that a client used to connect to your proxy or load balancer.
8///
9/// Your server access logs contain the protocol used between the server and the load balancer,
10/// but not the protocol used between the client and the load balancer. To determine the protocol
11/// used between the client and the load balancer, the X-Forwarded-Proto request header can be used.
12///
13/// It is recommended to use the [`Forwarded`](super::Forwarded) header instead if you can.
14///
15/// More info can be found at <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto>.
16///
17/// # Syntax
18///
19/// ```text
20/// X-Forwarded-Proto: <protocol>
21/// ```
22///
23/// # Example values
24///
25/// * `https`
26/// * `http`
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct XForwardedProto(ForwardedProtocol);
29
30impl TypedHeader for XForwardedProto {
31    fn name() -> &'static HeaderName {
32        &header::X_FORWARDED_PROTO
33    }
34}
35
36impl HeaderDecode for XForwardedProto {
37    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
38        Ok(Self(
39            values
40                .next()
41                .and_then(|value| value.to_str().ok().and_then(|s| s.parse().ok()))
42                .ok_or_else(Error::invalid)?,
43        ))
44    }
45}
46
47impl HeaderEncode for XForwardedProto {
48    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
49        let s = self.0.to_string();
50        match HeaderValue::try_from(s) {
51            Ok(value) => values.extend(::std::iter::once(value)),
52            Err(err) => {
53                tracing::debug!("failed to encode x-forwarded-proto as header value: {err}")
54            }
55        }
56    }
57}
58
59impl XForwardedProto {
60    /// Get a reference to the [`ForwardedProtocol`] of this [`XForwardedProto`].
61    #[must_use]
62    pub fn protocol(&self) -> &ForwardedProtocol {
63        &self.0
64    }
65
66    /// Consume this header into the inner data ([`ForwardedProtocol`]).
67    #[must_use]
68    pub fn into_protocol(self) -> ForwardedProtocol {
69        self.0
70    }
71}
72
73impl IntoIterator for XForwardedProto {
74    type Item = ForwardedElement;
75    type IntoIter = XForwardedProtoIterator;
76
77    fn into_iter(self) -> Self::IntoIter {
78        XForwardedProtoIterator(Some(self.0))
79    }
80}
81
82impl super::ForwardHeader for XForwardedProto {
83    fn try_from_forwarded<'a, I>(input: I) -> Option<Self>
84    where
85        I: IntoIterator<Item = &'a ForwardedElement>,
86    {
87        let proto = input.into_iter().next()?.forwarded_proto()?;
88        Some(Self(proto))
89    }
90}
91
92#[derive(Debug, Clone)]
93/// An iterator over the `XForwardedProto` header's elements.
94pub struct XForwardedProtoIterator(Option<ForwardedProtocol>);
95
96impl Iterator for XForwardedProtoIterator {
97    type Item = ForwardedElement;
98
99    fn next(&mut self) -> Option<Self::Item> {
100        self.0.take().map(ForwardedElement::new_forwarded_proto)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    use rama_http_types::HeaderValue;
109
110    macro_rules! test_header {
111        ($name: ident, $input: expr, $expected: expr) => {
112            #[test]
113            fn $name() {
114                assert_eq!(
115                    XForwardedProto::decode(
116                        &mut $input
117                            .into_iter()
118                            .map(|s| HeaderValue::from_bytes(s.as_bytes()).unwrap())
119                            .collect::<Vec<_>>()
120                            .iter()
121                    )
122                    .ok(),
123                    $expected,
124                );
125            }
126        };
127    }
128
129    // Tests from the Docs
130    test_header!(
131        test1,
132        vec!["https"],
133        Some(XForwardedProto(ForwardedProtocol::HTTPS))
134    );
135    test_header!(
136        test2,
137        // 2nd one gets ignored
138        vec!["https", "http"],
139        Some(XForwardedProto(ForwardedProtocol::HTTPS))
140    );
141    test_header!(
142        test3,
143        vec!["http"],
144        Some(XForwardedProto(ForwardedProtocol::HTTP))
145    );
146
147    #[test]
148    fn test_x_forwarded_proto_symmetric_encoder() {
149        for input in [ForwardedProtocol::HTTP, ForwardedProtocol::HTTPS] {
150            let input = XForwardedProto(input);
151            let mut values = Vec::new();
152            input.encode(&mut values);
153            let output = XForwardedProto::decode(&mut values.iter()).unwrap();
154            assert_eq!(input, output);
155        }
156    }
157}