use std::net::SocketAddr;
use crate::error::{ReqresError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyType {
Http,
Https,
Socks5,
}
#[derive(Debug, Clone)]
pub struct Proxy {
pub addr: String,
pub proxy_type: ProxyType,
pub username: Option<String>,
pub password: Option<String>,
}
impl Proxy {
pub fn new(addr: String, proxy_type: ProxyType) -> Self {
Proxy {
addr,
proxy_type,
username: None,
password: None,
}
}
pub fn with_auth(mut self, username: String, password: String) -> Self {
self.username = Some(username);
self.password = Some(password);
self
}
pub fn from_url(url: &str) -> Result<Self> {
let (scheme, rest) = if url.starts_with("http://") {
(ProxyType::Http, &url[7..])
} else if url.starts_with("https://") {
(ProxyType::Https, &url[8..])
} else if url.starts_with("socks5://") {
(ProxyType::Socks5, &url[9..])
} else if url.contains("://") {
return Err(ReqresError::InvalidUrl(format!(
"Unsupported proxy scheme: {}",
url
)));
} else {
(ProxyType::Http, url)
};
let (addr, username, password) = if let Some(at_pos) = rest.find('@') {
let auth = &rest[..at_pos];
let addr = &rest[at_pos + 1..];
let (username, password) = if let Some(colon_pos) = auth.find(':') {
(
Some(auth[..colon_pos].to_string()),
Some(auth[colon_pos + 1..].to_string()),
)
} else {
(Some(auth.to_string()), None)
};
(addr.to_string(), username, password)
} else {
(rest.to_string(), None, None)
};
Ok(Proxy {
addr,
proxy_type: scheme,
username,
password,
})
}
pub fn address(&self) -> Result<SocketAddr> {
self.addr
.parse()
.map_err(|_| ReqresError::InvalidUrl(format!("Invalid proxy address: {}", self.addr)))
}
pub fn needs_auth(&self) -> bool {
self.username.is_some()
}
pub fn build_auth_header(&self) -> Option<String> {
if let (Some(username), Some(password)) = (&self.username, &self.password) {
use base64::prelude::*;
let auth = format!("{}:{}", username, password);
let encoded = BASE64_STANDARD.encode(auth);
Some(format!("Basic {}", encoded))
} else {
None
}
}
}
pub struct ProxyBuilder {
addr: String,
proxy_type: ProxyType,
username: Option<String>,
password: Option<String>,
}
impl ProxyBuilder {
pub fn new(addr: impl Into<String>, proxy_type: ProxyType) -> Self {
ProxyBuilder {
addr: addr.into(),
proxy_type,
username: None,
password: None,
}
}
pub fn credentials(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
self.username = Some(username.into());
self.password = Some(password.into());
self
}
pub fn build(self) -> Proxy {
Proxy {
addr: self.addr,
proxy_type: self.proxy_type,
username: self.username,
password: self.password,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proxy_from_url_http() {
let proxy = Proxy::from_url("http://proxy.example.com:8080").unwrap();
assert_eq!(proxy.proxy_type, ProxyType::Http);
assert_eq!(proxy.addr, "proxy.example.com:8080");
assert!(!proxy.needs_auth());
}
#[test]
fn test_proxy_from_url_https() {
let proxy = Proxy::from_url("https://proxy.example.com:8080").unwrap();
assert_eq!(proxy.proxy_type, ProxyType::Https);
assert_eq!(proxy.addr, "proxy.example.com:8080");
}
#[test]
fn test_proxy_from_url_socks5() {
let proxy = Proxy::from_url("socks5://proxy.example.com:1080").unwrap();
assert_eq!(proxy.proxy_type, ProxyType::Socks5);
assert_eq!(proxy.addr, "proxy.example.com:1080");
}
#[test]
fn test_proxy_from_url_with_auth() {
let proxy = Proxy::from_url("http://user:pass@proxy.example.com:8080").unwrap();
assert_eq!(proxy.proxy_type, ProxyType::Http);
assert_eq!(proxy.addr, "proxy.example.com:8080");
assert!(proxy.needs_auth());
assert_eq!(proxy.username, Some("user".to_string()));
assert_eq!(proxy.password, Some("pass".to_string()));
}
#[test]
fn test_proxy_builder() {
let proxy = ProxyBuilder::new("proxy.example.com:8080", ProxyType::Http)
.credentials("user", "pass")
.build();
assert_eq!(proxy.addr, "proxy.example.com:8080");
assert!(proxy.needs_auth());
}
#[test]
fn test_proxy_build_auth_header() {
let proxy = Proxy::new("proxy.example.com:8080".to_string(), ProxyType::Http)
.with_auth("user".to_string(), "pass".to_string());
let header = proxy.build_auth_header();
assert!(header.is_some());
assert!(header.unwrap().starts_with("Basic "));
}
}