use std::net::SocketAddr;
use url::Url;
pub fn is_private_ip(ip: &str) -> bool {
if let Ok(addr) = ip.parse::<std::net::IpAddr>() {
match addr {
std::net::IpAddr::V4(ipv4) => {
ipv4.is_private()
|| ipv4.is_loopback()
|| ipv4.is_link_local()
|| ipv4.is_unspecified()
|| ipv4.is_multicast()
}
std::net::IpAddr::V6(ipv6) => {
ipv6.is_loopback()
|| ipv6.is_unspecified()
|| ipv6.is_unicast_link_local()
|| ipv6.is_unique_local()
|| ipv6.is_multicast()
}
}
} else {
false
}
}
pub fn extract_client_ip(headers: &http::HeaderMap, connect_info: &SocketAddr) -> String {
if let Some(cf_ip) = headers.get("CF-Connecting-IP").and_then(|h| h.to_str().ok()) {
return cf_ip.to_string();
}
if let Some(x_forwarded_for) = headers.get("X-Forwarded-For").and_then(|h| h.to_str().ok()) {
for ip in x_forwarded_for.split(',') {
let trimmed = ip.trim();
if !is_private_ip(trimmed) {
return trimmed.to_string();
}
}
}
let other_headers = ["X-Real-IP", "X-Client-IP", "X-Cluster-Client-IP"];
for header_name in &other_headers {
if let Some(ip) = headers.get(*header_name).and_then(|h| h.to_str().ok()) {
if !is_private_ip(ip) {
return ip.to_string();
}
}
}
let connect_ip = connect_info.ip().to_string();
if !is_private_ip(&connect_ip) {
return connect_ip;
}
"0.0.0.0".to_string()
}
pub struct WebExt;
impl WebExt {
pub fn domain(url_str: &str) -> Option<String> {
Url::parse(url_str)
.ok()
.and_then(|u| u.host_str().map(|s| s.to_string()))
}
pub fn path(url_str: &str) -> Option<String> {
Url::parse(url_str).ok().map(|u| u.path().to_string())
}
pub fn real_ip(headers: &[(String, String)], remote_addr: &str) -> String {
for (key, val) in headers {
if key.to_lowercase() == "x-forwarded-for" {
return val.split(',').next().unwrap_or("").trim().to_string();
}
if key.to_lowercase() == "x-real-ip" {
return val.clone();
}
}
remote_addr
.split(':')
.next()
.unwrap_or(remote_addr)
.to_string()
}
pub fn is_private_ip(ip: &str) -> bool {
is_private_ip(ip)
}
pub fn build_query(params: &[(&str, &str)]) -> String {
if params.is_empty() {
return String::new();
}
let parts: Vec<String> = params
.iter()
.map(|(k, v)| format!("{}={}", urlencoding(k), urlencoding(v)))
.collect();
format!("?{}", parts.join("&"))
}
}
fn urlencoding(s: &str) -> String {
let mut result = String::with_capacity(s.len() * 3);
for byte in s.bytes() {
match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
result.push(byte as char);
}
_ => {
result.push_str(&format!("%{:02X}", byte));
}
}
}
result
}