http_ip/
tonic014.rs

1//! Tonic 0.14 extension module
2
3use core::fmt;
4use core::net::IpAddr;
5
6pub use tonic014 as tonic;
7pub use tonic::metadata::MetadataMap;
8
9use crate::forwarded::{self, parse_forwarded_for, parse_forwarded_for_rev};
10use crate::filter::Filter;
11use crate::shared::FALLBACK_STR;
12
13const FORWARDED: &str = "forwarded";
14
15///FMT formatter for header values
16pub struct MetadataValueFmt<'a>(tonic::metadata::GetAll<'a, tonic::metadata::Ascii>);
17
18impl fmt::Debug for MetadataValueFmt<'_> {
19    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
20        let mut out = fmt.debug_list();
21        for header in self.0.iter() {
22            match header.to_str() {
23                Ok(header) => out.entry(&header),
24                Err(_) => out.entry(header),
25            };
26        }
27
28        out.finish()
29    }
30}
31
32impl fmt::Display for MetadataValueFmt<'_> {
33    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
34        let mut headers = self.0.iter();
35        if let Some(header) = headers.next() {
36            match header.to_str() {
37                Ok(header) => fmt.write_str(header)?,
38                Err(_) => fmt.write_str(FALLBACK_STR)?,
39            }
40
41            for header in headers {
42                fmt.write_str(" ,")?;
43                match header.to_str() {
44                    Ok(header) => fmt.write_str(header)?,
45                    Err(_) => fmt.write_str(FALLBACK_STR)?,
46                }
47            }
48        }
49
50        Ok(())
51    }
52}
53
54///`MetadataMap` extension trait
55pub trait MetadataMapClientIp {
56    ///Retrieves FMT formatter for header value matching provided `key`
57    fn get_header_value_fmt(&self, key: &str) -> MetadataValueFmt<'_>;
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 MetadataMapClientIp for MetadataMap {
76    #[inline(always)]
77    fn get_header_value_fmt(&self, key: &str) -> MetadataValueFmt<'_> {
78        MetadataValueFmt(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}