1use std::collections::HashMap;
2use std::str::FromStr;
3use std::sync::LazyLock;
4use percent_encoding::{utf8_percent_encode, AsciiSet};
5use url::{ParseError, Url};
6use crate::ParseErr::InvalidURL;
7
8pub type BasicAuthentication = (String, String);
10
11#[derive(Clone, Debug)]
12pub enum Proxy {
13 HTTP(HTTPProxy),
14 HTTPS(HTTPSProxy),
15 SOCKS4(SOCKS4Proxy),
16 SOCKS5(SOCKS5Proxy)
17}
18
19impl Proxy {
20 pub fn get_auth(&self) -> Option<&BasicAuthentication> {
24 match self {
25 Proxy::SOCKS4(_) => None,
26 Proxy::SOCKS5(x) => x.auth.as_ref(),
27 Proxy::HTTP(x) => x.auth.as_ref(),
28 Proxy::HTTPS(x) => x.auth.as_ref(),
29 }
30 }
31 pub fn get_scheme(&self) -> &str {
37 match self {
38 Proxy::HTTP(_) => "http",
39 Proxy::HTTPS(_) => "https",
40 Proxy::SOCKS4(x) => if x.a { "socks4a" } else { "socks4" },
41 Proxy::SOCKS5(x) => if x.h { "socks5h" } else { "socks5" }
42 }
43 }
44 pub fn get_host(&self) -> &String {
46 match self {
47 Proxy::HTTP(x) => &x.hostname,
48 Proxy::HTTPS(x) => &x.hostname,
49 Proxy::SOCKS4(x) => &x.hostname,
50 Proxy::SOCKS5(x) => &x.hostname,
51 }
52 }
53 pub fn get_port(&self) -> u16 {
55 match self {
56 Proxy::HTTP(x) => x.port,
57 Proxy::HTTPS(x) => x.port,
58 Proxy::SOCKS4(x) => x.port,
59 Proxy::SOCKS5(x) => x.port,
60 }
61 }
62}
63
64#[cfg(feature = "proxy_parse")]
65#[derive(Debug, Clone)]
66pub enum ParseErr {
67 InvalidURL(ParseError),
69 InvalidScheme(String),
71 MissingHost,
72 MissingPort,
74 AuthUnsupported
77}
78
79impl From<ParseError> for ParseErr {
80 fn from(err: ParseError) -> ParseErr {
81 InvalidURL(err)
82 }
83}
84
85static DEFAULT_PORTS: LazyLock<HashMap<&str, u16>> = LazyLock::new(|| {
87 [
88 ("http", 80),
89 ("https", 443),
90 ("socks4", 1080),
91 ("socks5", 1080)
92 ].into_iter().collect()
93});
94
95#[cfg(feature = "proxy_parse")]
96impl FromStr for Proxy {
97 type Err = ParseErr;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 let a = s.parse::<Url>()?;
101 let Some(host) = a.host() else { return Err(ParseErr::MissingHost) };
102 let lc = a.scheme().to_lowercase();
103 let Some(port) = a.port().or(DEFAULT_PORTS.get(lc.as_str()).cloned()) else { return Err(ParseErr::MissingPort) };
104 let auth: Option<BasicAuthentication> = if let Some(p) = a.password() {
105 Some((a.username().into(), p.into()))
106 } else { None };
107 match lc.as_str() {
108 "http" => Ok(Proxy::HTTP(HTTPProxy { hostname: host.to_string(), port, auth })),
109 "https" => Ok(Proxy::HTTPS(HTTPSProxy { hostname: host.to_string(), port, auth })),
110 "socks4" => {
111 if auth.is_some() {
112 return Err(ParseErr::AuthUnsupported)
113 }
114 Ok(Proxy::SOCKS4(SOCKS4Proxy { hostname: host.to_string(), port, a: false }))
115 },
116 "socks5" => Ok(Proxy::SOCKS5(SOCKS5Proxy { hostname: host.to_string(), port, auth, h: false })),
117 "socks4h" => {
118 if auth.is_some() {
119 return Err(ParseErr::AuthUnsupported)
120 }
121 Ok(Proxy::SOCKS4(SOCKS4Proxy { hostname: host.to_string(), port, a: true }))
122 },
123 "socks5h" => Ok(Proxy::SOCKS5(SOCKS5Proxy { hostname: host.to_string(), port, auth, h: true })),
124
125 scheme => Err(ParseErr::InvalidScheme(scheme.into())),
126 }
127 }
128}
129
130#[cfg(feature = "proxy_parse")]
131impl From<&Proxy> for Url {
132 fn from(px: &Proxy) -> Self {
133 let scheme = match px {
134 Proxy::HTTP(_) => "http",
135 Proxy::HTTPS(_) => "https",
136 Proxy::SOCKS4(_) => "socks4",
137 Proxy::SOCKS5(_) => "socks5"
138 };
139 let auth_stuff = px.get_auth().map(|a| {
140 let ascii_set = &AsciiSet::EMPTY;
141 let u = utf8_percent_encode(a.0.as_str(), ascii_set);
142 let p = utf8_percent_encode(a.1.as_str(), ascii_set);
143 format!("{u}:{p}@")
144 }).unwrap_or_else(|| "".into());
145 let host = px.get_host();
146 let port = px.get_port();
147 Url::parse(format!("{scheme}://{auth_stuff}{host}:{port}").as_str()).unwrap()
148 }
149}
150#[cfg(feature = "proxy_parse")]
151impl From<Proxy> for Url {
152 fn from(px: Proxy) -> Self {
153 Url::from(&px)
154 }
155}
156
157#[derive(Debug, Clone)]
158pub struct HTTPProxy {
159 pub hostname: String,
160 pub port: u16,
161 pub auth: Option<BasicAuthentication>
162}
163
164#[derive(Debug, Clone)]
165pub struct HTTPSProxy {
166 pub hostname: String,
167 pub port: u16,
168 pub auth: Option<BasicAuthentication>
169}
170
171#[derive(Debug, Clone)]
172pub struct SOCKS4Proxy {
173 pub hostname: String,
174 pub port: u16,
175 pub a: bool
178}
179
180#[derive(Debug, Clone)]
181pub struct SOCKS5Proxy {
182 pub hostname: String,
183 pub port: u16,
184 pub auth: Option<BasicAuthentication>,
185 pub h: bool
188}