Files
donutbrowser/src-tauri/src/ip_utils.rs
T
2026-07-08 01:19:52 +04:00

97 lines
2.4 KiB
Rust

//! IP address utilities shared across the application.
//!
//! Provides IP validation and public IP fetching functionality.
use std::net::IpAddr;
use std::str::FromStr;
/// IP utility error type.
#[derive(Debug, thiserror::Error)]
pub enum IpError {
#[error("Network error: {0}")]
Network(String),
}
/// Validate an IP address (IPv4 or IPv6).
pub fn validate_ip(ip: &str) -> bool {
IpAddr::from_str(ip).is_ok()
}
/// Fetch public IP address, optionally through a proxy.
pub async fn fetch_public_ip(proxy: Option<&str>) -> Result<String, IpError> {
let urls = [
"https://api.ipify.org",
"https://checkip.amazonaws.com",
"https://ipinfo.io/ip",
"https://icanhazip.com",
"https://ifconfig.co/ip",
"https://ipecho.net/plain",
];
let client_builder = reqwest::Client::builder().timeout(std::time::Duration::from_secs(5));
let client = if let Some(proxy_url) = proxy {
let proxy = reqwest::Proxy::all(proxy_url)
.map_err(|e| IpError::Network(format!("Invalid proxy: {}", e)))?;
client_builder
.no_proxy()
.proxy(proxy)
.build()
.map_err(|e| IpError::Network(e.to_string()))?
} else {
client_builder
.build()
.map_err(|e| IpError::Network(e.to_string()))?
};
let mut errors = Vec::new();
for url in &urls {
match client.get(*url).send().await {
Ok(response) if response.status().is_success() => match response.text().await {
Ok(text) => {
let ip = text.trim().to_string();
if validate_ip(&ip) {
return Ok(ip);
}
}
Err(e) => {
errors.push(format!("{}: {}", url, e));
}
},
Ok(response) => {
errors.push(format!("{}: HTTP {}", url, response.status()));
}
Err(e) => {
errors.push(format!("{}: {}", url, e));
}
}
}
if errors.is_empty() {
Err(IpError::Network(
"Failed to fetch public IP from any endpoint".to_string(),
))
} else {
Err(IpError::Network(format!(
"All {} endpoints failed: {}",
errors.len(),
errors.join("; ")
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_ip() {
assert!(validate_ip("8.8.8.8"));
assert!(validate_ip("192.168.1.1"));
assert!(validate_ip("2001:4860:4860::8888"));
assert!(!validate_ip("invalid"));
assert!(!validate_ip("256.256.256.256"));
}
}