1use serde::{Deserialize, Serialize};
6use std::env;
7use url::Url;
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct ProxyConfig {
12 #[serde(default)]
14 pub http: Option<String>,
15 #[serde(default)]
17 pub https: Option<String>,
18 #[serde(default)]
20 pub socks: Option<String>,
21 #[serde(default)]
23 pub no_proxy: Vec<String>,
24 #[serde(default)]
26 pub username: Option<String>,
27 #[serde(default)]
29 pub password: Option<String>,
30 #[serde(default = "default_use_system_proxy")]
32 pub use_system_proxy: bool,
33}
34
35fn default_use_system_proxy() -> bool {
36 true
37}
38
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
41pub struct ProxyAgentOptions {
42 #[serde(default)]
44 pub timeout: Option<u64>,
45 #[serde(default = "default_keep_alive")]
47 pub keep_alive: bool,
48 #[serde(default)]
50 pub max_sockets: Option<usize>,
51 #[serde(default)]
53 pub max_free_sockets: Option<usize>,
54 #[serde(default = "default_reject_unauthorized")]
56 pub reject_unauthorized: bool,
57}
58
59fn default_keep_alive() -> bool {
60 true
61}
62
63fn default_reject_unauthorized() -> bool {
64 true
65}
66
67#[derive(Debug, Clone)]
69pub struct ParsedProxyUrl {
70 pub url: String,
72 pub username: Option<String>,
74 pub password: Option<String>,
76}
77
78#[derive(Debug, Clone)]
80pub struct ProxyInfo {
81 pub enabled: bool,
83 pub proxy_url: Option<String>,
85 pub bypassed: bool,
87}
88
89pub fn get_proxy_from_env() -> ProxyConfig {
91 let no_proxy = env::var("NO_PROXY")
92 .or_else(|_| env::var("no_proxy"))
93 .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
94 .unwrap_or_default();
95
96 ProxyConfig {
97 http: env::var("HTTP_PROXY")
98 .or_else(|_| env::var("http_proxy"))
99 .ok(),
100 https: env::var("HTTPS_PROXY")
101 .or_else(|_| env::var("https_proxy"))
102 .ok(),
103 socks: env::var("ALL_PROXY")
104 .or_else(|_| env::var("all_proxy"))
105 .or_else(|_| env::var("SOCKS_PROXY"))
106 .or_else(|_| env::var("socks_proxy"))
107 .ok(),
108 no_proxy,
109 username: None,
110 password: None,
111 use_system_proxy: true,
112 }
113}
114
115pub fn parse_proxy_url(proxy_url: &str) -> ParsedProxyUrl {
117 match Url::parse(proxy_url) {
118 Ok(mut url) => {
119 let username = if url.username().is_empty() {
120 None
121 } else {
122 Some(url.username().to_string())
123 };
124 let password = url.password().map(|s| s.to_string());
125
126 let _ = url.set_username("");
128 let _ = url.set_password(None);
129
130 ParsedProxyUrl {
131 url: url.to_string(),
132 username,
133 password,
134 }
135 }
136 Err(_) => ParsedProxyUrl {
137 url: proxy_url.to_string(),
138 username: None,
139 password: None,
140 },
141 }
142}
143
144pub fn should_bypass_proxy(target_url: &str, no_proxy: &[String]) -> bool {
146 if no_proxy.is_empty() {
147 return false;
148 }
149
150 let hostname = match Url::parse(target_url) {
151 Ok(url) => match url.host_str() {
152 Some(h) => h.to_string(),
153 None => return false,
154 },
155 Err(_) => return false,
156 };
157
158 for pattern in no_proxy {
159 if pattern.is_empty() {
160 continue;
161 }
162
163 if pattern == "*" {
165 return true;
166 }
167
168 if hostname == *pattern {
170 return true;
171 }
172
173 if let Some(domain) = pattern.strip_prefix("*.") {
175 if hostname.ends_with(domain) {
176 return true;
177 }
178 }
179
180 if pattern.starts_with('.') && hostname.ends_with(pattern) {
182 return true;
183 }
184 }
185
186 false
187}
188
189pub fn get_proxy_for_url(target_url: &str, config: &ProxyConfig) -> Option<String> {
191 if should_bypass_proxy(target_url, &config.no_proxy) {
193 return None;
194 }
195
196 let is_https = target_url.starts_with("https://");
197
198 if let Some(ref socks) = config.socks {
200 return Some(socks.clone());
201 }
202
203 if is_https {
205 config.https.clone().or_else(|| config.http.clone())
206 } else {
207 config.http.clone().or_else(|| config.https.clone())
208 }
209}
210
211pub fn get_proxy_info(target_url: &str, config: Option<&ProxyConfig>) -> ProxyInfo {
213 let effective_config = match config {
214 Some(c) => c.clone(),
215 None => get_proxy_from_env(),
216 };
217
218 let bypassed = should_bypass_proxy(target_url, &effective_config.no_proxy);
219
220 if bypassed {
221 return ProxyInfo {
222 enabled: false,
223 proxy_url: None,
224 bypassed: true,
225 };
226 }
227
228 let proxy_url = get_proxy_for_url(target_url, &effective_config);
229
230 ProxyInfo {
231 enabled: proxy_url.is_some(),
232 proxy_url,
233 bypassed: false,
234 }
235}
236
237pub fn build_proxy_url_with_auth(
239 proxy_url: &str,
240 username: Option<&str>,
241 password: Option<&str>,
242) -> String {
243 if username.is_none() || password.is_none() {
244 return proxy_url.to_string();
245 }
246
247 match Url::parse(proxy_url) {
248 Ok(mut url) => {
249 if let Some(u) = username {
250 let _ = url.set_username(u);
251 }
252 if let Some(p) = password {
253 let _ = url.set_password(Some(p));
254 }
255 url.to_string()
256 }
257 Err(_) => proxy_url.to_string(),
258 }
259}
260
261pub fn get_reqwest_proxy(config: Option<&ProxyConfig>) -> Option<reqwest::Proxy> {
263 let effective_config = match config {
264 Some(c) => c.clone(),
265 None => get_proxy_from_env(),
266 };
267
268 let proxy_url = effective_config
270 .https
271 .or(effective_config.http)
272 .or(effective_config.socks)?;
273
274 let parsed = parse_proxy_url(&proxy_url);
275 let final_url = build_proxy_url_with_auth(
276 &parsed.url,
277 effective_config
278 .username
279 .as_deref()
280 .or(parsed.username.as_deref()),
281 effective_config
282 .password
283 .as_deref()
284 .or(parsed.password.as_deref()),
285 );
286
287 reqwest::Proxy::all(&final_url).ok()
288}