leyline-http 0.1.0

HTTP client that mimics browsers on the wire
Documentation
#![forbid(unsafe_code)]
use tokio::net::TcpStream;

use crate::tls::TlsStream;
use crate::tls::error::TlsError;

pub(crate) mod http;
#[cfg(feature = "socks")]
pub(crate) mod socks5;

const SOCKS5_DEFAULT_PORT: u16 = 1080;

pub(crate) async fn connect_to_proxy<C: crate::tls::TlsHandshake>(
    connector: &C,
    proxy: &url::Url,
) -> Result<TcpStream, TlsError> {
    let host = proxy
        .host_str()
        .ok_or_else(|| TlsError::proxy(format!("{} proxy has no host", proxy.scheme())))?;
    let port = proxy.port_or_known_default().unwrap_or(SOCKS5_DEFAULT_PORT);
    connector
        .dial_tcp(host, port)
        .await
        .map_err(TlsError::into_proxy)
}

pub(crate) async fn connect_through_proxy<C: crate::tls::TlsHandshake>(
    connector: &C,
    host: &str,
    port: u16,
    proxy_url: &str,
    include_alps: bool,
) -> Result<TlsStream, TlsError> {
    let proxy = url::Url::parse(proxy_url)
        .map_err(|e| TlsError::proxy(format!("invalid proxy URL: {e}")))?;

    match proxy.scheme() {
        "socks5" | "socks5h" => {
            #[cfg(feature = "socks")]
            return socks5::connect(connector, host, port, &proxy, include_alps).await;
            #[cfg(not(feature = "socks"))]
            return Err(TlsError::proxy(
                "SOCKS proxy support requires the `socks` feature",
            ));
        }
        "http" => http::connect(connector, host, port, &proxy, include_alps).await,
        "https" => http::connect_via_tls(connector, host, port, &proxy, include_alps).await,
        other => Err(TlsError::proxy(format!(
            "unsupported proxy scheme `{other}`: use an http, https, socks5, or socks5h proxy URL"
        ))),
    }
}

#[cfg(test)]
mod tests;