asimov_http/proxy/
system.rs1use super::{Error, ProxyMode, ProxyOptions, environment_proxy, parse_endpoint};
4use alloc::string::String;
5use core::fmt;
6use url::{Origin, Url};
7
8#[cfg(target_os = "macos")]
9mod macos;
10#[cfg(any(windows, target_os = "linux", test))]
11mod native;
12#[cfg(windows)]
13mod windows;
14
15#[derive(Clone)]
42pub struct OriginProxy {
43 origin: Origin,
44 pub(super) configuration: Configuration,
45}
46
47impl OriginProxy {
48 pub fn resolve(url: &Url, options: &ProxyOptions) -> Result<Self, Error> {
56 if !matches!(url.scheme(), "http" | "https") || url.host_str().is_none() {
57 return Err(Error::InvalidOrigin("expected an HTTP(S) URL with a host"));
58 }
59 let configuration = resolve_options(
60 options,
61 || Environment::capture(url),
62 |env| {
63 #[cfg(target_os = "macos")]
64 return macos::read(url, env);
65 #[cfg(windows)]
66 return windows::read(url, env);
67 #[cfg(target_os = "linux")]
68 return native::read_linux(url, env);
69 #[cfg(not(any(target_os = "macos", windows, target_os = "linux")))]
70 {
71 let _ = env;
72 Ok(Route::Automatic)
73 }
74 },
75 )?;
76 Ok(Self {
77 origin: url.origin(),
78 configuration,
79 })
80 }
81
82 pub fn origin(&self) -> &Origin {
84 &self.origin
85 }
86
87 pub fn proxy_url(&self) -> Option<&Url> {
94 self.configuration.proxy.as_ref()
95 }
96
97 pub fn no_proxy(&self) -> &str {
103 &self.configuration.no_proxy
104 }
105
106 pub fn check_origin(&self, url: &Url) -> Result<(), Error> {
112 if url.origin() != self.origin {
113 return Err(Error::InvalidOrigin(
114 "request must use the configured origin",
115 ));
116 }
117 Ok(())
118 }
119}
120
121impl fmt::Debug for OriginProxy {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.debug_struct("OriginProxy")
124 .field("origin", &self.origin)
125 .finish_non_exhaustive()
126 }
127}
128
129#[derive(Default)]
130struct Environment {
131 proxy: String,
132 all_proxy: String,
133 no_proxy: String,
134 no_proxy_override: bool,
135 cgi: bool,
136}
137
138impl Environment {
139 fn capture(url: &Url) -> Self {
140 fn first(upper: &str, lower: &str) -> String {
141 std::env::var(upper)
142 .or_else(|_| std::env::var(lower))
143 .unwrap_or_default()
144 }
145 Self {
146 proxy: match url.scheme() {
147 "https" => first("HTTPS_PROXY", "https_proxy"),
148 _ => first("HTTP_PROXY", "http_proxy"),
149 },
150 all_proxy: first("ALL_PROXY", "all_proxy"),
151 no_proxy: first("NO_PROXY", "no_proxy"),
152 no_proxy_override: false,
153 cgi: std::env::var_os("REQUEST_METHOD").is_some(),
154 }
155 }
156
157 fn use_native_bypass(&self) -> bool {
158 !self.no_proxy_override && self.no_proxy.is_empty()
159 }
160}
161
162#[derive(Debug, PartialEq)]
163enum Route {
164 Automatic,
165 Direct,
166 Native(Url),
167}
168
169#[derive(Clone)]
170pub(super) struct Configuration {
171 pub(super) proxy: Option<Url>,
172 pub(super) no_proxy: String,
173}
174
175fn resolve_options(
176 options: &ProxyOptions,
177 capture: impl FnOnce() -> Environment,
178 native: impl FnOnce(&Environment) -> Result<Route, Error>,
179) -> Result<Configuration, Error> {
180 match &options.mode {
181 ProxyMode::Direct => Ok(Configuration {
182 proxy: None,
183 no_proxy: String::new(),
184 }),
185 ProxyMode::Explicit(input) => {
186 let proxy = parse_endpoint(input)?;
187 let no_proxy = options
188 .no_proxy
189 .clone()
190 .unwrap_or_else(|| capture().no_proxy);
191 Ok(Configuration {
192 proxy: Some(proxy),
193 no_proxy,
194 })
195 },
196 ProxyMode::Automatic => {
197 let mut env = capture();
198 if let Some(no_proxy) = &options.no_proxy {
199 env.no_proxy.clone_from(no_proxy);
200 env.no_proxy_override = true;
201 }
202 resolve_environment(&env, || native(&env))
203 },
204 }
205}
206
207fn select(
208 env: &Environment,
209 native: impl FnOnce() -> Result<Route, Error>,
210) -> Result<Route, Error> {
211 if env.cgi || super::bypasses_all(&env.no_proxy) {
212 Ok(Route::Direct)
213 } else if !env.proxy.is_empty() {
214 Ok(Route::Automatic)
216 } else {
217 native()
218 }
219}
220
221fn resolve_environment(
222 env: &Environment,
223 native: impl FnOnce() -> Result<Route, Error>,
224) -> Result<Configuration, Error> {
225 let proxy = match select(env, native)? {
226 Route::Automatic => {
227 environment_proxy(&env.proxy).or_else(|| environment_proxy(&env.all_proxy))
228 },
229 Route::Direct => None,
230 Route::Native(proxy) => Some(proxy),
231 };
232 Ok(Configuration {
233 proxy,
234 no_proxy: env.no_proxy.clone(),
235 })
236}
237
238#[cfg(test)]
239mod tests;