leyline-http 0.1.1

HTTP client that mimics browsers on the wire
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,
}

pub(super) fn apply_env_proxy(
    config: ProxyConfig,
    on_invalid: InvalidEnvProxy,
) -> Result<ProxyConfig> {
    apply_env_proxy_from(
        config,
        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>(
    config: ProxyConfig,
    on_invalid: InvalidEnvProxy,
    get_var: F,
    has_var: G,
) -> Result<ProxyConfig>
where
    F: Fn(&str) -> Option<String>,
    G: Fn(&str) -> bool,
{
    if !config.rules().is_empty() || !config.uses_env() {
        return Ok(config);
    }
    let Some(EnvProxy { variable, value }) = env_proxy_from(get_var, has_var) else {
        return Ok(config);
    };
    if ProxyUrl::parse(&value).is_ok() {
        return Ok(config.set_default_proxy(value).set_from_env());
    }
    let config = config.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
}