1use core::fmt;
4use core::net::IpAddr;
5
6use crate::forwarded::{self, parse_forwarded_for, parse_forwarded_for_rev};
7use crate::filter::Filter;
8
9pub use http as http_ext;
11use http_ext::header::FORWARDED;
12
13const FALLBACK_STR: &str = "<non-utf8>";
14pub struct HeaderValueFmt<'a>(http_ext::header::GetAll<'a, http_ext::header::HeaderValue>);
16
17impl fmt::Debug for HeaderValueFmt<'_> {
18 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
19 let mut out = fmt.debug_list();
20 for header in self.0.iter() {
21 match header.to_str() {
22 Ok(header) => out.entry(&header),
23 Err(_) => out.entry(header),
24 };
25 }
26
27 out.finish()
28 }
29}
30
31impl fmt::Display for HeaderValueFmt<'_> {
32 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
33 let mut headers = self.0.iter();
34 if let Some(header) = headers.next() {
35 match header.to_str() {
36 Ok(header) => fmt.write_str(header)?,
37 Err(_) => fmt.write_str(FALLBACK_STR)?,
38 }
39
40 for header in headers {
41 fmt.write_str(" ,")?;
42 match header.to_str() {
43 Ok(header) => fmt.write_str(header)?,
44 Err(_) => fmt.write_str(FALLBACK_STR)?,
45 }
46 }
47 }
48
49 Ok(())
50 }
51}
52
53pub trait HeaderMapClientIp {
55 fn get_header_value_fmt(&self, key: impl http_ext::header::AsHeaderName) -> HeaderValueFmt<'_>;
57
58 fn extract_leftmost_forwarded_ip(&self) -> Option<IpAddr>;
65 fn extract_rightmost_forwarded_ip(&self) -> Option<IpAddr>;
69 fn extract_filtered_forwarded_ip(&self, filter: &impl Filter) -> Option<IpAddr>;
73}
74
75impl HeaderMapClientIp for http_ext::HeaderMap {
76 #[inline(always)]
77 fn get_header_value_fmt(&self, key: impl http_ext::header::AsHeaderName) -> HeaderValueFmt<'_> {
78 HeaderValueFmt(self.get_all(key))
79 }
80
81 #[inline(always)]
82 fn extract_leftmost_forwarded_ip(&self) -> Option<IpAddr> {
83 self.get_all(FORWARDED)
84 .into_iter()
85 .next()
86 .and_then(|header| header.to_str().ok())
87 .and_then(|header| parse_forwarded_for(header).next())
88 .and_then(|node| match node {
89 forwarded::ForwardedNode::Ip(ip) => Some(ip),
90 _ => None
91 })
92 }
93
94 #[inline(always)]
95 fn extract_rightmost_forwarded_ip(&self) -> Option<IpAddr> {
96 self.get_all(FORWARDED)
97 .into_iter()
98 .next_back()
99 .and_then(|header| header.to_str().ok())
100 .and_then(|header| parse_forwarded_for_rev(header).next())
101 .and_then(|node| match node {
102 forwarded::ForwardedNode::Ip(ip) => Some(ip),
103 _ => None
104 })
105 }
106
107 fn extract_filtered_forwarded_ip(&self, filter: &impl Filter) -> Option<IpAddr> {
108 let forwarded = self.get_all(FORWARDED)
109 .into_iter()
110 .rev()
111 .filter_map(|header| header.to_str().ok()).flat_map(|header| parse_forwarded_for_rev(header));
112
113 for node in forwarded {
114 match node {
115 forwarded::ForwardedNode::Ip(ip) => if filter.is_match(ip) {
116 continue
117 } else {
118 return Some(ip)
119 },
120 _ => return None,
121 }
122 }
123
124 None
125 }
126}