http_ip/
http.rs

1//! HTTP extension module
2
3use core::fmt;
4use core::net::IpAddr;
5
6use crate::forwarded::{self, parse_forwarded_for, parse_forwarded_for_rev};
7use crate::filter::Filter;
8
9///Re-export of [http](https://crates.io/crates/http)
10pub use http as http_ext;
11use http_ext::header::FORWARDED;
12
13const FALLBACK_STR: &str = "<non-utf8>";
14///FMT formatter for header values
15pub 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
53///`HeaderMap` extension trait
54pub trait HeaderMapClientIp {
55    ///Retrieves FMT formatter for header value matching provided `key`
56    fn get_header_value_fmt(&self, key: impl http_ext::header::AsHeaderName) -> HeaderValueFmt<'_>;
57
58    ///Extracts leftmost client IP with no assumption.
59    ///
60    ///Note that this is generally not reliable as your client might be behind proxy
61    ///Prefer to use `extract_client_ip_with` by filtering out your proxies to find correct IP
62    ///
63    ///Returns `None` if IP is not provided or obfuscated
64    fn extract_leftmost_forwarded_ip(&self) -> Option<IpAddr>;
65    ///Extracts rightmost client IP with no assumption.
66    ///
67    ///Returns `None` if IP is not provided or obfuscated
68    fn extract_rightmost_forwarded_ip(&self) -> Option<IpAddr>;
69    ///Extracts client ip taking rightmost, after filtering out any IP matching `filter`
70    ///
71    ///Returns `None` if IP is not provided or obfuscated
72    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}