Skip to main content

http_ip/
ohkami024.rs

1//! Ohkami 0.24 integration
2use core::{fmt, marker};
3use core::net::IpAddr;
4
5use crate::find_next_ip_after_filter;
6use crate::filter::Filter;
7use crate::forwarded::parse_forwarded_for_rev;
8use ohkami024::{FangProc, Fang};
9use ohkami024::{FromRequest, Request, Response};
10
11#[repr(transparent)]
12#[derive(Copy, Clone)]
13///ClientIp extractor
14///
15///Provided `F` parameter can be used to customize filter selection. Use `nil` type to only extract rightmost IP.
16pub struct ClientIp<F: Filter> {
17    ///Underlying IP addr if available
18    pub inner: Option<IpAddr>,
19    _filter: marker::PhantomData<F>
20}
21
22impl<F: Filter> ClientIp<F> {
23    #[inline(always)]
24    fn new(inner: Option<IpAddr>) -> Self {
25        Self {
26            inner,
27            _filter: marker::PhantomData,
28        }
29    }
30
31    #[inline(always)]
32    ///Access underlying value
33    pub fn into_inner(self) -> Option<IpAddr> {
34        self.inner
35    }
36}
37
38impl<F: Filter> fmt::Debug for ClientIp<F> {
39    #[inline(always)]
40    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
41        fmt::Debug::fmt(&self.inner, fmt)
42    }
43}
44
45impl<'req, F: Send + Sync + Filter + 'static> FromRequest<'req> for ClientIp<F> {
46    ///This extractor will not fail
47    type Error = core::convert::Infallible;
48    ///Extracts `Forwarded` header, or do nothing if not available
49    ///
50    ///If no IP can be determined due to filter or obfuscation, sets `ClientIp::inner` to `None`
51    fn from_request(req: &'req Request) -> Option<Result<Self, Self::Error>> {
52        let filter = req.context.get::<F>()?;
53        //This library is a mess of undocumented functionality, but this method is basically shortcut to getting Forwarded with both possible cases
54        if let Some(ip) = req.headers.forwarded().and_then(|value| find_next_ip_after_filter(parse_forwarded_for_rev(value), filter)) {
55            Some(Ok(ClientIp::new(Some(ip))))
56        } else {
57            //do not return None as it will cause 404 by default, it is best to have your control over this behaviour
58            Some(Ok(ClientIp::new(None)))
59        }
60    }
61}
62
63#[derive(Clone)]
64///Client IP modification middleware implementing [Fang](https://docs.rs/ohkami/latest/ohkami/fang/trait.Fang.html)
65pub struct ClientIpMiddleware<F> {
66    filter: F,
67}
68
69impl<F: Filter + Clone + 'static> ClientIpMiddleware<F> {
70    #[inline(always)]
71    ///Creates new instance
72    pub const fn new(filter: F) -> Self {
73        Self {
74            filter
75        }
76    }
77}
78
79impl<F: Filter + Clone + 'static, I: FangProc> Fang<I> for ClientIpMiddleware<F> {
80    type Proc = ClientIpMiddlewareProc<I, F>;
81    #[inline(always)]
82    fn chain(&self, inner: I) -> Self::Proc {
83        ClientIpMiddlewareProc {
84            inner,
85            filter: self.filter.clone(),
86        }
87    }
88}
89
90///Tracing middleware implementing [FangProc](https://docs.rs/ohkami/latest/ohkami/fang/trait.FancProc.html)
91pub struct ClientIpMiddlewareProc<I, F> {
92    inner: I,
93    filter: F,
94}
95
96impl<I: FangProc, F: Filter + 'static> FangProc for ClientIpMiddlewareProc<I, F> {
97    #[inline(always)]
98    fn bite<'b>(&'b self, req: &'b mut Request) -> impl Future<Output = Response> {
99        if req.ip.is_unspecified() {
100            //Assume ip is set to correct one if it is not default, so we try to look up Forwarded header only if user cannot know IP yet
101            if let Some(ip) = req.headers.forwarded().and_then(|value| find_next_ip_after_filter(parse_forwarded_for_rev(value), &self.filter)) {
102                req.ip = ip;
103            }
104        }
105
106        self.inner.bite(req)
107    }
108}