car-inference 0.61.2

Local model inference for CAR — Candle backend with Qwen3 models
Documentation
//! A redirect policy that keeps a request on the origin it was sent to.
//!
//! reqwest's default follows up to 10 redirects to any destination, and a
//! 307 or 308 re-sends the method and the body. It strips `Authorization` on
//! a cross-host hop but keeps every other header. So a request whose
//! destination was approved by a person, or which carries a credential in a
//! custom header, must not leave the origin it was sent to. One policy, so
//! `http_request`, the remote MCP connectors and the ChatGPT-subscription
//! client all judge an origin the same way.

use reqwest::Url;

/// What [`same_origin`] does with a hop to another origin.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrossOrigin {
    /// Hand the 3xx back to the caller as the response, so the caller can
    /// report where it pointed.
    Stop,
    /// Fail the request with an error naming both origins.
    Fail,
    /// Hand back the 3xx of a hop that only upgrades the first request from
    /// `http` to `https` on the same host (see [`is_https_upgrade`]), and
    /// fail on any other origin as [`Fail`](Self::Fail) does. The upgrade
    /// reaches the server the caller named over a channel at least as
    /// private, so the caller may re-send the request there itself; reqwest
    /// cannot follow it for the caller, because it strips `Authorization`
    /// on a port change, and the usual upgrade is 80 to 443. `https` to
    /// `http` is not an upgrade and fails.
    StopAtHttpsUpgrade,
}

/// Follow redirects that stay on the origin (scheme, host and port) of the
/// first request, up to 10 hops, and stop or fail at the first hop that
/// leaves it, as `cross_origin` says. The origin is the first request's,
/// never the previous hop's, so a chain of same-origin hops cannot walk off
/// it.
pub fn same_origin(cross_origin: CrossOrigin) -> reqwest::redirect::Policy {
    reqwest::redirect::Policy::custom(move |attempt| {
        let first = attempt.previous().first().unwrap_or(attempt.url());
        let origin = first.origin();
        if attempt.url().origin() != origin {
            match cross_origin {
                CrossOrigin::Stop => attempt.stop(),
                CrossOrigin::StopAtHttpsUpgrade if is_https_upgrade(first, attempt.url()) => {
                    attempt.stop()
                }
                CrossOrigin::Fail | CrossOrigin::StopAtHttpsUpgrade => {
                    let message = format!(
                        "refusing a redirect from {} to another origin {}",
                        origin.ascii_serialization(),
                        attempt.url().origin().ascii_serialization()
                    );
                    attempt.error(message)
                }
            }
        } else if attempt.previous().len() > 10 {
            attempt.error("too many redirects")
        } else {
            attempt.follow()
        }
    })
}

/// Whether `to` is `from` moved from `http` to `https` on the same host, on
/// the same port or from the default 80 to the default 443.
pub fn is_https_upgrade(from: &Url, to: &Url) -> bool {
    from.scheme() == "http"
        && to.scheme() == "https"
        && from.host() == to.host()
        && match (from.port_or_known_default(), to.port_or_known_default()) {
            (Some(80), Some(443)) => true,
            (Some(from_port), Some(to_port)) => from_port == to_port,
            _ => false,
        }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn url(text: &str) -> Url {
        Url::parse(text).unwrap()
    }

    /// The ports a local server cannot bind in a test: the default 80 maps
    /// to 443, and any other port change is not an upgrade.
    #[test]
    fn an_https_upgrade_keeps_the_host_and_the_port_or_maps_80_to_443() {
        for (from, to) in [
            ("http://example.com/mcp", "https://example.com/mcp"),
            ("http://example.com:80/a", "https://example.com:443/b"),
            ("http://example.com:8080/", "https://example.com:8080/"),
        ] {
            assert!(is_https_upgrade(&url(from), &url(to)), "{from} -> {to}");
        }
        for (from, to) in [
            ("https://example.com/", "http://example.com/"),
            ("http://example.com/", "https://other.example.com/"),
            ("http://example.com/", "https://example.com:8443/"),
            ("http://example.com:8080/", "https://example.com/"),
            ("http://example.com/", "http://example.com:8080/"),
        ] {
            assert!(!is_https_upgrade(&url(from), &url(to)), "{from} -> {to}");
        }
    }
}