#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct SystemProxySettings {
pub(crate) proxy: Option<String>,
pub(crate) bypass: Option<String>,
pub(crate) auto_config: bool,
pub(crate) auto_config_url: Option<String>,
pub(crate) auto_detect: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum UrlRoute {
Direct,
Proxy(String),
}
pub(crate) fn scheme_and_host(url: &str) -> Option<(String, String)> {
let uri: ureq::http::Uri = url.parse().ok()?;
let scheme = uri.scheme_str()?.to_ascii_lowercase();
if scheme != "http" && scheme != "https" {
return None;
}
let host = uri
.host()?
.trim_start_matches('[')
.trim_end_matches(']')
.to_ascii_lowercase();
(!host.is_empty()).then_some((scheme, host))
}
fn glob_match(pattern: &str, text: &str) -> bool {
let pattern: Vec<char> = pattern.to_lowercase().chars().collect();
let text: Vec<char> = text.to_lowercase().chars().collect();
let (mut p, mut t) = (0, 0);
let mut star: Option<(usize, usize)> = None;
while t < text.len() {
if p < pattern.len() && pattern[p] == '*' {
star = Some((p, t));
p += 1;
} else if p < pattern.len() && pattern[p] == text[t] {
p += 1;
t += 1;
} else if let Some((sp, st)) = star {
p = sp + 1;
t = st + 1;
star = Some((sp, st + 1));
} else {
return false;
}
}
while p < pattern.len() && pattern[p] == '*' {
p += 1;
}
p == pattern.len()
}
pub(crate) fn bypass_matches(bypass: &str, scheme: &str, host: &str) -> bool {
bypass
.split(|c: char| c == ';' || c.is_whitespace())
.map(str::trim)
.filter(|e| !e.is_empty())
.any(|entry| {
if entry.eq_ignore_ascii_case("<local>") {
return !host.contains('.') && !host.contains(':');
}
let (entry_scheme, rest) = match entry.split_once("://") {
Some((s, rest)) => (Some(s), rest),
None => (None, entry),
};
if entry_scheme.is_some_and(|s| !s.eq_ignore_ascii_case(scheme)) {
return false;
}
let rest = rest.trim_end_matches('/');
let rest = if let Some(inner) = rest.strip_prefix('[') {
inner.split_once(']').map_or(inner, |(addr, _)| addr)
} else {
match rest.rsplit_once(':') {
Some((head, port))
if !port.is_empty()
&& port.chars().all(|c| c.is_ascii_digit())
&& !head.contains(':') =>
{
head
}
_ => rest,
}
};
if rest.is_empty() {
return false;
}
match rest.strip_prefix('.') {
Some(suffix) if !suffix.contains('*') => {
let host = host.to_ascii_lowercase();
let suffix = suffix.to_ascii_lowercase();
host == suffix || host.ends_with(&format!(".{suffix}"))
}
_ => glob_match(rest, host),
}
})
}
pub(crate) fn static_route_for_url(
settings: &SystemProxySettings,
scheme: &str,
host: &str,
) -> Option<UrlRoute> {
let raw = settings.proxy.as_deref().unwrap_or_default().trim();
let (http, https) = endpoints_by_scheme(raw);
if http.is_none() && https.is_none() {
return None;
}
let bypass = settings.bypass.as_deref().unwrap_or_default();
if bypass_matches(bypass, scheme, host) {
return Some(UrlRoute::Direct);
}
let endpoint = if scheme == "https" { https } else { http };
Some(endpoint.map_or(UrlRoute::Direct, UrlRoute::Proxy))
}
#[cfg_attr(not(windows), allow(dead_code))] pub(crate) fn route_from_proxy_string(proxy: Option<&str>, scheme: &str) -> UrlRoute {
let (http, https) = endpoints_by_scheme(proxy.unwrap_or_default());
let endpoint = if scheme == "https" { https } else { http };
endpoint.map_or(UrlRoute::Direct, UrlRoute::Proxy)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum SystemProxyChoice {
None,
Single {
url: String,
no_proxy: Vec<String>,
},
AutoDetectOnly,
PerRequest,
Unsupported(String),
}
pub(crate) fn endpoints_by_scheme(proxy: &str) -> (Option<String>, Option<String>) {
let mut any = None;
let mut http = None;
let mut https = None;
for entry in proxy
.split(|c: char| c == ';' || c.is_whitespace())
.map(str::trim)
.filter(|e| !e.is_empty())
{
match entry.split_once('=') {
Some((protocol, value)) => match protocol.trim().to_ascii_lowercase().as_str() {
"http" => http = http.or(Some(value.trim())),
"https" => https = https.or(Some(value.trim())),
_ => {}
},
None => any = any.or(Some(entry)),
}
}
let url = |chosen: Option<&str>| {
let chosen = chosen.filter(|v| !v.is_empty())?;
let url = if chosen.contains("://") {
chosen.to_string()
} else {
format!("http://{chosen}")
};
let scheme = url
.split("://")
.next()
.unwrap_or_default()
.to_ascii_lowercase();
(scheme == "http" || scheme == "https").then_some(url)
};
let http = if http.is_some() { url(http) } else { url(any) };
let https = if https.is_some() {
url(https)
} else {
url(any)
};
(http, https)
}
pub(crate) fn bypass_hosts(bypass: &str) -> Result<Vec<String>, String> {
let mut no_proxy = Vec::new();
for entry in bypass
.split(|c: char| c == ';' || c.is_whitespace())
.map(str::trim)
.filter(|e| !e.is_empty())
{
let host = entry.strip_prefix('*').unwrap_or(entry);
let expressible = !entry.contains("://")
&& !entry.contains('<')
&& !host.contains('*')
&& !host.is_empty()
&& host != "."
&& (host.len() == entry.len() || host.starts_with('.'));
if !expressible {
return Err(entry.to_string());
}
if !no_proxy.iter().any(|known: &String| known == host) {
no_proxy.push(host.to_string());
}
}
Ok(no_proxy)
}
pub(crate) fn proxy_choice(settings: &SystemProxySettings) -> SystemProxyChoice {
if settings.auto_config {
return SystemProxyChoice::Unsupported("a proxy auto-config script is set".into());
}
let raw = settings.proxy.as_deref().unwrap_or_default().trim();
if settings.auto_detect && raw.is_empty() {
return SystemProxyChoice::AutoDetectOnly;
}
let (http, https) = endpoints_by_scheme(raw);
match (http, https) {
(None, None) => SystemProxyChoice::None,
(Some(http), Some(https)) if http.eq_ignore_ascii_case(&https) => {
match bypass_hosts(settings.bypass.as_deref().unwrap_or_default()) {
Ok(no_proxy) => SystemProxyChoice::Single {
url: http,
no_proxy,
},
Err(_) => SystemProxyChoice::PerRequest,
}
}
_ => SystemProxyChoice::PerRequest,
}
}
#[cfg(not(windows))]
pub(crate) fn system_proxy_settings() -> SystemProxySettings {
SystemProxySettings::default()
}
#[cfg(windows)]
pub(crate) fn system_proxy_settings() -> SystemProxySettings {
use win::take;
#[repr(C)]
struct IeProxyConfig {
auto_detect: i32,
auto_config_url: *mut u16,
proxy: *mut u16,
proxy_bypass: *mut u16,
}
#[link(name = "winhttp")]
unsafe extern "system" {
fn WinHttpGetIEProxyConfigForCurrentUser(config: *mut IeProxyConfig) -> i32;
}
let mut config = IeProxyConfig {
auto_detect: 0,
auto_config_url: std::ptr::null_mut(),
proxy: std::ptr::null_mut(),
proxy_bypass: std::ptr::null_mut(),
};
if unsafe { WinHttpGetIEProxyConfigForCurrentUser(&mut config) } == 0 {
return SystemProxySettings::default();
}
let (pac, proxy, bypass) = unsafe {
(
take(config.auto_config_url),
take(config.proxy),
take(config.proxy_bypass),
)
};
let auto_config_url = pac
.map(|url| url.trim().to_string())
.filter(|u| !u.is_empty());
SystemProxySettings {
proxy,
bypass,
auto_config: auto_config_url.is_some(),
auto_config_url,
auto_detect: config.auto_detect != 0,
}
}
#[cfg(windows)]
mod win {
use std::ffi::c_void;
#[link(name = "kernel32")]
unsafe extern "system" {
fn GlobalFree(memory: *mut c_void) -> *mut c_void;
}
pub(super) unsafe fn take(text: *mut u16) -> Option<String> {
if text.is_null() {
return None;
}
let mut len = 0;
while unsafe { *text.add(len) } != 0 {
len += 1;
}
let value = String::from_utf16_lossy(unsafe { std::slice::from_raw_parts(text, len) });
unsafe { GlobalFree(text.cast()) };
Some(value)
}
}
#[cfg(not(windows))]
pub(crate) fn winhttp_route_for_url(
_url: &str,
_settings: &SystemProxySettings,
) -> Option<UrlRoute> {
None
}
#[cfg(windows)]
pub(crate) fn winhttp_route_for_url(url: &str, settings: &SystemProxySettings) -> Option<UrlRoute> {
use std::ffi::c_void;
const ACCESS_TYPE_NO_PROXY: u32 = 1;
const AUTOPROXY_AUTO_DETECT: u32 = 0x0000_0001;
const AUTOPROXY_CONFIG_URL: u32 = 0x0000_0002;
const AUTO_DETECT_DHCP: u32 = 0x0000_0001;
const AUTO_DETECT_DNS_A: u32 = 0x0000_0002;
const ACCESS_TYPE_NAMED_PROXY: u32 = 3;
const TIMEOUT_MS: i32 = 5000;
#[repr(C)]
struct AutoProxyOptions {
flags: u32,
auto_detect_flags: u32,
auto_config_url: *const u16,
reserved: *mut c_void,
reserved2: u32,
auto_logon_if_challenged: i32,
}
#[repr(C)]
struct ProxyInfo {
access_type: u32,
proxy: *mut u16,
proxy_bypass: *mut u16,
}
#[link(name = "winhttp")]
unsafe extern "system" {
fn WinHttpOpen(
agent: *const u16,
access_type: u32,
proxy: *const u16,
bypass: *const u16,
flags: u32,
) -> *mut c_void;
fn WinHttpSetTimeouts(
session: *mut c_void,
resolve: i32,
connect: i32,
send: i32,
receive: i32,
) -> i32;
fn WinHttpGetProxyForUrl(
session: *mut c_void,
url: *const u16,
options: *mut AutoProxyOptions,
info: *mut ProxyInfo,
) -> i32;
fn WinHttpCloseHandle(handle: *mut c_void) -> i32;
}
struct Session(*mut c_void);
impl Drop for Session {
fn drop(&mut self) {
unsafe { WinHttpCloseHandle(self.0) };
}
}
let (scheme, _) = scheme_and_host(url)?;
let wide = |text: &str| -> Vec<u16> { text.encode_utf16().chain(Some(0)).collect() };
let wide_url = wide(url);
let wide_pac = settings
.auto_config_url
.as_deref()
.filter(|u| !u.is_empty())
.map(wide);
let mut flags = 0;
if settings.auto_detect {
flags |= AUTOPROXY_AUTO_DETECT;
}
if wide_pac.is_some() {
flags |= AUTOPROXY_CONFIG_URL;
}
if flags == 0 {
return None;
}
let mut options = AutoProxyOptions {
flags,
auto_detect_flags: if settings.auto_detect {
AUTO_DETECT_DHCP | AUTO_DETECT_DNS_A
} else {
0
},
auto_config_url: wide_pac
.as_ref()
.map_or(std::ptr::null(), |pac| pac.as_ptr()),
reserved: std::ptr::null_mut(),
reserved2: 0,
auto_logon_if_challenged: 1,
};
let mut info = ProxyInfo {
access_type: 0,
proxy: std::ptr::null_mut(),
proxy_bypass: std::ptr::null_mut(),
};
let session = unsafe {
WinHttpOpen(
std::ptr::null(),
ACCESS_TYPE_NO_PROXY,
std::ptr::null(),
std::ptr::null(),
0,
)
};
if session.is_null() {
return None;
}
let session = Session(session);
unsafe { WinHttpSetTimeouts(session.0, TIMEOUT_MS, TIMEOUT_MS, TIMEOUT_MS, TIMEOUT_MS) };
let ok =
unsafe { WinHttpGetProxyForUrl(session.0, wide_url.as_ptr(), &mut options, &mut info) };
if ok == 0 {
return None;
}
let (proxy, _bypass) = unsafe { (win::take(info.proxy), win::take(info.proxy_bypass)) };
if info.access_type == ACCESS_TYPE_NAMED_PROXY {
Some(route_from_proxy_string(proxy.as_deref(), &scheme))
} else {
Some(UrlRoute::Direct)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn settings(proxy: &str, bypass: &str) -> SystemProxySettings {
SystemProxySettings {
proxy: Some(proxy.into()).filter(|p: &String| !p.is_empty()),
bypass: Some(bypass.into()).filter(|b: &String| !b.is_empty()),
..SystemProxySettings::default()
}
}
fn single(url: &str, no_proxy: &[&str]) -> SystemProxyChoice {
SystemProxyChoice::Single {
url: url.into(),
no_proxy: no_proxy.iter().map(|s| s.to_string()).collect(),
}
}
fn unsupported(element: &str) -> SystemProxyChoice {
SystemProxyChoice::Unsupported(element.into())
}
#[test]
fn bypass_matching_is_exact_per_url() {
let rows: &[(&str, &str, &str, bool)] = &[
("<local>", "https", "intranet", true),
("<local>", "https", "intranet.corp", false),
("<LOCAL>", "http", "[::1]", false),
("*.corp.example", "https", "a.corp.example", true),
("*.corp.example", "https", "corp.example", false),
("10.*", "https", "10.1.2.3", true),
("10.*", "https", "110.1.2.3", false),
("192.168.*.1", "http", "192.168.7.1", true),
("*", "http", "anything.example", true),
("Host.Example", "https", "host.example", true),
("host.example:8443", "https", "host.example", true),
(".suffix.example", "https", "a.suffix.example", true),
(".suffix.example", "https", "suffix.example", true),
(".suffix.example", "https", "notsuffix.example", false),
("https://only.example", "https", "only.example", true),
("https://only.example", "http", "only.example", false),
("a;b c", "http", "d", false),
("a;b c", "http", "b", true),
("", "http", "x", false),
];
for (bypass, scheme, host, expected) in rows {
assert_eq!(
bypass_matches(bypass, scheme, host),
*expected,
"{bypass} {scheme}://{host}"
);
}
}
#[test]
fn bypass_matching_scheme_limits_ports_and_literals() {
let rows: &[(&str, &str, &str, bool)] = &[
("HTTPS://only.example", "https", "only.example", true),
("http://only.example", "https", "only.example", false),
("https://*.corp.example", "https", "a.corp.example", true),
("https://*.corp.example", "http", "a.corp.example", false),
("https://only.example/", "https", "only.example", true),
("<local>", "http", "localhost", true),
("<local>", "http", "127.0.0.1", false),
("<local>", "https", "fe80::1", false),
("localhost;<local>", "https", "files", true),
("other;<local>", "https", "files.corp", false),
("*.corp.*", "http", "a.corp.example", true),
("a*c", "http", "abbbc", true),
("a*c", "http", "abbbd", false),
("[::1]", "http", "::1", true),
("[::1]:8080", "http", "::1", true),
("::1", "http", "::1", true),
("a.example b.example", "http", "b.example", true),
(" ; ;", "http", "x", false),
];
for (bypass, scheme, host, expected) in rows {
assert_eq!(
bypass_matches(bypass, scheme, host),
*expected,
"{bypass} {scheme}://{host}"
);
}
}
#[test]
fn a_split_per_scheme_config_routes_each_url_on_its_own() {
let s = settings(
"http=plain.corp:8080;https=secure.corp:8443",
"<local>;*.corp.example;10.*;https://only-tls.example",
);
assert_eq!(proxy_choice(&s), SystemProxyChoice::PerRequest);
let route = |scheme: &str, host: &str| static_route_for_url(&s, scheme, host);
let proxy = |u: &str| Some(UrlRoute::Proxy(u.into()));
let direct = Some(UrlRoute::Direct);
assert_eq!(
route("http", "api.example.com"),
proxy("http://plain.corp:8080")
);
assert_eq!(
route("https", "api.example.com"),
proxy("http://secure.corp:8443")
);
assert_eq!(route("http", "intranet"), direct);
assert_eq!(route("https", "intranet"), direct);
assert_eq!(route("https", "a.corp.example"), direct);
assert_eq!(route("http", "10.1.2.3"), direct);
assert_eq!(
route("https", "110.1.2.3"),
proxy("http://secure.corp:8443")
);
assert_eq!(route("https", "only-tls.example"), direct);
assert_eq!(
route("http", "only-tls.example"),
proxy("http://plain.corp:8080")
);
}
#[test]
fn a_scheme_with_no_endpoint_is_direct_under_a_split_config() {
let s = settings("https=secure.corp:8443", "http://skip.example");
assert_eq!(proxy_choice(&s), SystemProxyChoice::PerRequest);
assert_eq!(
static_route_for_url(&s, "http", "x.example"),
Some(UrlRoute::Direct)
);
assert_eq!(
static_route_for_url(&s, "https", "skip.example"),
Some(UrlRoute::Proxy("http://secure.corp:8443".into()))
);
}
#[test]
fn urls_reduce_to_scheme_and_host() {
assert_eq!(
scheme_and_host("HTTPS://Feed.Example.com:8443/a?b=c"),
Some(("https".into(), "feed.example.com".into()))
);
assert_eq!(
scheme_and_host("http://[::1]:80/"),
Some(("http".into(), "::1".into()))
);
for bad in ["ftp://h/", "/path", "not a url", ""] {
assert_eq!(scheme_and_host(bad), None, "{bad}");
}
}
#[test]
fn a_resolver_proxy_string_gives_the_first_candidate_for_the_scheme() {
let http = |s: &str| UrlRoute::Proxy(s.into());
assert_eq!(
route_from_proxy_string(Some("a:80;b:80"), "https"),
http("http://a:80")
);
assert_eq!(
route_from_proxy_string(Some("http=h:80;https=s:443"), "https"),
http("http://s:443")
);
assert_eq!(
route_from_proxy_string(Some("http=h:80"), "https"),
UrlRoute::Direct
);
assert_eq!(route_from_proxy_string(None, "http"), UrlRoute::Direct);
assert_eq!(route_from_proxy_string(Some(""), "http"), UrlRoute::Direct);
}
#[test]
fn the_static_route_for_a_url_applies_bypass_and_scheme() {
let s = settings("http=h:80;https=s:443", "<local>;*.corp");
assert_eq!(
static_route_for_url(&s, "http", "x.example"),
Some(UrlRoute::Proxy("http://h:80".into()))
);
assert_eq!(
static_route_for_url(&s, "https", "x.example"),
Some(UrlRoute::Proxy("http://s:443".into()))
);
assert_eq!(
static_route_for_url(&s, "https", "box"),
Some(UrlRoute::Direct)
);
assert_eq!(
static_route_for_url(&s, "https", "a.corp"),
Some(UrlRoute::Direct)
);
let only_https = settings("https=s:443", "");
assert_eq!(
static_route_for_url(&only_https, "http", "x.example"),
Some(UrlRoute::Direct)
);
assert_eq!(
static_route_for_url(&settings("", "<local>"), "http", "x"),
None
);
assert_eq!(
static_route_for_url(&settings("socks=k:1", ""), "http", "x"),
None
);
}
#[test]
fn a_bare_host_and_port_is_one_http_proxy_for_both_schemes() {
assert_eq!(
endpoints_by_scheme("proxy.corp:3128"),
(
Some("http://proxy.corp:3128".into()),
Some("http://proxy.corp:3128".into())
)
);
assert_eq!(
proxy_choice(&settings("proxy.corp:3128", "")),
single("http://proxy.corp:3128", &[])
);
}
#[test]
fn each_scheme_gets_its_own_entry_and_never_the_other_one() {
let rows: &[(&str, Option<&str>, Option<&str>)] = &[
(
"http=h:80;https=s:443",
Some("http://h:80"),
Some("http://s:443"),
),
(
"https=s:443;http=h:80",
Some("http://h:80"),
Some("http://s:443"),
),
("https=s:443", None, Some("http://s:443")),
("http=h:80", Some("http://h:80"), None),
(
"https=s:443;any:8080",
Some("http://any:8080"),
Some("http://s:443"),
),
(
"any:8080 http=h:80",
Some("http://h:80"),
Some("http://any:8080"),
),
("ftp=f:21;socks=k:1080", None, None),
("ftp=f:21;socks=k:1080;http=h:80", Some("http://h:80"), None),
("http=first:1;http=second:2", Some("http://first:1"), None),
(
"https=https://tls.corp:8443",
None,
Some("https://tls.corp:8443"),
),
(
"https=socks5://k:1080;any:8080",
Some("http://any:8080"),
None,
),
];
for (proxy, http, https) in rows {
assert_eq!(
endpoints_by_scheme(proxy),
(http.map(str::to_string), https.map(str::to_string)),
"{proxy}"
);
}
}
#[test]
fn the_same_endpoint_for_both_schemes_is_one_proxy() {
assert_eq!(
proxy_choice(&settings("http=p.corp:8080;https=P.CORP:8080", "")),
single("http://p.corp:8080", &[])
);
}
#[test]
fn different_endpoints_or_one_direct_scheme_are_routed_per_request() {
for proxy in [
"http=web.corp:8080;https=secure.corp:8443",
"https=secure.corp:8443",
"http=web.corp:8080",
] {
assert_eq!(
proxy_choice(&settings(proxy, "")),
SystemProxyChoice::PerRequest,
"{proxy}"
);
}
}
#[test]
fn nothing_usable_is_no_proxy() {
assert_eq!(
proxy_choice(&SystemProxySettings::default()),
SystemProxyChoice::None
);
assert_eq!(
proxy_choice(&settings(" ", "localhost")),
SystemProxyChoice::None
);
assert_eq!(
proxy_choice(&settings("socks=k:1080", "")),
SystemProxyChoice::None
);
assert_eq!(
proxy_choice(&settings("socks5://k:1080", "")),
SystemProxyChoice::None
);
}
#[test]
fn a_bypass_list_of_plain_hosts_is_kept_whole() {
assert_eq!(
bypass_hosts("localhost; *.corp.example ;intranet 10.0.0.1;localhost;.suffix.example"),
Ok(vec![
"localhost".to_string(),
".corp.example".to_string(),
"intranet".to_string(),
"10.0.0.1".to_string(),
".suffix.example".to_string(),
])
);
assert_eq!(bypass_hosts(""), Ok(vec![]));
assert_eq!(
proxy_choice(&settings("p:1", "localhost;*.corp.example")),
single("http://p:1", &["localhost", ".corp.example"])
);
}
#[test]
fn a_bypass_entry_a_by_host_list_cannot_express_is_routed_per_request_never_dropped() {
let rows = [
("https://only.example;both.example", "https://only.example"),
("both.example;http://plain.example", "http://plain.example"),
("HTTP://plain.example", "HTTP://plain.example"),
("localhost;<local>", "<local>"),
("<LOCAL>", "<LOCAL>"),
("10.*", "10.*"),
("*.corp.example;192.168.*.1", "192.168.*.1"),
("*", "*"),
("*corp.example", "*corp.example"),
(".", "."),
];
for (bypass, entry) in rows {
assert_eq!(bypass_hosts(bypass), Err(entry.to_string()), "{bypass}");
assert_eq!(
proxy_choice(&settings("p:1", bypass)),
SystemProxyChoice::PerRequest,
"{bypass}"
);
}
assert_eq!(
proxy_choice(&settings("", "https://only.example;<local>")),
SystemProxyChoice::None
);
assert_eq!(
proxy_choice(&settings("http=h:80;https=s:443", "<local>")),
SystemProxyChoice::PerRequest
);
}
#[test]
fn a_pac_script_is_reported_with_or_without_a_static_proxy() {
for proxy in ["", "p:1"] {
let pac = SystemProxySettings {
auto_config: true,
..settings(proxy, "")
};
assert_eq!(
proxy_choice(&pac),
unsupported("a proxy auto-config script is set"),
"{proxy:?}"
);
let both = SystemProxySettings {
auto_detect: true,
..pac
};
assert_eq!(
proxy_choice(&both),
unsupported("a proxy auto-config script is set"),
"{proxy:?}"
);
}
}
#[test]
fn automatic_detection_alone_is_not_an_error_and_never_overrides_a_static_proxy() {
for proxy in ["", " "] {
let detect = SystemProxySettings {
auto_detect: true,
..settings(proxy, "<local>")
};
assert_eq!(proxy_choice(&detect), SystemProxyChoice::AutoDetectOnly);
}
let detect = SystemProxySettings {
auto_detect: true,
..settings("p:1", "localhost")
};
assert_eq!(proxy_choice(&detect), single("http://p:1", &["localhost"]));
let detect = SystemProxySettings {
auto_detect: true,
..settings("p:1", "<local>")
};
assert_eq!(proxy_choice(&detect), SystemProxyChoice::PerRequest);
let detect = SystemProxySettings {
auto_detect: true,
..settings("http=a:1;https=b:2", "")
};
assert_eq!(proxy_choice(&detect), SystemProxyChoice::PerRequest);
}
}