1#![doc = include_str!("../README.md")]
2use std::net::IpAddr;
3
4pub use error::Error;
5use http::{HeaderMap, HeaderName};
6
7type Result<T> = std::result::Result<T, Error>;
8
9pub fn cf_connecting_ip(header_map: &HeaderMap) -> Result<IpAddr> {
11 ip_from_single_header(header_map, &HeaderName::from_static("cf-connecting-ip"))
12}
13
14pub fn cloudfront_viewer_address(header_map: &HeaderMap) -> Result<IpAddr> {
16 const HEADER_NAME: HeaderName = HeaderName::from_static("cloudfront-viewer-address");
17
18 fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
19 header_value
26 .rsplit_once(':')
27 .map(|(ip, _port)| ip)
28 .ok_or_else(|| Error::MalformedHeaderValue {
29 header_name: HEADER_NAME,
30 header_value: header_value.to_owned(),
31 })?
32 .trim()
33 .parse::<IpAddr>()
34 .map_err(|_| Error::MalformedHeaderValue {
35 header_name: HEADER_NAME,
36 header_value: header_value.to_owned(),
37 })
38 }
39
40 let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
41 ip_from_header_value(header_value.0)
42}
43
44pub fn fly_client_ip(header_map: &HeaderMap) -> Result<IpAddr> {
50 ip_from_single_header(header_map, &HeaderName::from_static("fly-client-ip"))
51}
52
53#[cfg(feature = "forwarded-header")]
54pub fn rightmost_forwarded(header_map: &HeaderMap) -> Result<IpAddr> {
56 const HEADER_NAME: HeaderName = HeaderName::from_static("forwarded");
57
58 fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
59 use forwarded_header_value::{ForwardedHeaderValue, Identifier};
60
61 let stanza = ForwardedHeaderValue::from_forwarded(header_value)
62 .map_err(|_| Error::MalformedHeaderValue {
63 header_name: HEADER_NAME,
64 header_value: header_value.to_owned(),
65 })?
66 .into_iter()
67 .last()
68 .ok_or_else(|| Error::MalformedHeaderValue {
69 header_name: HEADER_NAME,
70 header_value: header_value.to_owned(),
71 })?;
72
73 let forwarded_for = stanza.forwarded_for.ok_or_else(|| Error::ForwardedNoFor {
74 header_value: header_value.to_owned(),
75 })?;
76
77 match forwarded_for {
78 Identifier::SocketAddr(a) => Ok(a.ip()),
79 Identifier::IpAddr(ip) => Ok(ip),
80 Identifier::String(_) => Err(Error::ForwardedObfuscated {
81 header_value: header_value.to_owned(),
82 }),
83 Identifier::Unknown => Err(Error::ForwardedUnknown {
84 header_value: header_value.to_owned(),
85 }),
86 }
87 }
88
89 let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
90 ip_from_header_value(header_value.0)
91}
92
93pub fn rightmost_x_forwarded_for(header_map: &HeaderMap) -> Result<IpAddr> {
96 const HEADER_NAME: HeaderName = HeaderName::from_static("x-forwarded-for");
97
98 fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
99 header_value
100 .split(',')
101 .next_back()
102 .ok_or_else(|| Error::MalformedHeaderValue {
103 header_name: HEADER_NAME,
104 header_value: header_value.to_owned(),
105 })?
106 .trim()
107 .parse::<IpAddr>()
108 .map_err(|_| Error::MalformedHeaderValue {
109 header_name: HEADER_NAME,
110 header_value: header_value.to_owned(),
111 })
112 }
113
114 let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
115 ip_from_header_value(header_value.0)
116}
117
118pub fn true_client_ip(header_map: &HeaderMap) -> Result<IpAddr> {
120 ip_from_single_header(header_map, &HeaderName::from_static("true-client-ip"))
121}
122
123pub fn x_envoy_external_address(header_map: &HeaderMap) -> Result<IpAddr> {
125 ip_from_single_header(
126 header_map,
127 &HeaderName::from_static("x-envoy-external-address"),
128 )
129}
130
131pub fn x_real_ip(header_map: &HeaderMap) -> Result<IpAddr> {
133 ip_from_single_header(header_map, &HeaderName::from_static("x-real-ip"))
134}
135
136#[derive(Debug)]
138struct AsciiHeaderValue<'a>(&'a str);
139
140impl<'a> AsciiHeaderValue<'a> {
141 fn of_single_header(header_map: &'a HeaderMap, header_name: &HeaderName) -> Result<Self> {
145 let mut iter = header_map.get_all(header_name).into_iter();
146
147 let Some(header_value) = iter.next() else {
148 return Err(Error::AbsentHeader {
149 header_name: header_name.to_owned(),
150 });
151 };
152
153 if iter.next().is_some() {
154 return Err(Error::SingleHeaderRequired {
155 header_name: header_name.to_owned(),
156 });
157 }
158
159 header_value
160 .to_str()
161 .map_err(|_| Error::NonAsciiHeaderValue {
162 header_name: header_name.to_owned(),
163 })
164 .map(Self)
165 }
166
167 fn of_last_header(header_map: &'a HeaderMap, header_name: &HeaderName) -> Result<Self> {
169 header_map
170 .get_all(header_name)
171 .into_iter()
172 .next_back()
173 .ok_or_else(|| Error::AbsentHeader {
174 header_name: header_name.to_owned(),
175 })?
176 .to_str()
177 .map_err(|_| Error::NonAsciiHeaderValue {
178 header_name: header_name.to_owned(),
179 })
180 .map(Self)
181 }
182
183 fn parse_ip(&self, header_name: &HeaderName) -> Result<IpAddr> {
185 self.0
186 .trim()
187 .parse()
188 .map_err(|_| Error::MalformedHeaderValue {
189 header_name: header_name.to_owned(),
190 header_value: self.0.to_owned(),
191 })
192 }
193}
194
195fn ip_from_single_header(header_map: &HeaderMap, header_name: &HeaderName) -> Result<IpAddr> {
198 AsciiHeaderValue::of_single_header(header_map, header_name)?.parse_ip(header_name)
199}
200
201mod error {
202 use std::fmt;
203
204 use http::HeaderName;
205
206 #[derive(Debug, PartialEq)]
208 pub enum Error {
209 AbsentHeader {
211 header_name: HeaderName,
213 },
214 NonAsciiHeaderValue {
216 header_name: HeaderName,
218 },
219 MalformedHeaderValue {
221 header_name: HeaderName,
223 header_value: String,
225 },
226 SingleHeaderRequired {
233 header_name: HeaderName,
235 },
236 #[cfg(feature = "forwarded-header")]
237 ForwardedNoFor {
239 header_value: String,
241 },
242 #[cfg(feature = "forwarded-header")]
243 ForwardedObfuscated {
245 header_value: String,
247 },
248 #[cfg(feature = "forwarded-header")]
249 ForwardedUnknown {
251 header_value: String,
253 },
254 }
255
256 impl fmt::Display for Error {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match self {
259 Self::AbsentHeader { header_name } => {
260 write!(f, "Missing required header: {header_name}")
261 }
262 Self::NonAsciiHeaderValue { header_name } => write!(
263 f,
264 "Header value contains non-ASCII characters: {header_name}",
265 ),
266 Self::MalformedHeaderValue {
267 header_name,
268 header_value,
269 } => write!(
270 f,
271 "Malformed header value for `{header_name}`: {header_value}",
272 ),
273 Self::SingleHeaderRequired { header_name } => write!(
274 f,
275 "Multiple occurrences of the header aren't allowed: {header_name}"
276 ),
277 #[cfg(feature = "forwarded-header")]
278 Self::ForwardedNoFor { header_value } => write!(
279 f,
280 "`Forwarded` header missing `for` directive: {header_value}",
281 ),
282 #[cfg(feature = "forwarded-header")]
283 Self::ForwardedObfuscated { header_value } => write!(
284 f,
285 "`Forwarded` header contains obfuscated IP: {header_value}",
286 ),
287 #[cfg(feature = "forwarded-header")]
288 Self::ForwardedUnknown { header_value } => write!(
289 f,
290 "`Forwarded` header contains unknown identifier: {header_value}",
291 ),
292 }
293 }
294 }
295
296 impl std::error::Error for Error {}
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 const VALID_IPV4: &str = "1.2.3.4";
304 const VALID_IPV6: &str = "1:23:4567:89ab:c:d:e:f";
305
306 fn headers<'a>(items: impl IntoIterator<Item = (&'a str, &'a str)>) -> HeaderMap {
307 HeaderMap::from_iter(
308 items
309 .into_iter()
310 .map(|(name, value)| (name.parse().unwrap(), value.parse().unwrap())),
311 )
312 }
313
314 #[test]
315 fn test_ascii_header_value_of_last_header() {
316 let header_name_str = "my-header";
317 let header_name = HeaderName::from_static(header_name_str);
318
319 assert_eq!(
320 AsciiHeaderValue::of_last_header(&headers([]), &header_name).unwrap_err(),
321 Error::AbsentHeader {
322 header_name: header_name.clone()
323 }
324 );
325
326 assert_eq!(
327 AsciiHeaderValue::of_last_header(&headers([(header_name_str, "ы")]), &header_name)
328 .unwrap_err(),
329 Error::NonAsciiHeaderValue {
330 header_name: header_name.clone()
331 }
332 );
333
334 assert_eq!(
335 AsciiHeaderValue::of_last_header(&headers([(header_name_str, "foo")]), &header_name)
336 .unwrap()
337 .0,
338 "foo",
339 "single valid header"
340 );
341
342 assert_eq!(
343 AsciiHeaderValue::of_last_header(
344 &headers([(header_name_str, "foo"), (header_name_str, "bar")]),
345 &header_name
346 )
347 .unwrap()
348 .0,
349 "bar",
350 "multiple valid headers"
351 );
352 }
353
354 #[test]
355 fn test_ascii_header_value_of_single_header() {
356 let header_name_str = "my-header";
357 let header_name = HeaderName::from_static(header_name_str);
358
359 assert_eq!(
360 AsciiHeaderValue::of_single_header(&headers([]), &header_name).unwrap_err(),
361 Error::AbsentHeader {
362 header_name: header_name.clone()
363 }
364 );
365
366 assert_eq!(
367 AsciiHeaderValue::of_single_header(&headers([(header_name_str, "ы")]), &header_name)
368 .unwrap_err(),
369 Error::NonAsciiHeaderValue {
370 header_name: header_name.clone()
371 }
372 );
373
374 assert_eq!(
375 AsciiHeaderValue::of_single_header(
376 &headers([(header_name_str, "foo"), (header_name_str, "bar")]),
377 &header_name
378 )
379 .unwrap_err(),
380 Error::SingleHeaderRequired {
381 header_name: header_name.clone()
382 }
383 );
384
385 assert_eq!(
386 AsciiHeaderValue::of_single_header(&headers([(header_name_str, "foo")]), &header_name)
387 .unwrap()
388 .0,
389 "foo"
390 );
391 }
392
393 #[test]
394 fn test_cf_connecting_ip() {
395 let header = "cf-connecting-ip";
396
397 assert_eq!(
398 cf_connecting_ip(&headers([])).unwrap_err(),
399 Error::AbsentHeader {
400 header_name: HeaderName::from_static(header)
401 }
402 );
403 assert_eq!(
404 cf_connecting_ip(&headers([(header, "ы")])).unwrap_err(),
405 Error::NonAsciiHeaderValue {
406 header_name: HeaderName::from_static(header)
407 }
408 );
409 assert_eq!(
410 cf_connecting_ip(&headers([(header, "foo")])).unwrap_err(),
411 Error::MalformedHeaderValue {
412 header_name: HeaderName::from_static(header),
413 header_value: "foo".into(),
414 }
415 );
416
417 assert_eq!(
418 cf_connecting_ip(&headers([(header, VALID_IPV4)])).unwrap(),
419 VALID_IPV4.parse::<IpAddr>().unwrap()
420 );
421 assert_eq!(
422 cf_connecting_ip(&headers([(header, VALID_IPV6)])).unwrap(),
423 VALID_IPV6.parse::<IpAddr>().unwrap()
424 );
425 }
426
427 #[test]
428 fn test_cloudfront_viewer_address() {
429 let header = "cloudfront-viewer-address";
430
431 assert_eq!(
432 cloudfront_viewer_address(&headers([])).unwrap_err(),
433 Error::AbsentHeader {
434 header_name: HeaderName::from_static(header)
435 }
436 );
437 assert_eq!(
438 cloudfront_viewer_address(&headers([(header, "ы")])).unwrap_err(),
439 Error::NonAsciiHeaderValue {
440 header_name: HeaderName::from_static(header)
441 }
442 );
443 assert_eq!(
444 cloudfront_viewer_address(&headers([(header, VALID_IPV4)])).unwrap_err(),
445 Error::MalformedHeaderValue {
446 header_name: HeaderName::from_static(header),
447 header_value: VALID_IPV4.into(),
448 }
449 );
450 assert_eq!(
451 cloudfront_viewer_address(&headers([(header, "foo:8000")])).unwrap_err(),
452 Error::MalformedHeaderValue {
453 header_name: HeaderName::from_static(header),
454 header_value: "foo:8000".into(),
455 }
456 );
457
458 let valid_header_value_v4 = format!("{VALID_IPV4}:8000");
459 let valid_header_value_v6 = format!("{VALID_IPV6}:8000");
460 assert_eq!(
461 cloudfront_viewer_address(&headers([(header, valid_header_value_v4.as_ref())]))
462 .unwrap(),
463 VALID_IPV4.parse::<IpAddr>().unwrap()
464 );
465 assert_eq!(
466 cloudfront_viewer_address(&headers([(header, valid_header_value_v6.as_ref())]))
467 .unwrap(),
468 VALID_IPV6.parse::<IpAddr>().unwrap()
469 );
470 }
471
472 #[test]
473 fn test_fly_client_ip() {
474 let header = "fly-client-ip";
475
476 assert_eq!(
477 fly_client_ip(&headers([])).unwrap_err(),
478 Error::AbsentHeader {
479 header_name: HeaderName::from_static(header)
480 }
481 );
482 assert_eq!(
483 fly_client_ip(&headers([(header, "ы")])).unwrap_err(),
484 Error::NonAsciiHeaderValue {
485 header_name: HeaderName::from_static(header)
486 }
487 );
488 assert_eq!(
489 fly_client_ip(&headers([(header, "foo")])).unwrap_err(),
490 Error::MalformedHeaderValue {
491 header_name: HeaderName::from_static(header),
492 header_value: "foo".into(),
493 }
494 );
495
496 assert_eq!(
497 fly_client_ip(&headers([(header, VALID_IPV4)])).unwrap(),
498 VALID_IPV4.parse::<IpAddr>().unwrap()
499 );
500 assert_eq!(
501 fly_client_ip(&headers([(header, VALID_IPV6)])).unwrap(),
502 VALID_IPV6.parse::<IpAddr>().unwrap()
503 );
504 }
505
506 #[cfg(feature = "forwarded-header")]
507 #[test]
508 fn test_rightmost_forwarded() {
509 let header = "forwarded";
510
511 assert_eq!(
512 rightmost_forwarded(&headers([])).unwrap_err(),
513 Error::AbsentHeader {
514 header_name: HeaderName::from_static(header)
515 }
516 );
517 assert_eq!(
518 rightmost_forwarded(&headers([(header, "ы")])).unwrap_err(),
519 Error::NonAsciiHeaderValue {
520 header_name: HeaderName::from_static(header)
521 }
522 );
523 assert_eq!(
524 rightmost_forwarded(&headers([(header, "foo")])).unwrap_err(),
525 Error::MalformedHeaderValue {
526 header_name: HeaderName::from_static(header),
527 header_value: "foo".into(),
528 }
529 );
530 assert_eq!(
531 rightmost_forwarded(&headers([
532 (header, format!("for={VALID_IPV4}").as_ref()),
533 (header, "proto=http"),
534 ]))
535 .unwrap_err(),
536 Error::ForwardedNoFor {
537 header_value: "proto=http".into(),
538 }
539 );
540 assert_eq!(
541 rightmost_forwarded(&headers([(header, "for=unknown")])).unwrap_err(),
542 Error::ForwardedUnknown {
543 header_value: "for=unknown".into(),
544 }
545 );
546 assert_eq!(
547 rightmost_forwarded(&headers([(header, "for=_foo")])).unwrap_err(),
548 Error::ForwardedObfuscated {
549 header_value: "for=_foo".into(),
550 }
551 );
552
553 assert_eq!(
554 rightmost_forwarded(&headers([
555 (header, "proto=http"),
556 (header, format!("for={VALID_IPV4};proto=http").as_ref()),
557 ]))
558 .unwrap(),
559 VALID_IPV4.parse::<IpAddr>().unwrap()
560 );
561 assert_eq!(
562 rightmost_forwarded(&headers([(
563 header,
564 format!("for={VALID_IPV4}:8000").as_ref()
565 ),]))
566 .unwrap(),
567 VALID_IPV4.parse::<IpAddr>().unwrap()
568 );
569
570 assert_eq!(
571 rightmost_forwarded(&headers([(header, format!("for={VALID_IPV6}").as_ref()),]))
572 .unwrap(),
573 VALID_IPV6.parse::<IpAddr>().unwrap()
574 );
575 assert_eq!(
576 rightmost_forwarded(&headers([(
577 header,
578 format!("for=[{VALID_IPV6}]:8000").as_ref()
579 ),]))
580 .unwrap(),
581 VALID_IPV6.parse::<IpAddr>().unwrap()
582 );
583 }
584
585 #[test]
586 fn test_rightmost_x_forwarded_for() {
587 let header = "x-forwarded-for";
588
589 assert_eq!(
590 rightmost_x_forwarded_for(&headers([])).unwrap_err(),
591 Error::AbsentHeader {
592 header_name: HeaderName::from_static(header)
593 }
594 );
595 assert_eq!(
596 rightmost_x_forwarded_for(&headers([(header, "ы")])).unwrap_err(),
597 Error::NonAsciiHeaderValue {
598 header_name: HeaderName::from_static(header)
599 }
600 );
601 assert_eq!(
602 rightmost_x_forwarded_for(&headers([(header, "1.2.3.4,foo")])).unwrap_err(),
603 Error::MalformedHeaderValue {
604 header_name: HeaderName::from_static(header),
605 header_value: "1.2.3.4,foo".into(),
606 }
607 );
608
609 assert_eq!(
610 rightmost_x_forwarded_for(&headers([(header, format!("foo,{VALID_IPV4}").as_ref())]))
611 .unwrap(),
612 VALID_IPV4.parse::<IpAddr>().unwrap()
613 );
614 assert_eq!(
615 rightmost_x_forwarded_for(&headers([(header, VALID_IPV6)])).unwrap(),
616 VALID_IPV6.parse::<IpAddr>().unwrap()
617 );
618 }
619
620 #[test]
621 fn test_true_client_ip() {
622 let header = "true-client-ip";
623
624 assert_eq!(
625 true_client_ip(&headers([])).unwrap_err(),
626 Error::AbsentHeader {
627 header_name: HeaderName::from_static(header)
628 }
629 );
630 assert_eq!(
631 true_client_ip(&headers([(header, "ы")])).unwrap_err(),
632 Error::NonAsciiHeaderValue {
633 header_name: HeaderName::from_static(header)
634 }
635 );
636 assert_eq!(
637 true_client_ip(&headers([(header, "foo")])).unwrap_err(),
638 Error::MalformedHeaderValue {
639 header_name: HeaderName::from_static(header),
640 header_value: "foo".into(),
641 }
642 );
643
644 assert_eq!(
645 true_client_ip(&headers([(header, VALID_IPV4)])).unwrap(),
646 VALID_IPV4.parse::<IpAddr>().unwrap()
647 );
648 assert_eq!(
649 true_client_ip(&headers([(header, VALID_IPV6)])).unwrap(),
650 VALID_IPV6.parse::<IpAddr>().unwrap()
651 );
652 }
653
654 #[test]
655 fn test_x_envoy_external_address() {
656 let header = "x-envoy-external-address";
657
658 assert_eq!(
659 x_envoy_external_address(&headers([])).unwrap_err(),
660 Error::AbsentHeader {
661 header_name: HeaderName::from_static(header)
662 }
663 );
664 assert_eq!(
665 x_envoy_external_address(&headers([(header, "ы")])).unwrap_err(),
666 Error::NonAsciiHeaderValue {
667 header_name: HeaderName::from_static(header)
668 }
669 );
670 assert_eq!(
671 x_envoy_external_address(&headers([(header, "foo")])).unwrap_err(),
672 Error::MalformedHeaderValue {
673 header_name: HeaderName::from_static(header),
674 header_value: "foo".into(),
675 }
676 );
677
678 assert_eq!(
679 x_envoy_external_address(&headers([(header, VALID_IPV4)])).unwrap(),
680 VALID_IPV4.parse::<IpAddr>().unwrap()
681 );
682 assert_eq!(
683 x_envoy_external_address(&headers([(header, VALID_IPV6)])).unwrap(),
684 VALID_IPV6.parse::<IpAddr>().unwrap()
685 );
686 }
687
688 #[test]
689 fn test_x_real_ip() {
690 let header = "x-real-ip";
691
692 assert_eq!(
693 x_real_ip(&headers([])).unwrap_err(),
694 Error::AbsentHeader {
695 header_name: HeaderName::from_static(header)
696 }
697 );
698 assert_eq!(
699 x_real_ip(&headers([(header, "ы")])).unwrap_err(),
700 Error::NonAsciiHeaderValue {
701 header_name: HeaderName::from_static(header)
702 }
703 );
704 assert_eq!(
705 x_real_ip(&headers([(header, "foo")])).unwrap_err(),
706 Error::MalformedHeaderValue {
707 header_name: HeaderName::from_static(header),
708 header_value: "foo".into(),
709 }
710 );
711
712 assert_eq!(
713 x_real_ip(&headers([(header, VALID_IPV4)])).unwrap(),
714 VALID_IPV4.parse::<IpAddr>().unwrap()
715 );
716 assert_eq!(
717 x_real_ip(&headers([(header, VALID_IPV6)])).unwrap(),
718 VALID_IPV6.parse::<IpAddr>().unwrap()
719 );
720 }
721}