use pyo3::prelude::*;
#[derive(PartialEq, Eq)]
pub(crate) enum ProxyOverride {
Inherit,
Disable,
Override(String),
}
pub(crate) fn env_proxy_urls(py: Python<'_>) -> PyResult<Vec<(&'static str, String)>> {
let urllib = py.import("urllib.request")?;
let values = urllib.call_method0("getproxies")?;
let value = |name: &str| -> PyResult<Option<String>> {
let value = values.call_method1("get", (name,))?;
if value.is_none() {
return Ok(None);
}
let value: String = value.extract()?;
Ok((!value.is_empty()).then_some(value))
};
let mut proxies = Vec::new();
if let Some(url) = value("http")? {
proxies.push(("http", normalize_environment_proxy_url(&url)));
}
if let Some(url) = value("https")? {
proxies.push(("https", normalize_environment_proxy_url(&url)));
}
if let Some(url) = value("all")? {
proxies.push(("all", normalize_environment_proxy_url(&url)));
}
Ok(proxies)
}
pub(crate) fn env_no_proxy(py: Python<'_>) -> PyResult<Option<String>> {
let urllib = py.import("urllib.request")?;
let values = urllib.call_method0("getproxies")?;
let value = values.call_method1("get", ("no",))?;
if value.is_none() {
return Ok(None);
}
let value: String = value.extract()?;
Ok((!value.is_empty()).then_some(value))
}
pub(crate) fn normalize_environment_proxy_url(url: &str) -> String {
if url.contains("://") {
url.to_owned()
} else {
format!("http://{url}")
}
}
pub(crate) fn normalize_compat_proxy_url(url: &str) -> String {
let Ok(mut parsed) = url::Url::parse(url) else {
return url.to_owned();
};
if parsed.scheme() == "socks5" {
let _ = parsed.set_scheme("socks5h");
parsed.to_string()
} else {
url.to_owned()
}
}
pub fn parse_proxy(proxy: Option<&Bound<'_, PyAny>>) -> PyResult<ProxyOverride> {
match proxy {
None => Ok(ProxyOverride::Inherit),
Some(val) => {
if val.is_none() {
return Ok(ProxyOverride::Inherit);
}
if val.is_instance_of::<pyo3::types::PyBool>() {
let b: bool = val.extract()?;
if b {
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"proxy must be a URL string, False, or None; True is not valid",
));
}
return Ok(ProxyOverride::Disable);
}
if let Ok(url) = val.extract::<String>() {
return Ok(ProxyOverride::Override(normalize_compat_proxy_url(&url)));
}
if let Ok(url_obj) = val.getattr("url") {
if let Ok(url_string) = url_obj.str() {
let mut url = url_string.to_string_lossy().to_string();
if let Ok(raw_auth) = val.getattr("raw_auth") {
if !raw_auth.is_none() {
if let Ok(auth_tuple) = raw_auth.downcast::<pyo3::types::PyTuple>() {
if auth_tuple.len() == 2 {
if let (Ok(username), Ok(password)) = (
auth_tuple.get_item(0)?.extract::<String>(),
auth_tuple.get_item(1)?.extract::<String>(),
) {
if let Ok(mut parsed) = url::Url::parse(&url) {
let _ = parsed.set_username(&username);
let _ = parsed.set_password(Some(&password));
url = parsed.to_string();
}
}
}
}
}
}
return Ok(ProxyOverride::Override(normalize_compat_proxy_url(&url)));
}
}
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"proxy must be a URL string, False, or None",
))
}
}
}
pub(crate) fn extract_proxy_extras(
py: Python<'_>,
proxy: Option<&Bound<'_, PyAny>>,
) -> PyResult<(
Option<eggfetch_core::Headers>,
Option<eggfetch_core::TlsConfig>,
)> {
let Some(proxy_obj) = proxy else {
return Ok((None, None));
};
let Ok(proxy_module) = py.import("eggfetch.compat.httpx._proxy") else {
return Ok((None, None));
};
let Ok(proxy_class) = proxy_module.getattr("Proxy") else {
return Ok((None, None));
};
if !proxy_obj.is_instance(&proxy_class).unwrap_or(false) {
return Ok((None, None));
}
let headers = if proxy_obj.hasattr("headers")? {
let h = proxy_obj.getattr("headers")?;
Some(crate::conversion::python_headers_to_rust(py, &h)?)
} else {
None
};
let ssl_ctx = proxy_obj.getattr("ssl_context").ok();
let tls = crate::tls::ssl_context_to_tls_config(py, ssl_ctx.as_ref())?;
Ok((headers, tls))
}