rama_http_headers/forwarded/
exotic_forward_ip.rs1use rama_core::error::BoxErrorExt as _;
2use rama_core::error::ErrorExt;
3use rama_core::error::{BoxError, ErrorContext};
4use rama_http_types::header::{
5 CF_CONNECTING_IP, CLIENT_IP, TRUE_CLIENT_IP, X_CLIENT_IP, X_REAL_IP,
6};
7use rama_http_types::{HeaderName, HeaderValue};
8use rama_macros::paste;
9use rama_net::forwarded::{ForwardedElement, NodeId};
10use std::fmt;
11use std::net::{IpAddr, Ipv6Addr};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14struct ClientAddr {
15 ip: IpAddr,
16 port: Option<u16>,
17}
18
19impl fmt::Display for ClientAddr {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match &self.port {
22 Some(port) => match &self.ip {
23 IpAddr::V6(ip) => write!(f, "[{ip}]:{port}"),
24 IpAddr::V4(ip) => write!(f, "{ip}:{port}"),
25 },
26 None => self.ip.fmt(f),
27 }
28 }
29}
30
31impl std::str::FromStr for ClientAddr {
32 type Err = BoxError;
33
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
35 if let Ok(ip) = s.parse() {
36 return Ok(Self { ip, port: None });
39 }
40
41 let (s, port) = try_to_split_num_port_from_str(s);
42 let ip = try_to_parse_str_to_ip(s).context("parse forwarded ip")?;
43
44 match ip {
45 IpAddr::V6(_) if port.is_some() && !s.starts_with('[') => Err(
46 BoxError::from_static_str("missing brackets for IPv6 address with port")
47 .context_field("ip", ip)
48 .context_debug_field("port", port),
49 ),
50 _ => Ok(Self { ip, port }),
51 }
52 }
53}
54
55fn try_to_parse_str_to_ip(value: &str) -> Option<IpAddr> {
56 if value.starts_with('[') || value.ends_with(']') {
57 let value = value
58 .strip_prefix('[')
59 .and_then(|value| value.strip_suffix(']'))?;
60 Some(IpAddr::V6(value.parse::<Ipv6Addr>().ok()?))
61 } else {
62 value.parse::<IpAddr>().ok()
63 }
64}
65
66fn try_to_split_num_port_from_str(s: &str) -> (&str, Option<u16>) {
67 if let Some(colon) = s.as_bytes().iter().rposition(|c| *c == b':') {
68 match s[colon + 1..].parse() {
69 Ok(port) => (&s[..colon], Some(port)),
70 Err(_) => (s, None),
71 }
72 } else {
73 (s, None)
74 }
75}
76
77macro_rules! exotic_forward_ip_headers {
78 (
79 $(
80 #[doc = $desc:literal]
81 #[header = $header:ident]
82 $(#[$outer:meta])*
83 pub struct $name:ident;
84 )+
85 ) => {
86 $(
87 #[derive(Debug, Clone, PartialEq, Eq)]
88 #[doc = $desc]
89 $(#[$outer])*
90 pub struct $name(ClientAddr);
91
92 impl $crate::TypedHeader for $name {
93 fn name() -> &'static HeaderName {
94 &$header
95 }
96 }
97
98 impl $crate::HeaderDecode for $name {
99 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(
100 values: &mut I,
101 ) -> Result<Self, crate::Error> {
102 Ok($name(
103 values
104 .next()
105 .and_then(|value| value.to_str().ok().and_then(|s| s.parse().ok()))
106 .ok_or_else(crate::Error::invalid)?,
107 ))
108 }
109 }
110
111 impl $crate::HeaderEncode for $name {
112 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
113 let s = self.0.to_string();
114 values.extend(Some(HeaderValue::try_from(s).unwrap()))
115 }
116 }
117
118 impl super::ForwardHeader for $name {
119 fn try_from_forwarded<'a, I>(input: I) -> Option<Self>
120 where
121 I: IntoIterator<Item = &'a ForwardedElement>,
122 {
123 let node = input
124 .into_iter()
125 .next()?
126 .forwarded_for()?;
127 let ip = node.ip()?;
128 let port = node.port();
129 Some($name(ClientAddr { ip, port }))
130 }
131 }
132
133 paste! {
134 impl IntoIterator for $name {
135 type Item = ForwardedElement;
136 type IntoIter = [<$name Iterator>];
137
138 fn into_iter(self) -> Self::IntoIter {
139 [<$name Iterator>](Some(self.0))
140 }
141 }
142
143 #[derive(Debug, Clone)]
144 #[doc = concat!("An iterator over the `", stringify!($name), "` header's elements.")]
145 pub struct [<$name Iterator>](Option<ClientAddr>);
146
147 impl Iterator for [<$name Iterator>] {
148 type Item = ForwardedElement;
149
150 fn next(&mut self) -> Option<Self::Item> {
151 self.0.take().map(|addr| {
152 let node: NodeId = (addr.ip, addr.port).into();
153 ForwardedElement::new_forwarded_for(node)
154 })
155 }
156 }
157 }
158 )+
159 };
160}
161
162exotic_forward_ip_headers! {
163 #[doc = "CF-Connecting-IP provides the client IP address connecting to Cloudflare to the origin web server."]
164 #[header = CF_CONNECTING_IP]
165 pub struct CFConnectingIp;
166
167 #[doc = "True-Client-IP provides the original client IP address to the origin web server (Cloudflare Enterprise)."]
168 #[header = TRUE_CLIENT_IP]
169 pub struct TrueClientIp;
170
171 #[doc = "X-Real-Ip is used by some proxy software to set the real client Ip Address (known to them)."]
172 #[header = X_REAL_IP]
173 pub struct XRealIp;
174
175
176 #[doc = "Client-Ip is used by some proxy software to set the real client Ip Address (known to them)."]
177 #[header = CLIENT_IP]
178 pub struct ClientIp;
179
180 #[doc = "X-Client-Ip is used by some proxy software to set the real client Ip Address (known to them)."]
181 #[header = X_CLIENT_IP]
182 pub struct XClientIp;
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::{HeaderDecode, HeaderEncode};
189
190 macro_rules! test_headers {
191 ($($ty: ident),+ ; $name: ident, $input: expr, $expected: literal) => {
192 #[test]
193 fn $name() {
194 $(
195 assert_eq!(
196 $ty::decode(
197 &mut $input
198 .into_iter()
199 .map(|s| HeaderValue::from_bytes(s.as_bytes()).unwrap())
200 .collect::<Vec<_>>()
201 .iter()
202 )
203 .unwrap(),
204 $ty($expected.parse().unwrap()),
205 );
206 )+
207 }
208 };
209 }
210
211 macro_rules! test_header {
212 ($name: ident, $input: expr, $expected: literal) => {
213 test_headers!(CFConnectingIp, TrueClientIp, XRealIp, ClientIp, XClientIp; $name, $input, $expected);
214 };
215 }
216
217 test_header!(test1, vec!["203.0.113.195"], "203.0.113.195");
219 test_header!(test2, vec!["203.0.113.195:80"], "203.0.113.195:80");
220 test_header!(
221 test3,
222 vec!["2001:db8:85a3:8d3:1319:8a2e:370:7348"],
223 "2001:db8:85a3:8d3:1319:8a2e:370:7348"
224 );
225 test_header!(
226 test4,
227 vec!["[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"],
228 "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"
229 );
230
231 macro_rules! symmetric_test_header {
232 ($name: ident) => {
233 for input in [
234 $name("127.0.0.1:8080".parse().unwrap()),
235 $name(
236 "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"
237 .parse()
238 .unwrap(),
239 ),
240 $name("203.0.113.195".parse().unwrap()),
241 $name("203.0.113.195:80".parse().unwrap()),
242 ] {
243 let mut values = Vec::new();
244 input.encode(&mut values);
245 assert_eq!($name::decode(&mut values.iter()).unwrap(), input);
246 }
247 };
248 }
249
250 #[test]
251 fn test_symmetry_encode() {
252 symmetric_test_header!(CFConnectingIp);
253 symmetric_test_header!(TrueClientIp);
254 symmetric_test_header!(XRealIp);
255 symmetric_test_header!(ClientIp);
256 symmetric_test_header!(XClientIp);
257 }
258}