use std::collections::HashMap;
use std::str::FromStr;
use std::sync::LazyLock;
use percent_encoding::{utf8_percent_encode, AsciiSet};
use url::{ParseError, Url};
use crate::ParseErr::InvalidURL;
pub type BasicAuthentication = (String, String);
#[derive(Clone, Debug)]
pub enum Proxy {
HTTP(HTTPProxy),
HTTPS(HTTPSProxy),
SOCKS4(SOCKS4Proxy),
SOCKS5(SOCKS5Proxy)
}
impl Proxy {
pub fn get_auth(&self) -> Option<&BasicAuthentication> {
match self {
Proxy::SOCKS4(_) => None,
Proxy::SOCKS5(x) => x.auth.as_ref(),
Proxy::HTTP(x) => x.auth.as_ref(),
Proxy::HTTPS(x) => x.auth.as_ref(),
}
}
pub fn get_scheme(&self) -> &str {
match self {
Proxy::HTTP(_) => "http",
Proxy::HTTPS(_) => "https",
Proxy::SOCKS4(x) => if x.a { "socks4a" } else { "socks4" },
Proxy::SOCKS5(x) => if x.h { "socks5h" } else { "socks5" }
}
}
pub fn get_host(&self) -> &String {
match self {
Proxy::HTTP(x) => &x.hostname,
Proxy::HTTPS(x) => &x.hostname,
Proxy::SOCKS4(x) => &x.hostname,
Proxy::SOCKS5(x) => &x.hostname,
}
}
pub fn get_port(&self) -> u16 {
match self {
Proxy::HTTP(x) => x.port,
Proxy::HTTPS(x) => x.port,
Proxy::SOCKS4(x) => x.port,
Proxy::SOCKS5(x) => x.port,
}
}
}
#[cfg(feature = "proxy_parse")]
#[derive(Debug, Clone)]
pub enum ParseErr {
InvalidURL(ParseError),
InvalidScheme(String),
MissingHost,
MissingPort,
AuthUnsupported
}
impl From<ParseError> for ParseErr {
fn from(err: ParseError) -> ParseErr {
InvalidURL(err)
}
}
static DEFAULT_PORTS: LazyLock<HashMap<&str, u16>> = LazyLock::new(|| {
[
("http", 80),
("https", 443),
("socks4", 1080),
("socks5", 1080)
].into_iter().collect()
});
#[cfg(feature = "proxy_parse")]
impl FromStr for Proxy {
type Err = ParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let a = s.parse::<Url>()?;
let Some(host) = a.host() else { return Err(ParseErr::MissingHost) };
let lc = a.scheme().to_lowercase();
let Some(port) = a.port().or(DEFAULT_PORTS.get(lc.as_str()).cloned()) else { return Err(ParseErr::MissingPort) };
let auth: Option<BasicAuthentication> = if let Some(p) = a.password() {
Some((a.username().into(), p.into()))
} else { None };
match lc.as_str() {
"http" => Ok(Proxy::HTTP(HTTPProxy { hostname: host.to_string(), port, auth })),
"https" => Ok(Proxy::HTTPS(HTTPSProxy { hostname: host.to_string(), port, auth })),
"socks4" => {
if auth.is_some() {
return Err(ParseErr::AuthUnsupported)
}
Ok(Proxy::SOCKS4(SOCKS4Proxy { hostname: host.to_string(), port, a: false }))
},
"socks5" => Ok(Proxy::SOCKS5(SOCKS5Proxy { hostname: host.to_string(), port, auth, h: false })),
"socks4h" => {
if auth.is_some() {
return Err(ParseErr::AuthUnsupported)
}
Ok(Proxy::SOCKS4(SOCKS4Proxy { hostname: host.to_string(), port, a: true }))
},
"socks5h" => Ok(Proxy::SOCKS5(SOCKS5Proxy { hostname: host.to_string(), port, auth, h: true })),
scheme => Err(ParseErr::InvalidScheme(scheme.into())),
}
}
}
#[cfg(feature = "proxy_parse")]
impl From<&Proxy> for Url {
fn from(px: &Proxy) -> Self {
let scheme = match px {
Proxy::HTTP(_) => "http",
Proxy::HTTPS(_) => "https",
Proxy::SOCKS4(_) => "socks4",
Proxy::SOCKS5(_) => "socks5"
};
let auth_stuff = px.get_auth().map(|a| {
let ascii_set = &AsciiSet::EMPTY;
let u = utf8_percent_encode(a.0.as_str(), ascii_set);
let p = utf8_percent_encode(a.1.as_str(), ascii_set);
format!("{u}:{p}@")
}).unwrap_or_else(|| "".into());
let host = px.get_host();
let port = px.get_port();
Url::parse(format!("{scheme}://{auth_stuff}{host}:{port}").as_str()).unwrap()
}
}
#[cfg(feature = "proxy_parse")]
impl From<Proxy> for Url {
fn from(px: Proxy) -> Self {
Url::from(&px)
}
}
#[derive(Debug, Clone)]
pub struct HTTPProxy {
pub hostname: String,
pub port: u16,
pub auth: Option<BasicAuthentication>
}
#[derive(Debug, Clone)]
pub struct HTTPSProxy {
pub hostname: String,
pub port: u16,
pub auth: Option<BasicAuthentication>
}
#[derive(Debug, Clone)]
pub struct SOCKS4Proxy {
pub hostname: String,
pub port: u16,
pub a: bool
}
#[derive(Debug, Clone)]
pub struct SOCKS5Proxy {
pub hostname: String,
pub port: u16,
pub auth: Option<BasicAuthentication>,
pub h: bool
}