leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use crate::core::{Kind, ProxyConfig, ProxyUrl, Result};

pub(crate) const CGI_SIGNAL_ENV_VARS: &[&str] = &[
    "GATEWAY_INTERFACE",
    "REQUEST_METHOD",
    "SERVER_SOFTWARE",
    "SCRIPT_NAME",
    "SCRIPT_FILENAME",
    "PATH_INFO",
    "QUERY_STRING",
    "SERVER_PROTOCOL",
    "SERVER_NAME",
    "SERVER_PORT",
];

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum InvalidEnvProxy {
    FailBuild,
    FailRequests,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct EnvProxy {
    pub(crate) variable: &'static str,
    pub(crate) value: String,
}

impl ProxyConfig {
    pub(super) fn apply_env_proxy(self, on_invalid: InvalidEnvProxy) -> Result<ProxyConfig> {
        self.apply_env_proxy_from(
            on_invalid,
            |k| std::env::var(k).ok(),
            |k| std::env::var_os(k).is_some(),
        )
    }

    pub(super) fn apply_env_proxy_from<F, G>(
        self,
        on_invalid: InvalidEnvProxy,
        get_var: F,
        has_var: G,
    ) -> Result<ProxyConfig>
    where
        F: Fn(&str) -> Option<String>,
        G: Fn(&str) -> bool,
    {
        if !self.rules().is_empty() || !self.uses_env() {
            return Ok(self);
        }
        let Some(EnvProxy { variable, value }) = env_proxy_from(get_var, has_var) else {
            return Ok(self);
        };
        if ProxyUrl::parse(&value).is_ok() {
            return Ok(self.set_default_proxy(value).set_from_env());
        }
        let config = self.reject_env(variable);
        match (on_invalid, config.rejection(Kind::Config)) {
            (InvalidEnvProxy::FailBuild, Some(error)) => Err(error),
            _ => Ok(config),
        }
    }
}

pub(crate) fn env_proxy_from<F, G>(get_var: F, has_var: G) -> Option<EnvProxy>
where
    F: Fn(&str) -> Option<String>,
    G: Fn(&str) -> bool,
{
    let in_cgi = CGI_SIGNAL_ENV_VARS.iter().any(|k| has_var(k));

    if in_cgi && has_var("HTTP_PROXY") {
        tracing::warn!(
            target: "leyline::env_proxy::cgi",
            "CGI environment detected — ignoring HTTP_PROXY (httpoxy mitigation)"
        );
    }

    let candidates: &[&'static str] = if in_cgi {
        &[
            "HTTPS_PROXY",
            "https_proxy",
            "http_proxy",
            "ALL_PROXY",
            "all_proxy",
        ]
    } else {
        &[
            "HTTPS_PROXY",
            "https_proxy",
            "HTTP_PROXY",
            "http_proxy",
            "ALL_PROXY",
            "all_proxy",
        ]
    };

    for &variable in candidates {
        if let Some(val) = get_var(variable) {
            let trimmed = val.trim();
            if !trimmed.is_empty() {
                return Some(EnvProxy {
                    variable,
                    value: trimmed.to_string(),
                });
            }
        }
    }
    None
}