what-core 1.7.5

Core framework for What - an HTML-first web framework powered by Rust
Documentation
use std::panic::{self, AssertUnwindSafe};
use std::time::Duration;

fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
    if let Some(msg) = payload.downcast_ref::<&str>() {
        (*msg).to_string()
    } else if let Some(msg) = payload.downcast_ref::<String>() {
        msg.clone()
    } else {
        "unknown panic".to_string()
    }
}

fn build_once(
    timeout: Option<Duration>,
    disable_proxy_autodiscovery: bool,
) -> Result<reqwest::Client, String> {
    let mut builder = reqwest::Client::builder();
    if let Some(timeout) = timeout {
        builder = builder.timeout(timeout);
    }
    if disable_proxy_autodiscovery {
        builder = builder.no_proxy();
    }

    match panic::catch_unwind(AssertUnwindSafe(|| builder.build())) {
        Ok(Ok(client)) => Ok(client),
        Ok(Err(err)) => Err(err.to_string()),
        Err(payload) => Err(panic_message(payload)),
    }
}

/// Build a reqwest client without letting system proxy autodiscovery panic the process.
///
/// The first attempt keeps normal proxy behavior. If that fails or panics, fall back
/// to `no_proxy()` so framework startup remains deterministic across environments.
pub(crate) fn build_http_client(timeout: Option<Duration>) -> Result<reqwest::Client, String> {
    match build_once(timeout, false) {
        Ok(client) => Ok(client),
        Err(primary_err) => {
            tracing::warn!(
                "HTTP client build failed with system proxy autodiscovery: {}. Retrying without proxies.",
                primary_err
            );
            build_once(timeout, true).map_err(|fallback_err| {
                format!(
                    "primary build failed: {}; fallback without proxies failed: {}",
                    primary_err, fallback_err
                )
            })
        }
    }
}

/// Build the SSRF-guarded client used only for `fetch` directives and API
/// datasources (attacker-influenceable URLs). Distinct from the shared client:
/// - `no_proxy()` so a system/corporate proxy can't bypass the local IP checks;
/// - `redirect::Policy::none()` — redirects are followed MANUALLY by the caller
///   so each hop's host is re-resolved and re-validated (a sync redirect policy
///   can't do async DNS). Different trust domain than framework-owned outbound.
pub(crate) fn build_fetch_client(timeout: Option<Duration>) -> Result<reqwest::Client, String> {
    let build = |t: Option<Duration>| -> Result<reqwest::Client, String> {
        let mut builder = reqwest::Client::builder()
            .no_proxy()
            .redirect(reqwest::redirect::Policy::none());
        if let Some(t) = t {
            builder = builder.timeout(t);
        }
        match panic::catch_unwind(AssertUnwindSafe(|| builder.build())) {
            Ok(Ok(client)) => Ok(client),
            Ok(Err(err)) => Err(err.to_string()),
            Err(payload) => Err(panic_message(payload)),
        }
    };
    build(timeout)
}