1use std::net::SocketAddr;
4use url::Url;
5
6pub fn is_private_ip(ip: &str) -> bool {
20 if let Ok(addr) = ip.parse::<std::net::IpAddr>() {
21 match addr {
22 std::net::IpAddr::V4(ipv4) => {
23 ipv4.is_private()
24 || ipv4.is_loopback()
25 || ipv4.is_link_local()
26 || ipv4.is_unspecified()
27 || ipv4.is_multicast()
28 }
29 std::net::IpAddr::V6(ipv6) => {
30 ipv6.is_loopback()
31 || ipv6.is_unspecified()
32 || ipv6.is_unicast_link_local()
33 || ipv6.is_unique_local()
34 || ipv6.is_multicast()
35 }
36 }
37 } else {
38 false
39 }
40}
41
42pub fn extract_client_ip(headers: &http::HeaderMap, connect_info: &SocketAddr) -> String {
58 if let Some(cf_ip) = headers.get("CF-Connecting-IP").and_then(|h| h.to_str().ok()) {
59 return cf_ip.to_string();
60 }
61
62 if let Some(x_forwarded_for) = headers.get("X-Forwarded-For").and_then(|h| h.to_str().ok()) {
63 for ip in x_forwarded_for.split(',') {
64 let trimmed = ip.trim();
65 if !is_private_ip(trimmed) {
66 return trimmed.to_string();
67 }
68 }
69 }
70
71 let other_headers = ["X-Real-IP", "X-Client-IP", "X-Cluster-Client-IP"];
72 for header_name in &other_headers {
73 if let Some(ip) = headers.get(*header_name).and_then(|h| h.to_str().ok()) {
74 if !is_private_ip(ip) {
75 return ip.to_string();
76 }
77 }
78 }
79
80 let connect_ip = connect_info.ip().to_string();
81 if !is_private_ip(&connect_ip) {
82 return connect_ip;
83 }
84
85 "0.0.0.0".to_string()
86}
87
88pub struct WebExt;
92
93impl WebExt {
94 pub fn domain(url_str: &str) -> Option<String> {
96 Url::parse(url_str)
97 .ok()
98 .and_then(|u| u.host_str().map(|s| s.to_string()))
99 }
100
101 pub fn path(url_str: &str) -> Option<String> {
103 Url::parse(url_str).ok().map(|u| u.path().to_string())
104 }
105
106 pub fn real_ip(headers: &[(String, String)], remote_addr: &str) -> String {
108 for (key, val) in headers {
109 if key.to_lowercase() == "x-forwarded-for" {
110 return val.split(',').next().unwrap_or("").trim().to_string();
111 }
112 if key.to_lowercase() == "x-real-ip" {
113 return val.clone();
114 }
115 }
116 remote_addr
117 .split(':')
118 .next()
119 .unwrap_or(remote_addr)
120 .to_string()
121 }
122 pub fn is_private_ip(ip: &str) -> bool {
124 is_private_ip(ip)
125 }
126 pub fn build_query(params: &[(&str, &str)]) -> String {
128 if params.is_empty() {
129 return String::new();
130 }
131 let parts: Vec<String> = params
132 .iter()
133 .map(|(k, v)| format!("{}={}", urlencoding(k), urlencoding(v)))
134 .collect();
135 format!("?{}", parts.join("&"))
136 }
137}
138
139fn urlencoding(s: &str) -> String {
140 let mut result = String::with_capacity(s.len() * 3);
141 for byte in s.bytes() {
142 match byte {
143 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
144 result.push(byte as char);
145 }
146 _ => {
147 result.push_str(&format!("%{:02X}", byte));
148 }
149 }
150 }
151 result
152}