use super::{Error, ProxyMode, ProxyOptions, environment_proxy, parse_endpoint};
use alloc::string::String;
use core::fmt;
use url::{Origin, Url};
#[cfg(target_os = "macos")]
mod macos;
#[cfg(any(windows, target_os = "linux", test))]
mod native;
#[cfg(windows)]
mod windows;
#[derive(Clone)]
pub struct OriginProxy {
origin: Origin,
pub(super) configuration: Configuration,
}
impl OriginProxy {
pub fn resolve(url: &Url, options: &ProxyOptions) -> Result<Self, Error> {
if !matches!(url.scheme(), "http" | "https") || url.host_str().is_none() {
return Err(Error::InvalidOrigin("expected an HTTP(S) URL with a host"));
}
let configuration = resolve_options(
options,
|| Environment::capture(url),
|env| {
#[cfg(target_os = "macos")]
return macos::read(url, env);
#[cfg(windows)]
return windows::read(url, env);
#[cfg(target_os = "linux")]
return native::read_linux(url, env);
#[cfg(not(any(target_os = "macos", windows, target_os = "linux")))]
{
let _ = env;
Ok(Route::Automatic)
}
},
)?;
Ok(Self {
origin: url.origin(),
configuration,
})
}
pub fn origin(&self) -> &Origin {
&self.origin
}
pub fn proxy_url(&self) -> Option<&Url> {
self.configuration.proxy.as_ref()
}
pub fn no_proxy(&self) -> &str {
&self.configuration.no_proxy
}
pub fn check_origin(&self, url: &Url) -> Result<(), Error> {
if url.origin() != self.origin {
return Err(Error::InvalidOrigin(
"request must use the configured origin",
));
}
Ok(())
}
}
impl fmt::Debug for OriginProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OriginProxy")
.field("origin", &self.origin)
.finish_non_exhaustive()
}
}
#[derive(Default)]
struct Environment {
proxy: String,
all_proxy: String,
no_proxy: String,
no_proxy_override: bool,
cgi: bool,
}
impl Environment {
fn capture(url: &Url) -> Self {
fn first(upper: &str, lower: &str) -> String {
std::env::var(upper)
.or_else(|_| std::env::var(lower))
.unwrap_or_default()
}
Self {
proxy: match url.scheme() {
"https" => first("HTTPS_PROXY", "https_proxy"),
_ => first("HTTP_PROXY", "http_proxy"),
},
all_proxy: first("ALL_PROXY", "all_proxy"),
no_proxy: first("NO_PROXY", "no_proxy"),
no_proxy_override: false,
cgi: std::env::var_os("REQUEST_METHOD").is_some(),
}
}
fn use_native_bypass(&self) -> bool {
!self.no_proxy_override && self.no_proxy.is_empty()
}
}
#[derive(Debug, PartialEq)]
enum Route {
Automatic,
Direct,
Native(Url),
}
#[derive(Clone)]
pub(super) struct Configuration {
pub(super) proxy: Option<Url>,
pub(super) no_proxy: String,
}
fn resolve_options(
options: &ProxyOptions,
capture: impl FnOnce() -> Environment,
native: impl FnOnce(&Environment) -> Result<Route, Error>,
) -> Result<Configuration, Error> {
match &options.mode {
ProxyMode::Direct => Ok(Configuration {
proxy: None,
no_proxy: String::new(),
}),
ProxyMode::Explicit(input) => {
let proxy = parse_endpoint(input)?;
let no_proxy = options
.no_proxy
.clone()
.unwrap_or_else(|| capture().no_proxy);
Ok(Configuration {
proxy: Some(proxy),
no_proxy,
})
},
ProxyMode::Automatic => {
let mut env = capture();
if let Some(no_proxy) = &options.no_proxy {
env.no_proxy.clone_from(no_proxy);
env.no_proxy_override = true;
}
resolve_environment(&env, || native(&env))
},
}
}
fn select(
env: &Environment,
native: impl FnOnce() -> Result<Route, Error>,
) -> Result<Route, Error> {
if env.cgi || super::bypasses_all(&env.no_proxy) {
Ok(Route::Direct)
} else if !env.proxy.is_empty() {
Ok(Route::Automatic)
} else {
native()
}
}
fn resolve_environment(
env: &Environment,
native: impl FnOnce() -> Result<Route, Error>,
) -> Result<Configuration, Error> {
let proxy = match select(env, native)? {
Route::Automatic => {
environment_proxy(&env.proxy).or_else(|| environment_proxy(&env.all_proxy))
},
Route::Direct => None,
Route::Native(proxy) => Some(proxy),
};
Ok(Configuration {
proxy,
no_proxy: env.no_proxy.clone(),
})
}
#[cfg(test)]
mod tests;