1use std::net::{IpAddr, SocketAddr};
17
18use http::{HeaderMap, HeaderName, Request};
19
20use crate::{Error, header::extract_single_header_text};
21
22pub const CF_CONNECTING_IP: HeaderName = HeaderName::from_static("cf-connecting-ip");
24
25pub const CLOUDFRONT_VIEWER_ADDRESS: HeaderName =
27 HeaderName::from_static("cloudfront-viewer-address");
28
29pub const FLY_CLIENT_IP: HeaderName = HeaderName::from_static("fly-client-ip");
31
32pub const TRUE_CLIENT_IP: HeaderName = HeaderName::from_static("true-client-ip");
34
35pub const X_ENVOY_EXTERNAL_ADDRESS: HeaderName =
37 HeaderName::from_static("x-envoy-external-address");
38
39pub const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip");
41
42pub fn extract_header_cf_connecting_ip(headers: &HeaderMap) -> Result<Option<IpAddr>, Error> {
49 extract_single_ip(headers, &CF_CONNECTING_IP)
50}
51
52pub fn extract_request_cf_connecting_ip<B>(request: &Request<B>) -> Result<Option<IpAddr>, Error> {
58 extract_header_cf_connecting_ip(request.headers())
59}
60
61pub fn extract_header_cloudfront_viewer_address(
71 headers: &HeaderMap,
72) -> Result<Option<IpAddr>, Error> {
73 extract_single_header_text(headers, &CLOUDFRONT_VIEWER_ADDRESS)?
74 .map(parse_cloudfront_viewer_address)
75 .transpose()
76}
77
78pub fn extract_request_cloudfront_viewer_address<B>(
85 request: &Request<B>,
86) -> Result<Option<IpAddr>, Error> {
87 extract_header_cloudfront_viewer_address(request.headers())
88}
89
90pub fn extract_header_fly_client_ip(headers: &HeaderMap) -> Result<Option<IpAddr>, Error> {
97 extract_single_ip(headers, &FLY_CLIENT_IP)
98}
99
100pub fn extract_request_fly_client_ip<B>(request: &Request<B>) -> Result<Option<IpAddr>, Error> {
106 extract_header_fly_client_ip(request.headers())
107}
108
109pub fn extract_header_true_client_ip(headers: &HeaderMap) -> Result<Option<IpAddr>, Error> {
117 extract_single_ip(headers, &TRUE_CLIENT_IP)
118}
119
120pub fn extract_request_true_client_ip<B>(request: &Request<B>) -> Result<Option<IpAddr>, Error> {
126 extract_header_true_client_ip(request.headers())
127}
128
129pub fn extract_header_x_envoy_external_address(
138 headers: &HeaderMap,
139) -> Result<Option<IpAddr>, Error> {
140 extract_single_ip(headers, &X_ENVOY_EXTERNAL_ADDRESS)
141}
142
143pub fn extract_request_x_envoy_external_address<B>(
149 request: &Request<B>,
150) -> Result<Option<IpAddr>, Error> {
151 extract_header_x_envoy_external_address(request.headers())
152}
153
154pub fn extract_header_x_real_ip(headers: &HeaderMap) -> Result<Option<IpAddr>, Error> {
161 extract_single_ip(headers, &X_REAL_IP)
162}
163
164pub fn extract_request_x_real_ip<B>(request: &Request<B>) -> Result<Option<IpAddr>, Error> {
170 extract_header_x_real_ip(request.headers())
171}
172
173fn extract_single_ip(headers: &HeaderMap, name: &HeaderName) -> Result<Option<IpAddr>, Error> {
174 extract_single_header_text(headers, name)?
175 .map(|value| parse_ip(value, name))
176 .transpose()
177}
178
179fn parse_ip(value: &str, name: &HeaderName) -> Result<IpAddr, Error> {
180 value
181 .trim()
182 .parse()
183 .map_err(|_| Error::invalid_header(name.clone()))
184}
185
186fn parse_cloudfront_viewer_address(value: &str) -> Result<IpAddr, Error> {
187 let value = value.trim();
188 if let Ok(address) = value.parse::<SocketAddr>() {
189 return Ok(address.ip());
190 }
191
192 let (address, port) = value
193 .rsplit_once(':')
194 .ok_or_else(|| Error::invalid_header(CLOUDFRONT_VIEWER_ADDRESS))?;
195 port.parse::<u16>()
196 .map_err(|_| Error::invalid_header(CLOUDFRONT_VIEWER_ADDRESS))?;
197 address
198 .parse()
199 .map_err(|_| Error::invalid_header(CLOUDFRONT_VIEWER_ADDRESS))
200}
201
202#[cfg(test)]
203mod tests {
204 use http::{HeaderMap, HeaderValue};
205
206 use super::*;
207
208 type Extractor = fn(&HeaderMap) -> Result<Option<IpAddr>, Error>;
209
210 fn ordinary_extractors() -> [(HeaderName, Extractor); 5] {
211 [
212 (CF_CONNECTING_IP, extract_header_cf_connecting_ip),
213 (FLY_CLIENT_IP, extract_header_fly_client_ip),
214 (TRUE_CLIENT_IP, extract_header_true_client_ip),
215 (
216 X_ENVOY_EXTERNAL_ADDRESS,
217 extract_header_x_envoy_external_address,
218 ),
219 (X_REAL_IP, extract_header_x_real_ip),
220 ]
221 }
222
223 #[test]
224 fn single_ip_fields_handle_missing_valid_and_invalid_values() {
225 for (name, extract) in ordinary_extractors() {
226 let mut headers = HeaderMap::new();
227 assert_eq!(extract(&headers).unwrap(), None);
228
229 headers.insert(&name, " 2001:db8::1 ".parse().unwrap());
230 assert_eq!(
231 extract(&headers).unwrap(),
232 Some("2001:db8::1".parse().unwrap())
233 );
234
235 headers.insert(&name, "not-an-ip".parse().unwrap());
236 let error = extract(&headers).unwrap_err();
237 assert!(matches!(error, Error::InvalidHeader { .. }));
238 assert!(!error.to_string().contains("not-an-ip"));
239 }
240 }
241
242 #[test]
243 fn every_single_ip_field_rejects_duplicates() {
244 for (name, extract) in ordinary_extractors() {
245 let mut headers = HeaderMap::new();
246 headers.append(&name, "192.0.2.1".parse().unwrap());
247 headers.append(&name, "198.51.100.2".parse().unwrap());
248 assert!(matches!(
249 extract(&headers),
250 Err(Error::DuplicateHeader { .. })
251 ));
252 }
253 }
254
255 #[test]
256 fn cloudfront_viewer_address_handles_ipv4_and_ipv6_with_ports() {
257 let mut headers = HeaderMap::new();
258 assert_eq!(
259 extract_header_cloudfront_viewer_address(&headers).unwrap(),
260 None
261 );
262
263 for (value, expected) in [
264 ("198.51.100.10:46532", "198.51.100.10"),
265 ("2001:db8::abcd:1234", "2001:db8::abcd"),
266 ("[2001:db8::17]:4711", "2001:db8::17"),
267 ] {
268 headers.insert(&CLOUDFRONT_VIEWER_ADDRESS, value.parse().unwrap());
269 assert_eq!(
270 extract_header_cloudfront_viewer_address(&headers).unwrap(),
271 Some(expected.parse().unwrap())
272 );
273 }
274
275 for value in ["198.51.100.10", "198.51.100.10:not-a-port", "not-an-ip:80"] {
276 headers.insert(&CLOUDFRONT_VIEWER_ADDRESS, value.parse().unwrap());
277 let error = extract_header_cloudfront_viewer_address(&headers).unwrap_err();
278 assert!(matches!(error, Error::InvalidHeader { .. }));
279 assert!(!error.to_string().contains(value));
280 }
281
282 headers.clear();
283 headers.append(
284 &CLOUDFRONT_VIEWER_ADDRESS,
285 "198.51.100.10:443".parse().unwrap(),
286 );
287 headers.append(
288 &CLOUDFRONT_VIEWER_ADDRESS,
289 "198.51.100.11:443".parse().unwrap(),
290 );
291 assert!(matches!(
292 extract_header_cloudfront_viewer_address(&headers),
293 Err(Error::DuplicateHeader { .. })
294 ));
295 }
296
297 #[test]
298 fn all_fields_reject_non_text_without_echoing_values() {
299 let mut cases = ordinary_extractors().to_vec();
300 cases.push((
301 CLOUDFRONT_VIEWER_ADDRESS,
302 extract_header_cloudfront_viewer_address,
303 ));
304
305 for (name, extract) in cases {
306 let mut headers = HeaderMap::new();
307 headers.insert(&name, HeaderValue::from_bytes(&[0xff]).unwrap());
308 let error = extract(&headers).unwrap_err();
309 assert!(matches!(error, Error::InvalidHeader { .. }));
310 assert!(!error.to_string().contains("255"));
311 }
312 }
313
314 #[test]
315 fn request_entry_points_delegate_to_header_extractors() {
316 let request = Request::builder()
317 .header(&CF_CONNECTING_IP, "192.0.2.1")
318 .header(&CLOUDFRONT_VIEWER_ADDRESS, "198.51.100.2:443")
319 .header(&FLY_CLIENT_IP, "203.0.113.3")
320 .header(&TRUE_CLIENT_IP, "192.0.2.4")
321 .header(&X_ENVOY_EXTERNAL_ADDRESS, "198.51.100.5")
322 .header(&X_REAL_IP, "203.0.113.6")
323 .body(())
324 .unwrap();
325
326 assert_eq!(
327 extract_request_cf_connecting_ip(&request).unwrap(),
328 Some("192.0.2.1".parse().unwrap())
329 );
330 assert_eq!(
331 extract_request_cloudfront_viewer_address(&request).unwrap(),
332 Some("198.51.100.2".parse().unwrap())
333 );
334 assert_eq!(
335 extract_request_fly_client_ip(&request).unwrap(),
336 Some("203.0.113.3".parse().unwrap())
337 );
338 assert_eq!(
339 extract_request_true_client_ip(&request).unwrap(),
340 Some("192.0.2.4".parse().unwrap())
341 );
342 assert_eq!(
343 extract_request_x_envoy_external_address(&request).unwrap(),
344 Some("198.51.100.5".parse().unwrap())
345 );
346 assert_eq!(
347 extract_request_x_real_ip(&request).unwrap(),
348 Some("203.0.113.6".parse().unwrap())
349 );
350 }
351}