Skip to main content

bios_basic/helper/
request_helper.rs

1//! Http request helper
2//!
3//! Http请求辅助操作
4use std::{
5    collections::HashMap,
6    net::{IpAddr, Ipv4Addr},
7    str::FromStr,
8};
9
10use itertools::Itertools;
11use tardis::{
12    basic::{dto::TardisContext, result::TardisResult},
13    web::poem::{http::header::FORWARDED, Request},
14};
15
16pub const REMOTE_ADDR: &str = "remote-addr";
17
18/// Add ip to context
19pub async fn add_ip(ip: Option<String>, ctx: &TardisContext) -> TardisResult<()> {
20    if let Some(ip) = ip {
21        ctx.add_ext(REMOTE_ADDR, &ip).await?;
22    }
23    Ok(())
24}
25
26/// Try to set real ip from request to context
27pub async fn try_set_real_ip_from_req_to_ctx(request: &Request, ctx: &TardisContext) -> TardisResult<()> {
28    ctx.add_ext(REMOTE_ADDR, &try_get_real_ip_from_req(request).await?.unwrap_or_default()).await?;
29    Ok(())
30}
31
32/// Parse the Forwarded header to get the IP
33///
34/// Forwarded format: `Forwarded: by=<identifier>; for=<identifier><,for=<identifier>>; host=<host>; proto=<http|https>`
35///
36/// ```
37/// use bios_basic::helper::request_helper::parse_forwarded_ip;
38/// assert_eq!(parse_forwarded_ip("Forwarded: for=192.168.0.11; proto=http").unwrap().to_string(),"192.168.0.11");
39/// assert_eq!(parse_forwarded_ip("Forwarded: for=192.168.0.9, 192.168.0.11; proto=http").unwrap().to_string(), "192.168.0.9");
40/// assert_eq!(parse_forwarded_ip("Forwarded: proto=http; for=192.168.0.12").unwrap().to_string(), "192.168.0.12");
41/// assert_eq!(parse_forwarded_ip("Forwarded: for=192.168.0.10").unwrap().to_string(), "192.168.0.10");
42/// assert_eq!(parse_forwarded_ip("Forwarded: proto=http; for=192.168.0"), None);
43/// ```
44pub fn parse_forwarded_ip(forwarded_value: &str) -> Option<IpAddr> {
45    forwarded_value.strip_prefix("Forwarded: ").and_then(|forwarded_value| {
46        forwarded_value
47            .split(';')
48            .find(|part| part.trim().starts_with("for="))
49            .and_then(|part| part.trim()[4..].split(',').next().and_then(|ip_str| IpAddr::from_str(ip_str).ok()))
50    })
51}
52
53/// Convert IPv4-mapped IPv6 to ipv4
54/// e.g
55/// ::ffff:192.168.0.11 => 192.168.0.11
56/// ```
57/// use std::{
58/// net::IpAddr,
59/// str::FromStr,
60/// };
61/// use bios_basic::helper::request_helper::mapped_ipv6_to_ipv4;
62/// assert_eq!(mapped_ipv6_to_ipv4(IpAddr::from_str("::ffff:192.168.0.11").unwrap()),IpAddr::from_str("192.168.0.11").unwrap() );
63/// assert_eq!(mapped_ipv6_to_ipv4(IpAddr::from_str("::ffff:c0a8:000b").unwrap()),IpAddr::from_str("192.168.0.11").unwrap() );
64/// assert_eq!(mapped_ipv6_to_ipv4(IpAddr::from_str("192.168.0.11").unwrap()),IpAddr::from_str("192.168.0.11").unwrap());
65/// assert_eq!(mapped_ipv6_to_ipv4(IpAddr::from_str("fd00::1a2b:3c4d:5e6f:7a8b").unwrap()),IpAddr::from_str("fd00::1a2b:3c4d:5e6f:7a8b").unwrap());
66/// assert_eq!(mapped_ipv6_to_ipv4(IpAddr::from_str("::1").unwrap()),IpAddr::from_str("::1").unwrap());
67/// ```
68pub fn mapped_ipv6_to_ipv4(ip_addr: IpAddr) -> IpAddr {
69    match ip_addr {
70        IpAddr::V6(ip_addr) => {
71            let segments = ip_addr.segments();
72            if segments[0] == 0 && segments[1] == 0 && segments[2] == 0 && segments[3] == 0 && segments[4] == 0 && segments[5] == 0xffff {
73                // 提取并返回 IPv4 地址部分
74                IpAddr::V4(Ipv4Addr::new(ip_addr.octets()[12], ip_addr.octets()[13], ip_addr.octets()[14], ip_addr.octets()[15]))
75            } else {
76                IpAddr::V6(ip_addr)
77            }
78        }
79        IpAddr::V4(ip_addr) => IpAddr::V4(ip_addr),
80    }
81}
82
83/// Try to get real ip from request
84///
85/// This method only parses the main request headers and cannot guarantee that the real IP can be obtained.
86pub async fn try_get_real_ip_from_req(request: &Request) -> TardisResult<Option<String>> {
87    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
88    if let Some(forwarded_header) = request.headers().get(FORWARDED) {
89        if let Ok(forwarded_value) = forwarded_header.to_str() {
90            if let Some(ip) = parse_forwarded_ip(forwarded_value.trim()) {
91                return Ok(Some(mapped_ipv6_to_ipv4(ip).to_string()));
92            }
93        }
94    }
95    if let Some(xff_header) = request.headers().get("X-Forwarded-For") {
96        if let Ok(xff_value) = xff_header.to_str() {
97            if let Some(ip) = xff_value.split(',').next().and_then(|s| IpAddr::from_str(s.trim()).ok()) {
98                return Ok(Some(mapped_ipv6_to_ipv4(ip).to_string()));
99            }
100        }
101    }
102    if let Some(xrp_header) = request.headers().get("X-Real-IP") {
103        if let Ok(xrp_value) = xrp_header.to_str() {
104            if let Some(ip) = xrp_value.split(',').next().and_then(|s| IpAddr::from_str(s.trim()).ok()) {
105                return Ok(Some(mapped_ipv6_to_ipv4(ip).to_string()));
106            }
107        }
108    }
109    Ok(request.remote_addr().as_socket_addr().map(|addr| mapped_ipv6_to_ipv4(addr.ip()).to_string()))
110}
111
112/// Get real ip from context
113pub async fn get_real_ip_from_ctx(ctx: &TardisContext) -> TardisResult<Option<String>> {
114    ctx.get_ext(REMOTE_ADDR).await
115}
116
117/// Sort query string and convert to lowercase
118pub fn sort_query(query: &str) -> String {
119    if query.is_empty() {
120        return "".to_string();
121    }
122    query.split('&').sorted_by(|a, b| Ord::cmp(&a.to_lowercase(), &b.to_lowercase())).join("&")
123}
124
125/// Sort query and convert to lowercase
126pub fn sort_hashmap_query(query: HashMap<String, String>) -> String {
127    if query.is_empty() {
128        return "".to_string();
129    }
130    query.iter().map(|a| format!("{}={}", a.0, a.1)).sorted_by(|a, b| Ord::cmp(&a.to_lowercase(), &b.to_lowercase())).join("&")
131}