rama_http_headers/forwarded/
x_forwarded_proto.rs

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