1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
use http::header::HeaderMap;
use std::net::IpAddr;

/// Extension trait for [`HeaderMap`](http::HeaderMap).
pub trait HeaderMapExt {
    /// Ges the string corresponding to the key.
    fn get_str(&self, key: &str) -> Option<&str>;

    /// Gets the destination host.
    ///
    /// It is determined in the following priority:
    ///
    /// 1. `Forwarded` header `host` key
    /// 2. The first `X-Forwarded-Host` header
    /// 3. `Host` header
    fn get_host(&self) -> Option<&str> {
        self.get_str("forwarded")
            .and_then(|s| {
                s.split(';').find_map(|entry| {
                    let parts = entry.split('=').collect::<Vec<_>>();
                    (parts.len() == 2 && parts[0].eq_ignore_ascii_case("host")).then(|| parts[1])
                })
            })
            .or_else(|| {
                self.get_str("x-forwarded-host")
                    .and_then(|s| s.split(',').next())
            })
            .or_else(|| self.get_str("host"))
    }

    /// Gets the client's remote IP.
    ///
    /// It is determined in the following priority:
    ///
    /// 1. `Forwarded` header `for` key
    /// 2. The first `X-Forwarded-For` header
    /// 3. The first `X-Real-Ip` header
    fn get_client_ip(&self) -> Option<IpAddr> {
        self.get_str("forwarded")
            .and_then(|s| {
                s.split(';').find_map(|entry| {
                    let parts = entry.split('=').collect::<Vec<_>>();
                    (parts.len() == 2 && parts[0].eq_ignore_ascii_case("for")).then(|| parts[1])
                })
            })
            .or_else(|| {
                self.get_str("x-forwarded-for")
                    .and_then(|s| s.split(',').next())
            })
            .or_else(|| self.get_str("x-real-ip"))
            .and_then(|s| s.parse().ok())
    }

    /// Gets the essence of the `content-type` header, discarding the optional parameters.
    fn get_content_type(&self) -> Option<&str> {
        self.get_str("content-type").map(|content_type| {
            if let Some((essence, _)) = content_type.split_once(';') {
                essence
            } else {
                content_type
            }
        })
    }

    /// Checks whether it has a `content-type: application/json` or similar header.
    fn has_json_content_type(&self) -> bool {
        if let Some(content_type) = self.get_str("content-type") {
            crate::helper::check_json_content_type(content_type)
        } else {
            false
        }
    }
}

impl HeaderMapExt for HeaderMap {
    #[inline]
    fn get_str(&self, key: &str) -> Option<&str> {
        self.get(key).and_then(|v| v.to_str().ok())
    }
}