/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-3.3.0/src/proxy.rs
Line | Count | Source |
1 | | use std::convert::{TryFrom, TryInto}; |
2 | | use std::fmt; |
3 | | use std::sync::Arc; |
4 | | use ureq_proto::http::uri::{PathAndQuery, Scheme}; |
5 | | |
6 | | use http::Uri; |
7 | | |
8 | | use crate::Error; |
9 | | use crate::http; |
10 | | use crate::util::{AuthorityExt, DebugUri}; |
11 | | |
12 | | #[cfg(all(windows, feature = "win-system-proxy"))] |
13 | | const REGISTRY_PATH: &str = r#"Software\Microsoft\Windows\CurrentVersion\Internet Settings"#; |
14 | | |
15 | | /// Proxy protocol |
16 | | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
17 | | #[non_exhaustive] |
18 | | pub enum ProxyProtocol { |
19 | | /// CONNECT proxy over HTTP |
20 | | Http, |
21 | | /// CONNECT proxy over HTTPS |
22 | | Https, |
23 | | /// A SOCKS4 proxy |
24 | | Socks4, |
25 | | /// A SOCKS4a proxy (proxy can resolve domain name) |
26 | | Socks4A, |
27 | | /// SOCKS5 proxy |
28 | | Socks5, |
29 | | /// SOCKS5h proxy (proxy can resolve domain name) |
30 | | Socks5h, |
31 | | } |
32 | | |
33 | | impl ProxyProtocol { |
34 | 0 | pub(crate) fn default_port(&self) -> u16 { |
35 | 0 | match self { |
36 | 0 | ProxyProtocol::Http => 80, |
37 | 0 | ProxyProtocol::Https => 443, |
38 | | ProxyProtocol::Socks4 |
39 | | | ProxyProtocol::Socks4A |
40 | | | ProxyProtocol::Socks5 |
41 | 0 | | ProxyProtocol::Socks5h => 1080, |
42 | | } |
43 | 0 | } |
44 | | |
45 | 0 | pub(crate) fn is_socks(&self) -> bool { |
46 | 0 | matches!( |
47 | 0 | self, |
48 | | Self::Socks4 | Self::Socks4A | Self::Socks5 | Self::Socks5h |
49 | | ) |
50 | 0 | } |
51 | | |
52 | 0 | pub(crate) fn is_connect(&self) -> bool { |
53 | 0 | matches!(self, Self::Http | Self::Https) |
54 | 0 | } |
55 | | |
56 | 0 | fn default_resolve_target(&self) -> bool { |
57 | 0 | match self { |
58 | 0 | ProxyProtocol::Http => false, |
59 | 0 | ProxyProtocol::Https => false, |
60 | 0 | ProxyProtocol::Socks4 => true, // we must locally resolve before using proxy |
61 | 0 | ProxyProtocol::Socks4A => false, |
62 | 0 | ProxyProtocol::Socks5 => true, // we must locally resolve before using proxy |
63 | 0 | ProxyProtocol::Socks5h => false, |
64 | | } |
65 | 0 | } |
66 | | } |
67 | | |
68 | | /// Proxy server settings |
69 | | /// |
70 | | /// This struct represents a proxy server configuration that can be used to route HTTP/HTTPS |
71 | | /// requests through a proxy server. It supports various proxy protocols including HTTP CONNECT, |
72 | | /// HTTPS CONNECT, SOCKS4, SOCKS4A, and SOCKS5. |
73 | | /// |
74 | | /// # Protocol Support |
75 | | /// |
76 | | /// * `HTTP`: HTTP CONNECT proxy |
77 | | /// * `HTTPS`: HTTPS CONNECT proxy (requires a TLS provider) |
78 | | /// * `SOCKS4`: SOCKS4 proxy (requires **socks-proxy** feature) |
79 | | /// * `SOCKS4A`: SOCKS4A proxy (requires **socks-proxy** feature) |
80 | | /// * `SOCKS5`: SOCKS5 proxy (requires **socks-proxy** feature) |
81 | | /// |
82 | | /// # DNS Resolution |
83 | | /// |
84 | | /// The `resolve_target` setting controls where DNS resolution happens: |
85 | | /// |
86 | | /// * When `true`: DNS resolution happens locally before connecting to the proxy. |
87 | | /// The resolved IP address is sent to the proxy. |
88 | | /// * When `false`: The hostname is sent to the proxy, which performs DNS resolution. |
89 | | /// |
90 | | /// Default behavior: |
91 | | /// * For SOCKS4: `true` (local resolution required) |
92 | | /// * For all other protocols: `false` (proxy performs resolution) |
93 | | /// |
94 | | /// # Examples |
95 | | /// |
96 | | /// ```rust |
97 | | /// use ureq::{Proxy, ProxyProtocol}; |
98 | | /// |
99 | | /// // Create a proxy from a URI string |
100 | | /// let proxy = Proxy::new("http://localhost:8080").unwrap(); |
101 | | /// |
102 | | /// // Create a proxy using the builder pattern |
103 | | /// let proxy = Proxy::builder(ProxyProtocol::Socks5) |
104 | | /// .host("proxy.example.com") |
105 | | /// .port(1080) |
106 | | /// .username("user") |
107 | | /// .password("pass") |
108 | | /// .resolve_target(true) // Force local DNS resolution |
109 | | /// .build() |
110 | | /// .unwrap(); |
111 | | /// |
112 | | /// // Read proxy settings from environment variables |
113 | | /// if let Some(proxy) = Proxy::try_from_env() { |
114 | | /// // Use proxy from environment |
115 | | /// } |
116 | | /// ``` |
117 | | #[derive(Clone, Eq, Hash, PartialEq)] |
118 | | pub struct Proxy { |
119 | | inner: Arc<ProxyInner>, |
120 | | } |
121 | | |
122 | | #[derive(Eq, Hash, PartialEq)] |
123 | | struct ProxyInner { |
124 | | proto: ProxyProtocol, |
125 | | uri: Uri, |
126 | | from_env: bool, |
127 | | resolve_target: bool, |
128 | | no_proxy: Option<NoProxy>, |
129 | | } |
130 | | |
131 | | impl Proxy { |
132 | | /// Create a proxy from a uri. |
133 | | /// |
134 | | /// # Arguments: |
135 | | /// |
136 | | /// * `proxy` - a str of format `<protocol>://<user>:<password>@<host>:port` . All parts |
137 | | /// except host are optional. |
138 | | /// |
139 | | /// ### Protocols |
140 | | /// |
141 | | /// * `http`: HTTP CONNECT proxy |
142 | | /// * `https`: HTTPS CONNECT proxy (requires a TLS provider) |
143 | | /// * `socks4`: SOCKS4 (requires **socks-proxy** feature) |
144 | | /// * `socks4a`: SOCKS4A (requires **socks-proxy** feature) |
145 | | /// * `socks5` and `socks`: SOCKS5 (requires **socks-proxy** feature) |
146 | | /// |
147 | | /// # Examples proxy formats |
148 | | /// |
149 | | /// * `http://127.0.0.1:8080` |
150 | | /// * `socks5://john:smith@socks.google.com` |
151 | | /// * `john:smith@socks.google.com:8000` |
152 | | /// * `localhost` |
153 | 0 | pub fn new(proxy: &str) -> Result<Self, Error> { |
154 | 0 | Self::new_with_flag(proxy, None, false, None) |
155 | 0 | } |
156 | | |
157 | | /// Creates a proxy config using a builder. |
158 | 0 | pub fn builder(p: ProxyProtocol) -> ProxyBuilder { |
159 | 0 | ProxyBuilder { |
160 | 0 | protocol: p, |
161 | 0 | host: None, |
162 | 0 | port: None, |
163 | 0 | username: None, |
164 | 0 | password: None, |
165 | 0 | resolve_target: p.default_resolve_target(), |
166 | 0 | no_proxy: None, |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | 0 | fn new_with_flag( |
171 | 0 | proxy: &str, |
172 | 0 | no_proxy: Option<NoProxy>, |
173 | 0 | from_env: bool, |
174 | 0 | resolve_target: Option<bool>, |
175 | 0 | ) -> Result<Self, Error> { |
176 | 0 | let mut uri = proxy.parse::<Uri>().or(Err(Error::InvalidProxyUrl))?; |
177 | | |
178 | | // The uri must have an authority part (with the host), or |
179 | | // it is invalid. |
180 | 0 | let _ = uri.authority().ok_or(Error::InvalidProxyUrl)?; |
181 | | |
182 | 0 | let scheme = match uri.scheme_str() { |
183 | 0 | Some(v) => v, |
184 | | None => { |
185 | | // The default protocol is Proto::HTTP, and it is missing in |
186 | | // the uri. Let's put it in place. |
187 | 0 | uri = insert_default_scheme(uri); |
188 | 0 | "http" |
189 | | } |
190 | | }; |
191 | | |
192 | 0 | let proto: ProxyProtocol = scheme.try_into()?; |
193 | 0 | let resolve_target = resolve_target.unwrap_or(proto.default_resolve_target()); |
194 | | |
195 | 0 | let inner = ProxyInner { |
196 | 0 | proto, |
197 | 0 | uri, |
198 | 0 | from_env, |
199 | 0 | resolve_target, |
200 | 0 | no_proxy, |
201 | 0 | }; |
202 | | |
203 | 0 | Ok(Self { |
204 | 0 | inner: Arc::new(inner), |
205 | 0 | }) |
206 | 0 | } |
207 | | |
208 | | /// Read proxy settings from environment variables. |
209 | | /// |
210 | | /// The environment variable is expected to contain a proxy URI. The following |
211 | | /// environment variables are attempted: |
212 | | /// |
213 | | /// * `ALL_PROXY` |
214 | | /// * `HTTPS_PROXY` |
215 | | /// * `HTTP_PROXY` |
216 | | /// |
217 | | /// Additionally, the `NO_PROXY` environment variable is automatically read to determine |
218 | | /// which hosts should bypass the proxy. This supports various pattern types including |
219 | | /// exact hostnames, wildcard suffixes, and dot suffixes. |
220 | | /// |
221 | | /// Returns `None` if no environment variable is set or the URI is invalid. |
222 | 0 | pub fn try_from_env() -> Option<Self> { |
223 | | const TRY_ENV: &[&str] = &[ |
224 | | "ALL_PROXY", |
225 | | "all_proxy", |
226 | | "HTTPS_PROXY", |
227 | | "https_proxy", |
228 | | "HTTP_PROXY", |
229 | | "http_proxy", |
230 | | ]; |
231 | | |
232 | 0 | let no_proxy = NoProxy::try_from_env(); |
233 | 0 | for attempt in TRY_ENV { |
234 | 0 | if let Ok(env) = std::env::var(attempt) { |
235 | 0 | if let Ok(proxy) = Self::new_with_flag(&env, no_proxy.clone(), true, None) { |
236 | 0 | return Some(proxy); |
237 | 0 | } |
238 | 0 | } |
239 | | } |
240 | | |
241 | | #[cfg(all(windows, feature = "win-system-proxy"))] |
242 | | { |
243 | | use winreg::RegKey; |
244 | | use winreg::enums::{HKEY_CURRENT_USER, KEY_READ}; |
245 | | |
246 | | let registry = RegKey::predef(HKEY_CURRENT_USER); |
247 | | let Ok(ie_settings) = registry.open_subkey_with_flags(REGISTRY_PATH, KEY_READ) else { |
248 | | return None; |
249 | | }; |
250 | | |
251 | | let enabled = ie_settings |
252 | | .get_value::<u32, _>("ProxyEnable") |
253 | | .is_ok_and(|enable| enable == 1); |
254 | | if !enabled { |
255 | | return None; |
256 | | } |
257 | | |
258 | | ie_settings |
259 | | .get_value::<String, _>("ProxyServer") |
260 | | .ok() |
261 | | .and_then(|proxy| { |
262 | | Self::new_with_flag(&format!("http://{proxy}"), no_proxy, true, None).ok() |
263 | | }) |
264 | | } |
265 | | #[cfg(not(all(windows, feature = "win-system-proxy")))] |
266 | 0 | None |
267 | 0 | } |
268 | | |
269 | | /// The configured protocol. |
270 | 0 | pub fn protocol(&self) -> ProxyProtocol { |
271 | 0 | self.inner.proto |
272 | 0 | } |
273 | | |
274 | | /// The proxy uri |
275 | 0 | pub fn uri(&self) -> &Uri { |
276 | 0 | &self.inner.uri |
277 | 0 | } |
278 | | |
279 | | /// The host part of the proxy uri |
280 | 0 | pub fn host(&self) -> &str { |
281 | 0 | self.inner |
282 | 0 | .uri |
283 | 0 | .authority() |
284 | 0 | .map(|a| a.host()) |
285 | 0 | .expect("constructor to ensure there is an authority") |
286 | 0 | } |
287 | | |
288 | | /// The port of the proxy uri |
289 | 0 | pub fn port(&self) -> u16 { |
290 | 0 | self.inner |
291 | 0 | .uri |
292 | 0 | .authority() |
293 | 0 | .and_then(|a| a.port_u16()) |
294 | 0 | .unwrap_or_else(|| self.inner.proto.default_port()) |
295 | 0 | } |
296 | | |
297 | | /// The username of the proxy uri |
298 | 0 | pub fn username(&self) -> Option<&str> { |
299 | 0 | self.inner.uri.authority().and_then(|a| a.username()) |
300 | 0 | } |
301 | | |
302 | | /// The password of the proxy uri |
303 | 0 | pub fn password(&self) -> Option<&str> { |
304 | 0 | self.inner.uri.authority().and_then(|a| a.password()) |
305 | 0 | } |
306 | | |
307 | | /// Whether this proxy setting was created manually or from |
308 | | /// environment variables. |
309 | 0 | pub fn is_from_env(&self) -> bool { |
310 | 0 | self.inner.from_env |
311 | 0 | } |
312 | | |
313 | | /// Whether to resolve target locally before calling the proxy. |
314 | | /// |
315 | | /// * `true` - resolve the DNS before calling proxy. |
316 | | /// * `false` - send the target host to the proxy and let it resolve. |
317 | | /// |
318 | | /// Defaults to `false` for all proxies protocols except `SOCKS4`. I.e. the normal |
319 | | /// case is to let the proxy resolve the target host. |
320 | 0 | pub fn resolve_target(&self) -> bool { |
321 | 0 | self.inner.resolve_target |
322 | 0 | } |
323 | | |
324 | | /// Tells if this entry matches anything on the NO_PROXY list. |
325 | | /// |
326 | | /// This method is used by Proxy Connectors to decide if a connection to the given host |
327 | | /// should be routed through the proxy or established directly. |
328 | | /// |
329 | | /// * `false` - The connection should be routed through the proxy connector |
330 | | /// * `true` - The connection should bypass the proxy and connect directly to the host |
331 | 0 | pub fn is_no_proxy(&self, uri: &Uri) -> bool { |
332 | 0 | if let (Some(no_proxy), Some(host)) = (&self.inner.no_proxy, uri.host()) { |
333 | 0 | return no_proxy.is_no_proxy(host); |
334 | 0 | } |
335 | 0 | false |
336 | 0 | } |
337 | | } |
338 | | |
339 | 0 | fn insert_default_scheme(uri: Uri) -> Uri { |
340 | 0 | let mut parts = uri.into_parts(); |
341 | | |
342 | 0 | parts.scheme = Some(Scheme::HTTP); |
343 | | |
344 | | // For some reason uri.into_parts can produce None for |
345 | | // the path, but Uri::from_parts does not accept that. |
346 | 0 | parts.path_and_query = parts |
347 | 0 | .path_and_query |
348 | 0 | .or_else(|| Some(PathAndQuery::from_static("/"))); |
349 | | |
350 | 0 | Uri::from_parts(parts).unwrap() |
351 | 0 | } |
352 | | |
353 | | /// Builder for configuring a proxy. |
354 | | /// |
355 | | /// Obtained via [`Proxy::builder()`]. |
356 | | pub struct ProxyBuilder { |
357 | | protocol: ProxyProtocol, |
358 | | host: Option<String>, |
359 | | port: Option<u16>, |
360 | | username: Option<String>, |
361 | | password: Option<String>, |
362 | | resolve_target: bool, |
363 | | no_proxy: Option<NoProxy>, |
364 | | } |
365 | | |
366 | | impl ProxyBuilder { |
367 | | /// Set the proxy hostname |
368 | | /// |
369 | | /// Defaults to `localhost`. Invalid hostnames surface in [`ProxyBuilder::build()`]. |
370 | 0 | pub fn host(mut self, host: &str) -> Self { |
371 | 0 | self.host = Some(host.to_string()); |
372 | 0 | self |
373 | 0 | } |
374 | | |
375 | | /// Set the proxy port |
376 | | /// |
377 | | /// Defaults to whatever is default for the chosen [`ProxyProtocol`]. |
378 | 0 | pub fn port(mut self, port: u16) -> Self { |
379 | 0 | self.port = Some(port); |
380 | 0 | self |
381 | 0 | } |
382 | | |
383 | | /// Set the username |
384 | | /// |
385 | | /// Defaults to none. Invalid usernames surface in [`ProxyBuilder::build()`]. |
386 | 0 | pub fn username(mut self, v: &str) -> Self { |
387 | 0 | self.username = Some(v.to_string()); |
388 | 0 | self |
389 | 0 | } |
390 | | |
391 | | /// Set the password |
392 | | /// |
393 | | /// If you want to set only a password, no username, i.e. `https://secret@foo.com`, |
394 | | /// you need to set it as [`ProxyBuilder::username()`]. |
395 | | /// |
396 | | /// Defaults to none. Invalid passwords surface in [`ProxyBuilder::build()`]. |
397 | 0 | pub fn password(mut self, v: &str) -> Self { |
398 | 0 | self.password = Some(v.to_string()); |
399 | 0 | self |
400 | 0 | } |
401 | | |
402 | | /// Whether to resolve the target host locally before calling the proxy. |
403 | | /// |
404 | | /// * `true` - resolve target host locally before calling proxy. |
405 | | /// * `false` - let proxy resolve the host. |
406 | | /// |
407 | | /// For SOCKS4, this defaults to `true`, for all other protocols `false`. I.e. |
408 | | /// in the "normal" case, we let the proxy itself resolve host names. |
409 | 0 | pub fn resolve_target(mut self, do_resolve: bool) -> Self { |
410 | 0 | self.resolve_target = do_resolve; |
411 | 0 | self |
412 | 0 | } |
413 | | |
414 | | /// Add a NO_PROXY expression to not route proxy through. |
415 | | /// |
416 | | /// Correct expressions are: |
417 | | /// |
418 | | /// * `example.com` -> Literally match `example.com`, but not `sub.example.com` |
419 | | /// * `.example.com` -> Match `sub.example.com` and `foo.sub.example.com`, but not `example.com`. |
420 | | /// * `*.example.com` -> Exactly like `.example.com` |
421 | | /// * `*` -> Match everything |
422 | | /// |
423 | | /// Silently ignores expressions that are not on the above form. |
424 | 0 | pub fn no_proxy(mut self, expr: &str) -> Self { |
425 | 0 | if let Some(entry) = NoProxyEntry::try_parse(expr) { |
426 | 0 | if self.no_proxy.is_none() { |
427 | 0 | self.no_proxy = Some(NoProxy::default()); |
428 | 0 | } |
429 | 0 | self.no_proxy.as_mut().unwrap().inner.push(entry); |
430 | 0 | } |
431 | | |
432 | 0 | self |
433 | 0 | } |
434 | | |
435 | | /// Construct the [`Proxy`] |
436 | 0 | pub fn build(self) -> Result<Proxy, Error> { |
437 | 0 | let host = self.host.as_deref().unwrap_or("localhost"); |
438 | 0 | let port = self.port.unwrap_or(self.protocol.default_port()); |
439 | | |
440 | 0 | let mut userpass = String::new(); |
441 | 0 | if let Some(username) = self.username { |
442 | 0 | userpass.push_str(&username); |
443 | 0 | if let Some(password) = self.password { |
444 | 0 | userpass.push(':'); |
445 | 0 | userpass.push_str(&password); |
446 | 0 | } |
447 | 0 | userpass.push('@'); |
448 | 0 | } |
449 | | |
450 | | // TODO(martin): This incurs as a somewhat unnecessary allocation, but we get some |
451 | | // validation and normalization in new_with_flag. This could be refactored |
452 | | // in the future. |
453 | 0 | let proxy = format!("{}://{}{}:{}", self.protocol, userpass, host, port); |
454 | 0 | Proxy::new_with_flag(&proxy, self.no_proxy, false, Some(self.resolve_target)) |
455 | 0 | } |
456 | | } |
457 | | |
458 | | impl TryFrom<&str> for ProxyProtocol { |
459 | | type Error = Error; |
460 | | |
461 | 0 | fn try_from(scheme: &str) -> Result<Self, Self::Error> { |
462 | 0 | match scheme.to_ascii_lowercase().as_str() { |
463 | 0 | "http" => Ok(ProxyProtocol::Http), |
464 | 0 | "https" => Ok(ProxyProtocol::Https), |
465 | 0 | "socks4" => Ok(ProxyProtocol::Socks4), |
466 | 0 | "socks4a" => Ok(ProxyProtocol::Socks4A), |
467 | 0 | "socks" => Ok(ProxyProtocol::Socks5), |
468 | 0 | "socks5" => Ok(ProxyProtocol::Socks5), |
469 | 0 | "socks5h" => Ok(ProxyProtocol::Socks5h), |
470 | 0 | _ => Err(Error::InvalidProxyUrl), |
471 | | } |
472 | 0 | } |
473 | | } |
474 | | |
475 | | impl fmt::Debug for Proxy { |
476 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
477 | 0 | f.debug_struct("Proxy") |
478 | 0 | .field("proto", &self.inner.proto) |
479 | 0 | .field("uri", &DebugUri(&self.inner.uri)) |
480 | 0 | .field("from_env", &self.inner.from_env) |
481 | 0 | .finish() |
482 | 0 | } |
483 | | } |
484 | | |
485 | | impl fmt::Display for ProxyProtocol { |
486 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
487 | 0 | match self { |
488 | 0 | ProxyProtocol::Http => write!(f, "HTTP"), |
489 | 0 | ProxyProtocol::Https => write!(f, "HTTPS"), |
490 | 0 | ProxyProtocol::Socks4 => write!(f, "SOCKS4"), |
491 | 0 | ProxyProtocol::Socks4A => write!(f, "SOCKS4a"), |
492 | 0 | ProxyProtocol::Socks5 => write!(f, "SOCKS5"), |
493 | 0 | ProxyProtocol::Socks5h => write!(f, "SOCKS5h"), |
494 | | } |
495 | 0 | } |
496 | | } |
497 | | |
498 | | #[derive(Debug, Clone, Eq, PartialEq, Hash)] |
499 | | enum NoProxyEntry { |
500 | | ExactHost(String), |
501 | | HostPrefix(String), |
502 | | HostSuffix(String), |
503 | | MatchAll, |
504 | | } |
505 | | |
506 | | #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] |
507 | | struct NoProxy { |
508 | | inner: Vec<NoProxyEntry>, |
509 | | } |
510 | | |
511 | | impl NoProxy { |
512 | | /// Read no proxy settings from environment variables. |
513 | | /// |
514 | | /// The environment variable is expected to contain values separated by comma. The following |
515 | | /// environment variables are attempted: |
516 | | /// |
517 | | /// * `NO_PROXY` |
518 | | /// * `no_proxy` |
519 | | /// |
520 | | /// ## Supported Pattern Types |
521 | | /// |
522 | | /// * **Exact match**: `localhost`, `127.0.0.1` - matches the exact hostname (case-insensitive) |
523 | | /// * **Wildcard suffix**: `*.example.com` - matches any subdomain of example.com |
524 | | /// * **Dot suffix**: `.example.com` - matches any subdomain of example.com (but not example.com itself) |
525 | | /// * **Match all**: `*` - bypasses proxy for all requests |
526 | | /// |
527 | | /// ## Examples |
528 | | /// |
529 | | /// ```bash |
530 | | /// # Bypass proxy for localhost and internal domains |
531 | | /// export NO_PROXY=localhost,127.0.0.1,*.internal.com |
532 | | /// |
533 | | /// # Bypass proxy for staging subdomains but not staging itself |
534 | | /// export NO_PROXY=.staging |
535 | | /// |
536 | | /// # Bypass proxy for everything |
537 | | /// export NO_PROXY=* |
538 | | /// ``` |
539 | | /// |
540 | | /// Returns `None` if no environment variable is set |
541 | 0 | pub fn try_from_env() -> Option<Self> { |
542 | | const TRY_ENV: &[&str] = &["NO_PROXY", "no_proxy"]; |
543 | | |
544 | 0 | for attempt in TRY_ENV { |
545 | 0 | if let Ok(env) = std::env::var(attempt) { |
546 | 0 | let inner = env.split(',').filter_map(NoProxyEntry::try_parse).collect(); |
547 | 0 | return Some(Self { inner }); |
548 | 0 | } |
549 | | } |
550 | | |
551 | | #[cfg(all(windows, feature = "win-system-proxy"))] |
552 | | { |
553 | | use winreg::RegKey; |
554 | | use winreg::enums::{HKEY_CURRENT_USER, KEY_READ}; |
555 | | |
556 | | let registry = RegKey::predef(HKEY_CURRENT_USER); |
557 | | let Ok(ie_settings) = registry.open_subkey_with_flags(REGISTRY_PATH, KEY_READ) else { |
558 | | return None; |
559 | | }; |
560 | | |
561 | | ie_settings |
562 | | .get_value::<String, _>("ProxyOverride") |
563 | | .ok() |
564 | | .map(|no_proxy| NoProxy { |
565 | | inner: no_proxy |
566 | | .split(";") |
567 | | .map(str::trim) |
568 | | // bypass <local>, which tells windows to bypass intranet addresses |
569 | | .filter(|&s| s != "<local>") |
570 | | .map(NoProxyEntry::try_parse) |
571 | | .flatten() |
572 | | .collect(), |
573 | | }) |
574 | | } |
575 | | #[cfg(not(all(windows, feature = "win-system-proxy")))] |
576 | 0 | None |
577 | 0 | } |
578 | | |
579 | 0 | pub fn is_no_proxy(&self, host: &str) -> bool { |
580 | 0 | self.inner.iter().any(|entry| entry.matches(host)) |
581 | 0 | } |
582 | | } |
583 | | |
584 | | impl NoProxyEntry { |
585 | 0 | fn try_parse(u: &str) -> Option<Self> { |
586 | 0 | let entry = match u { |
587 | 0 | "*" => Self::MatchAll, |
588 | 0 | u if u.starts_with("*") => { |
589 | 0 | Self::HostSuffix(u.chars().skip(1).collect::<String>().to_ascii_lowercase()) |
590 | | } |
591 | 0 | u if u.starts_with(".") => Self::HostSuffix(u.to_ascii_lowercase()), |
592 | 0 | u if u.ends_with("*") => Self::HostPrefix( |
593 | 0 | u.chars() |
594 | 0 | .take(u.len() - 1) |
595 | 0 | .collect::<String>() |
596 | 0 | .to_ascii_lowercase(), |
597 | 0 | ), |
598 | 0 | u if u.ends_with(".") => Self::HostPrefix(u.to_ascii_lowercase()), |
599 | 0 | _ => Self::ExactHost(u.to_ascii_lowercase()), |
600 | | }; |
601 | 0 | Some(entry) |
602 | 0 | } |
603 | | |
604 | 0 | fn matches(&self, host: &str) -> bool { |
605 | 0 | match self { |
606 | 0 | NoProxyEntry::MatchAll => true, |
607 | 0 | NoProxyEntry::ExactHost(pattern) => { |
608 | | // Fast path: if host is already lowercase, do direct comparison |
609 | 0 | if host.chars().all(|c| !c.is_ascii_uppercase()) { |
610 | 0 | pattern == host |
611 | | } else { |
612 | | // Slow path: convert host to lowercase and compare |
613 | 0 | pattern == &host.to_ascii_lowercase() |
614 | | } |
615 | | } |
616 | 0 | NoProxyEntry::HostPrefix(prefix) => { |
617 | 0 | if host.len() < prefix.len() { |
618 | 0 | return false; |
619 | 0 | } |
620 | 0 | let host_prefix = &host[..prefix.len()]; |
621 | | // Fast path: if host prefix is already lowercase, do direct comparison |
622 | 0 | if host_prefix.chars().all(|c| !c.is_ascii_uppercase()) { |
623 | 0 | prefix == host_prefix |
624 | | } else { |
625 | | // Slow path: convert host prefix to lowercase and compare |
626 | 0 | prefix == &host_prefix.to_ascii_lowercase() |
627 | | } |
628 | | } |
629 | 0 | NoProxyEntry::HostSuffix(suffix) => { |
630 | 0 | if host.len() < suffix.len() { |
631 | 0 | return false; |
632 | 0 | } |
633 | 0 | let host_suffix = &host[host.len() - suffix.len()..]; |
634 | | // Fast path: if host suffix is already lowercase, do direct comparison |
635 | 0 | if host_suffix.chars().all(|c| !c.is_ascii_uppercase()) { |
636 | 0 | suffix == host_suffix |
637 | | } else { |
638 | | // Slow path: convert host suffix to lowercase and compare |
639 | 0 | suffix == &host_suffix.to_ascii_lowercase() |
640 | | } |
641 | | } |
642 | | } |
643 | 0 | } |
644 | | } |
645 | | |
646 | | #[cfg(test)] |
647 | | mod tests { |
648 | | use assert_no_alloc::*; |
649 | | use std::str::FromStr; |
650 | | |
651 | | use super::*; |
652 | | |
653 | | #[test] |
654 | | fn parse_proxy_fakeproto() { |
655 | | assert!(Proxy::new("fakeproto://localhost").is_err()); |
656 | | } |
657 | | |
658 | | #[test] |
659 | | fn parse_proxy_http_user_pass_server_port() { |
660 | | let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999").unwrap(); |
661 | | assert_eq!(proxy.username(), Some("user")); |
662 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
663 | | assert_eq!(proxy.host(), "localhost"); |
664 | | assert_eq!(proxy.port(), 9999); |
665 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Http); |
666 | | } |
667 | | |
668 | | #[test] |
669 | | fn parse_proxy_http_user_pass_server_port_trailing_slash() { |
670 | | let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999/").unwrap(); |
671 | | assert_eq!(proxy.username(), Some("user")); |
672 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
673 | | assert_eq!(proxy.host(), "localhost"); |
674 | | assert_eq!(proxy.port(), 9999); |
675 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Http); |
676 | | } |
677 | | |
678 | | #[test] |
679 | | fn parse_proxy_socks4_user_pass_server_port() { |
680 | | let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap(); |
681 | | assert_eq!(proxy.username(), Some("user")); |
682 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
683 | | assert_eq!(proxy.host(), "localhost"); |
684 | | assert_eq!(proxy.port(), 9999); |
685 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Socks4); |
686 | | assert!(proxy.resolve_target()); |
687 | | } |
688 | | |
689 | | #[test] |
690 | | fn parse_proxy_socks4a_user_pass_server_port() { |
691 | | let proxy = Proxy::new("socks4a://user:p@ssw0rd@localhost:9999").unwrap(); |
692 | | assert_eq!(proxy.username(), Some("user")); |
693 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
694 | | assert_eq!(proxy.host(), "localhost"); |
695 | | assert_eq!(proxy.port(), 9999); |
696 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Socks4A); |
697 | | assert!(!proxy.resolve_target()); |
698 | | } |
699 | | |
700 | | #[test] |
701 | | fn parse_proxy_socks_user_pass_server_port() { |
702 | | let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap(); |
703 | | assert_eq!(proxy.username(), Some("user")); |
704 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
705 | | assert_eq!(proxy.host(), "localhost"); |
706 | | assert_eq!(proxy.port(), 9999); |
707 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Socks5); |
708 | | assert!(proxy.resolve_target()); |
709 | | } |
710 | | |
711 | | #[test] |
712 | | fn parse_proxy_socks5_user_pass_server_port() { |
713 | | let proxy = Proxy::new("socks5://user:p@ssw0rd@localhost:9999").unwrap(); |
714 | | assert_eq!(proxy.username(), Some("user")); |
715 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
716 | | assert_eq!(proxy.host(), "localhost"); |
717 | | assert_eq!(proxy.port(), 9999); |
718 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Socks5); |
719 | | assert!(proxy.resolve_target()); |
720 | | } |
721 | | |
722 | | #[test] |
723 | | fn parse_proxy_socks5h_user_pass_server_port() { |
724 | | let proxy = Proxy::new("socks5h://user:p@ssw0rd@localhost:9999").unwrap(); |
725 | | assert_eq!(proxy.username(), Some("user")); |
726 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
727 | | assert_eq!(proxy.host(), "localhost"); |
728 | | assert_eq!(proxy.port(), 9999); |
729 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Socks5h); |
730 | | assert!(!proxy.resolve_target()); |
731 | | } |
732 | | |
733 | | #[test] |
734 | | fn parse_proxy_user_pass_server_port() { |
735 | | let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap(); |
736 | | assert_eq!(proxy.username(), Some("user")); |
737 | | assert_eq!(proxy.password(), Some("p@ssw0rd")); |
738 | | assert_eq!(proxy.host(), "localhost"); |
739 | | assert_eq!(proxy.port(), 9999); |
740 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Http); |
741 | | } |
742 | | |
743 | | #[test] |
744 | | fn parse_proxy_server_port() { |
745 | | let proxy = Proxy::new("localhost:9999").unwrap(); |
746 | | assert_eq!(proxy.username(), None); |
747 | | assert_eq!(proxy.password(), None); |
748 | | assert_eq!(proxy.host(), "localhost"); |
749 | | assert_eq!(proxy.port(), 9999); |
750 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Http); |
751 | | } |
752 | | |
753 | | #[test] |
754 | | fn parse_proxy_server() { |
755 | | let proxy = Proxy::new("localhost").unwrap(); |
756 | | assert_eq!(proxy.username(), None); |
757 | | assert_eq!(proxy.password(), None); |
758 | | assert_eq!(proxy.host(), "localhost"); |
759 | | assert_eq!(proxy.port(), 80); |
760 | | assert_eq!(proxy.inner.proto, ProxyProtocol::Http); |
761 | | } |
762 | | |
763 | | #[test] |
764 | | fn no_proxy_exact_host_matching() { |
765 | | let p = Proxy::builder(ProxyProtocol::Http) |
766 | | .host("proxy.example.com") |
767 | | .port(8080) |
768 | | .no_proxy("localhost") |
769 | | .no_proxy("127.0.0.1") |
770 | | .no_proxy("api.internal.com") |
771 | | .build() |
772 | | .unwrap(); |
773 | | |
774 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
775 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
776 | | p.is_no_proxy(&uri) |
777 | | } |
778 | | |
779 | | // Should match exact hosts |
780 | | assert!(is_no_proxy(&p, "localhost")); |
781 | | assert!(is_no_proxy(&p, "127.0.0.1")); |
782 | | assert!(is_no_proxy(&p, "api.internal.com")); |
783 | | |
784 | | // Should not match partial or different hosts |
785 | | assert!(!is_no_proxy(&p, "mylocalhost")); |
786 | | assert!(!is_no_proxy(&p, "localhost.example.com")); |
787 | | assert!(!is_no_proxy(&p, "127.0.0.2")); |
788 | | assert!(!is_no_proxy(&p, "api.internal.com.evil.com")); |
789 | | assert!(!is_no_proxy(&p, "docs.rs")); |
790 | | } |
791 | | |
792 | | #[test] |
793 | | fn no_proxy_wildcard_suffix_matching() { |
794 | | let p = Proxy::builder(ProxyProtocol::Http) |
795 | | .host("proxy.example.com") |
796 | | .port(8080) |
797 | | .no_proxy("*.internal.com") |
798 | | .no_proxy("*.dev") |
799 | | .build() |
800 | | .unwrap(); |
801 | | |
802 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
803 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
804 | | p.is_no_proxy(&uri) |
805 | | } |
806 | | |
807 | | // Should match wildcard suffixes |
808 | | assert!(is_no_proxy(&p, "api.internal.com")); |
809 | | assert!(is_no_proxy(&p, "auth.internal.com")); |
810 | | assert!(is_no_proxy(&p, "db.internal.com")); |
811 | | assert!(is_no_proxy(&p, "app.dev")); |
812 | | assert!(is_no_proxy(&p, "test.dev")); |
813 | | |
814 | | // Should not match the bare suffix or unrelated hosts |
815 | | assert!(!is_no_proxy(&p, "internal.com")); |
816 | | assert!(!is_no_proxy(&p, "dev")); |
817 | | assert!(!is_no_proxy(&p, "api.external.com")); |
818 | | assert!(!is_no_proxy(&p, "app.prod")); |
819 | | assert!(!is_no_proxy(&p, "docs.rs")); |
820 | | } |
821 | | |
822 | | #[test] |
823 | | fn no_proxy_dot_suffix_matching() { |
824 | | let p = Proxy::builder(ProxyProtocol::Http) |
825 | | .host("proxy.example.com") |
826 | | .port(8080) |
827 | | .no_proxy(".internal.com") |
828 | | .no_proxy(".staging") |
829 | | .build() |
830 | | .unwrap(); |
831 | | |
832 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
833 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
834 | | p.is_no_proxy(&uri) |
835 | | } |
836 | | |
837 | | // Should match dot suffix patterns (only subdomains, not the domain itself) |
838 | | assert!(is_no_proxy(&p, "api.internal.com")); |
839 | | assert!(is_no_proxy(&p, "auth.internal.com")); |
840 | | assert!(is_no_proxy(&p, "db.sub.internal.com")); |
841 | | assert!(is_no_proxy(&p, "app.staging")); |
842 | | assert!(is_no_proxy(&p, "test.staging")); |
843 | | |
844 | | // Should NOT match the bare domain (key difference from wildcard) |
845 | | assert!(!is_no_proxy(&p, "internal.com")); |
846 | | assert!(!is_no_proxy(&p, "staging")); |
847 | | |
848 | | // Should not match unrelated hosts |
849 | | assert!(!is_no_proxy(&p, "api.external.com")); |
850 | | assert!(!is_no_proxy(&p, "prod")); |
851 | | assert!(!is_no_proxy(&p, "docs.rs")); |
852 | | } |
853 | | |
854 | | #[test] |
855 | | fn no_proxy_match_all_wildcard() { |
856 | | let p = Proxy::builder(ProxyProtocol::Http) |
857 | | .host("proxy.example.com") |
858 | | .port(8080) |
859 | | .no_proxy("*") |
860 | | .build() |
861 | | .unwrap(); |
862 | | |
863 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
864 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
865 | | p.is_no_proxy(&uri) |
866 | | } |
867 | | |
868 | | // Should match everything when using "*" |
869 | | assert!(is_no_proxy(&p, "localhost")); |
870 | | assert!(is_no_proxy(&p, "127.0.0.1")); |
871 | | assert!(is_no_proxy(&p, "api.example.com")); |
872 | | assert!(is_no_proxy(&p, "docs.rs")); |
873 | | assert!(is_no_proxy(&p, "github.com")); |
874 | | assert!(is_no_proxy(&p, "any.random.domain")); |
875 | | } |
876 | | |
877 | | #[test] |
878 | | fn no_proxy_mixed_patterns() { |
879 | | let p = Proxy::builder(ProxyProtocol::Http) |
880 | | .host("proxy.example.com") |
881 | | .port(8080) |
882 | | .no_proxy("localhost") // exact host |
883 | | .no_proxy("*.dev") // wildcard suffix |
884 | | .no_proxy(".staging") // dot suffix |
885 | | .no_proxy("127.0.0.1") // exact IP |
886 | | .build() |
887 | | .unwrap(); |
888 | | |
889 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
890 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
891 | | p.is_no_proxy(&uri) |
892 | | } |
893 | | |
894 | | // Should match exact hosts |
895 | | assert!(is_no_proxy(&p, "localhost")); |
896 | | assert!(is_no_proxy(&p, "127.0.0.1")); |
897 | | |
898 | | // Should match wildcard suffixes |
899 | | assert!(is_no_proxy(&p, "api.dev")); |
900 | | assert!(is_no_proxy(&p, "test.dev")); |
901 | | |
902 | | // Should match dot suffixes (only subdomains, not the domain itself) |
903 | | assert!(is_no_proxy(&p, "app.staging")); |
904 | | assert!(!is_no_proxy(&p, "staging")); |
905 | | |
906 | | // Should not match unrelated hosts |
907 | | assert!(!is_no_proxy(&p, "dev")); // bare wildcard suffix |
908 | | assert!(!is_no_proxy(&p, "api.prod")); // different suffix |
909 | | assert!(!is_no_proxy(&p, "docs.rs")); // unrelated |
910 | | assert!(!is_no_proxy(&p, "127.0.0.2")); // different IP |
911 | | } |
912 | | |
913 | | #[test] |
914 | | fn no_proxy_case_insensitive_matching() { |
915 | | let p = Proxy::builder(ProxyProtocol::Http) |
916 | | .host("proxy.example.com") |
917 | | .port(8080) |
918 | | .no_proxy("localhost") |
919 | | .no_proxy("*.Example.Com") |
920 | | .no_proxy(".INTERNAL") |
921 | | .build() |
922 | | .unwrap(); |
923 | | |
924 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
925 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
926 | | p.is_no_proxy(&uri) |
927 | | } |
928 | | |
929 | | // Test exact host matching - should be case insensitive |
930 | | // These patterns are stored as lowercase: "localhost" |
931 | | assert!(is_no_proxy(&p, "localhost")); // fast path: already lowercase |
932 | | assert!(is_no_proxy(&p, "LOCALHOST")); // slow path: needs conversion |
933 | | assert!(is_no_proxy(&p, "LocalHost")); // slow path: needs conversion |
934 | | |
935 | | // Test wildcard suffix case insensitive matching |
936 | | // These patterns are stored as lowercase: ".example.com" |
937 | | assert!(is_no_proxy(&p, "api.example.com")); // fast path: already lowercase |
938 | | assert!(is_no_proxy(&p, "api.EXAMPLE.COM")); // slow path: needs conversion |
939 | | assert!(is_no_proxy(&p, "API.example.COM")); // slow path: needs conversion |
940 | | assert!(is_no_proxy(&p, "api.Example.Com")); // slow path: needs conversion |
941 | | |
942 | | // Test dot suffix case insensitive matching (only matches subdomains) |
943 | | // These patterns are stored as lowercase: ".internal" |
944 | | assert!(is_no_proxy(&p, "app.internal")); // fast path: already lowercase |
945 | | assert!(is_no_proxy(&p, "app.INTERNAL")); // slow path: needs conversion |
946 | | assert!(is_no_proxy(&p, "APP.Internal")); // slow path: needs conversion |
947 | | assert!(!is_no_proxy(&p, "INTERNAL")); // bare domain doesn't match dot suffix |
948 | | assert!(!is_no_proxy(&p, "internal")); // bare domain doesn't match dot suffix |
949 | | } |
950 | | |
951 | | #[test] |
952 | | fn no_proxy_edge_cases() { |
953 | | let p = Proxy::builder(ProxyProtocol::Http) |
954 | | .host("proxy.example.com") |
955 | | .port(8080) |
956 | | .no_proxy("") // empty string |
957 | | .no_proxy("single") // single word |
958 | | .no_proxy("*..") // malformed wildcard |
959 | | .no_proxy("..") // malformed dot suffix |
960 | | .no_proxy("192.168.1.1") // IP address |
961 | | .no_proxy("*.local") // local domain |
962 | | .build() |
963 | | .unwrap(); |
964 | | |
965 | | fn is_no_proxy(p: &Proxy, host: &str) -> bool { |
966 | | let uri = Uri::from_str(&format!("http://{}", host)).unwrap(); |
967 | | p.is_no_proxy(&uri) |
968 | | } |
969 | | |
970 | | // Test exact matching of various formats |
971 | | assert!(is_no_proxy(&p, "single")); |
972 | | assert!(is_no_proxy(&p, "192.168.1.1")); |
973 | | assert!(!is_no_proxy(&p, "192.168.1.2")); |
974 | | |
975 | | // Test wildcard with local domains |
976 | | assert!(is_no_proxy(&p, "printer.local")); |
977 | | assert!(is_no_proxy(&p, "router.local")); |
978 | | assert!(!is_no_proxy(&p, "local")); // bare domain |
979 | | |
980 | | // Test that malformed patterns don't break things |
981 | | assert!(is_no_proxy(&p, "something..")); // matches exactly |
982 | | assert!(!is_no_proxy(&p, "something.else")); |
983 | | |
984 | | // Test empty string exact match |
985 | | // Note: This is likely an edge case that shouldn't happen in practice |
986 | | // but we want to ensure it doesn't crash |
987 | | } |
988 | | |
989 | | #[test] |
990 | | fn proxy_clone_does_not_allocate() { |
991 | | let c = Proxy::new("socks://1.2.3.4").unwrap(); |
992 | | assert_no_alloc(|| c.clone()); |
993 | | } |
994 | | |
995 | | #[test] |
996 | | fn proxy_new_default_scheme() { |
997 | | let c = Proxy::new("localhost:1234").unwrap(); |
998 | | assert_eq!(c.protocol(), ProxyProtocol::Http); |
999 | | assert_eq!(c.uri(), "http://localhost:1234"); |
1000 | | } |
1001 | | |
1002 | | #[test] |
1003 | | fn proxy_empty_env_url() { |
1004 | | let result = Proxy::new_with_flag("", None, false, None); |
1005 | | assert!(result.is_err()); |
1006 | | } |
1007 | | |
1008 | | #[test] |
1009 | | fn proxy_invalid_env_url() { |
1010 | | let result = Proxy::new_with_flag("r32/?//52:**", None, false, None); |
1011 | | assert!(result.is_err()); |
1012 | | } |
1013 | | |
1014 | | #[test] |
1015 | | fn proxy_builder() { |
1016 | | let proxy = Proxy::builder(ProxyProtocol::Socks4) |
1017 | | .host("my-proxy.com") |
1018 | | .port(5551) |
1019 | | .resolve_target(false) |
1020 | | .build() |
1021 | | .unwrap(); |
1022 | | |
1023 | | assert_eq!(proxy.protocol(), ProxyProtocol::Socks4); |
1024 | | assert_eq!(proxy.uri(), "SOCKS4://my-proxy.com:5551/"); |
1025 | | assert_eq!(proxy.host(), "my-proxy.com"); |
1026 | | assert_eq!(proxy.port(), 5551); |
1027 | | assert_eq!(proxy.username(), None); |
1028 | | assert_eq!(proxy.password(), None); |
1029 | | assert_eq!(proxy.is_from_env(), false); |
1030 | | assert_eq!(proxy.resolve_target(), false); |
1031 | | } |
1032 | | |
1033 | | #[test] |
1034 | | fn proxy_builder_username() { |
1035 | | let proxy = Proxy::builder(ProxyProtocol::Https) |
1036 | | .username("hemligearne") |
1037 | | .build() |
1038 | | .unwrap(); |
1039 | | |
1040 | | assert_eq!(proxy.protocol(), ProxyProtocol::Https); |
1041 | | assert_eq!(proxy.uri(), "https://hemligearne@localhost:443/"); |
1042 | | assert_eq!(proxy.host(), "localhost"); |
1043 | | assert_eq!(proxy.port(), 443); |
1044 | | assert_eq!(proxy.username(), Some("hemligearne")); |
1045 | | assert_eq!(proxy.password(), None); |
1046 | | assert_eq!(proxy.is_from_env(), false); |
1047 | | assert_eq!(proxy.resolve_target(), false); |
1048 | | } |
1049 | | |
1050 | | #[test] |
1051 | | fn proxy_builder_username_password() { |
1052 | | let proxy = Proxy::builder(ProxyProtocol::Https) |
1053 | | .username("hemligearne") |
1054 | | .password("kulgrej") |
1055 | | .build() |
1056 | | .unwrap(); |
1057 | | |
1058 | | assert_eq!(proxy.protocol(), ProxyProtocol::Https); |
1059 | | assert_eq!(proxy.uri(), "https://hemligearne:kulgrej@localhost:443/"); |
1060 | | assert_eq!(proxy.host(), "localhost"); |
1061 | | assert_eq!(proxy.port(), 443); |
1062 | | assert_eq!(proxy.username(), Some("hemligearne")); |
1063 | | assert_eq!(proxy.password(), Some("kulgrej")); |
1064 | | assert_eq!(proxy.is_from_env(), false); |
1065 | | assert_eq!(proxy.resolve_target(), false); |
1066 | | } |
1067 | | } |