use reqwest::Client;
pub(crate) async fn fetch_proxies_from_source(source: &str) -> Result<Vec<String>, reqwest::Error> {
if source.starts_with("http") {
let client = Client::new();
let response = client.get(source).send().await?;
let content = response.text().await?;
Ok(parse_proxy_list(&content))
} else {
match std::fs::read_to_string(source) {
Ok(content) => Ok(parse_proxy_list(&content)),
Err(_) => Ok(Vec::new()),
}
}
}
pub(crate) fn parse_proxy_list(content: &str) -> Vec<String> {
content
.lines()
.filter_map(|line| {
let line = line.trim();
if line.starts_with("socks5://") || line.starts_with("socks5h://") {
Some(line.to_string())
} else if line.contains(':')
&& !line.contains("://")
&& !line.starts_with('#')
&& !line.is_empty()
{
Some(format!("socks5://{}", line))
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::parse_proxy_list;
#[test]
fn parse_supports_socks5_and_socks5h() {
let content = "socks5://127.0.0.1:1080\nsocks5h://127.0.0.2:1080\n";
let parsed = parse_proxy_list(content);
assert_eq!(
parsed,
vec![
"socks5://127.0.0.1:1080".to_string(),
"socks5h://127.0.0.2:1080".to_string()
]
);
}
#[test]
fn parse_rejects_invalid_socks5_prefix() {
let content = "socks5x://127.0.0.1:1080\nnot-a-proxy\n";
let parsed = parse_proxy_list(content);
assert!(parsed.is_empty());
}
#[test]
fn parse_plain_host_port_to_socks5() {
let content = "1.2.3.4:1080\n# comment\n";
let parsed = parse_proxy_list(content);
assert_eq!(parsed, vec!["socks5://1.2.3.4:1080".to_string()]);
}
}