use anyctx::AnyCtx;
use bytes::Bytes;
use http_body_util::Full;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use std::convert::Infallible;
use crate::Config;
pub async fn pac_serve(ctx: &AnyCtx<Config>) -> anyhow::Result<()> {
let listener = tokio::net::TcpListener::bind(if let Some(listen) = ctx.init().pac_listen {
listen
} else {
std::future::pending().await
})
.await?;
let ctx = ctx.clone();
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let ctx = ctx.clone();
tokio::task::spawn(async move {
let service = service_fn(move |req| serve_pac(req, ctx.clone()));
if let Err(err) = hyper::server::conn::http1::Builder::new()
.serve_connection(io, service)
.await
{
eprintln!("Error serving connection: {:?}", err);
}
});
}
}
async fn serve_pac(
_req: Request<hyper::body::Incoming>,
ctx: AnyCtx<Config>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let to_loopback = |mut addr: std::net::SocketAddr| {
if addr.ip().is_unspecified() {
addr.set_ip(std::net::Ipv4Addr::LOCALHOST.into());
}
addr
};
let init = ctx.init();
let directive = if let Some(http) = init.http_proxy_listen {
format!("PROXY {}", to_loopback(http))
} else if let Some(socks) = init.socks5_listen {
let socks = to_loopback(socks);
format!("SOCKS5 {socks}; SOCKS {socks}")
} else {
"DIRECT".to_string()
};
Ok(Response::new(Full::new(Bytes::from(format!(
"function FindProxyForURL(url, host){{return '{directive}';}}"
)))))
}