use std::future::Future;
use std::io;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
use std::task::Poll;
use std::task::{self};
use std::vec;
use deno_permissions::PermissionsContainer;
use hickory_resolver::name_server::TokioConnectionProvider;
use http::Uri;
use http::uri::Scheme;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::connect::dns::GaiResolver;
use hyper_util::client::legacy::connect::dns::Name;
use hyper_util::rt::TokioIo;
use tokio::net::TcpStream;
use tokio::task::JoinHandle;
use tower::Service;
#[derive(Clone, Debug)]
pub struct Resolver {
kind: ResolverKind,
}
#[allow(clippy::large_enum_variant, reason = "TODO: investigate")]
#[derive(Clone, Debug)]
enum ResolverKind {
Gai(GaiResolver),
Hickory(hickory_resolver::Resolver<TokioConnectionProvider>),
Custom(Arc<dyn Resolve>),
}
pub type Resolving =
Pin<Box<dyn Future<Output = Result<SocketAddrs, io::Error>> + Send>>;
pub trait Resolve: Send + Sync + std::fmt::Debug {
fn resolve(&self, name: Name) -> Resolving;
}
impl Default for Resolver {
fn default() -> Self {
Self::gai()
}
}
impl Resolver {
pub fn gai() -> Self {
Self {
kind: ResolverKind::Gai(GaiResolver::new()),
}
}
pub fn hickory() -> Result<Self, hickory_resolver::ResolveError> {
Ok(Self {
kind: ResolverKind::Hickory(
hickory_resolver::Resolver::builder_tokio()?.build(),
),
})
}
pub fn hickory_from_resolver(
resolver: hickory_resolver::Resolver<TokioConnectionProvider>,
) -> Self {
Self {
kind: ResolverKind::Hickory(resolver),
}
}
pub fn custom(resolver: Arc<dyn Resolve>) -> Self {
Self {
kind: ResolverKind::Custom(resolver),
}
}
}
type SocketAddrs = vec::IntoIter<SocketAddr>;
pub struct ResolveFut {
inner: JoinHandle<Result<SocketAddrs, io::Error>>,
}
impl Future for ResolveFut {
type Output = Result<SocketAddrs, io::Error>;
fn poll(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|res| match res {
Ok(Ok(addrs)) => Ok(addrs),
Ok(Err(e)) => Err(e),
Err(join_err) => {
if join_err.is_cancelled() {
Err(io::Error::new(io::ErrorKind::Interrupted, join_err))
} else {
Err(io::Error::other(join_err))
}
}
})
}
}
impl Service<Name> for Resolver {
type Response = SocketAddrs;
type Error = io::Error;
type Future = ResolveFut;
fn poll_ready(
&mut self,
_cx: &mut task::Context<'_>,
) -> Poll<Result<(), io::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, name: Name) -> Self::Future {
let task = match &mut self.kind {
ResolverKind::Gai(gai_resolver) => {
let mut resolver = gai_resolver.clone();
tokio::spawn(async move {
let result = resolver.call(name).await?;
Ok(result.collect::<Vec<_>>().into_iter())
})
}
ResolverKind::Hickory(async_resolver) => {
let resolver = async_resolver.clone();
tokio::spawn(async move {
let result = resolver.lookup_ip(name.as_str()).await?;
let addrs: Vec<_> =
result.into_iter().map(|x| SocketAddr::new(x, 0)).collect();
Ok(addrs.into_iter())
})
}
ResolverKind::Custom(resolver) => {
let resolver = resolver.clone();
tokio::spawn(async move { resolver.resolve(name).await })
}
};
ResolveFut { inner: task }
}
}
#[derive(Clone, Debug)]
pub struct PermissionedHttpConnector {
resolver: Resolver,
local_address: Option<IpAddr>,
permissions: Option<PermissionsContainer>,
}
impl PermissionedHttpConnector {
pub fn new(
resolver: Resolver,
local_address: Option<IpAddr>,
permissions: Option<PermissionsContainer>,
) -> Self {
Self {
resolver,
local_address,
permissions,
}
}
fn http_connector(&self, resolver: Resolver) -> HttpConnector<Resolver> {
let mut connector = HttpConnector::new_with_resolver(resolver);
connector.enforce_http(false);
connector.set_local_address(self.local_address);
connector
}
}
#[derive(Debug)]
struct PreResolved(Vec<SocketAddr>);
impl Resolve for PreResolved {
fn resolve(&self, _name: Name) -> Resolving {
let addrs = self.0.clone();
Box::pin(async move { Ok(addrs.into_iter()) })
}
}
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug)]
struct DnsError(io::Error);
impl std::fmt::Display for DnsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("dns error")
}
}
impl std::error::Error for DnsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
fn check_resolved(
permissions: &PermissionsContainer,
ip: &IpAddr,
port: u16,
) -> Result<(), BoxError> {
permissions
.clone()
.check_net_resolved(ip, port, "fetch()")
.map_err(|e| {
io::Error::new(io::ErrorKind::PermissionDenied, e.to_string()).into()
})
}
fn bare_host_and_port(uri: &Uri) -> Option<(&str, u16)> {
let host = uri.host()?;
let port = uri.port_u16().unwrap_or_else(|| {
if uri.scheme() == Some(&Scheme::HTTPS) {
443
} else {
80
}
});
let bare_host = host
.strip_prefix('[')
.and_then(|h| h.strip_suffix(']'))
.unwrap_or(host);
Some((bare_host, port))
}
impl Service<Uri> for PermissionedHttpConnector {
type Response = TokioIo<TcpStream>;
type Error = BoxError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(
&mut self,
_cx: &mut task::Context<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, uri: Uri) -> Self::Future {
let this = self.clone();
Box::pin(async move {
let Some(permissions) = &this.permissions else {
let mut connector = this.http_connector(this.resolver.clone());
return connector.call(uri).await.map_err(Into::into);
};
let Some((bare_host, port)) = bare_host_and_port(&uri) else {
return Err(
io::Error::new(io::ErrorKind::InvalidInput, "missing host in URI")
.into(),
);
};
if let Ok(ip) = bare_host.parse::<IpAddr>() {
check_resolved(permissions, &ip, port)?;
let mut connector = this.http_connector(this.resolver.clone());
return connector.call(uri).await.map_err(Into::into);
}
let name = Name::from_str(bare_host).map_err(|e| -> BoxError {
io::Error::new(io::ErrorKind::InvalidInput, e.to_string()).into()
})?;
let addrs: Vec<SocketAddr> = this
.resolver
.clone()
.call(name)
.await
.map_err(|e| -> BoxError { DnsError(e).into() })?
.collect();
for addr in &addrs {
check_resolved(permissions, &addr.ip(), port)?;
}
let mut connector =
this.http_connector(Resolver::custom(Arc::new(PreResolved(addrs))));
connector.call(uri).await.map_err(Into::into)
})
}
}
pub trait CheckDst {
fn check_dst(
&self,
uri: Uri,
) -> Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send>>;
}
impl CheckDst for PermissionedHttpConnector {
fn check_dst(
&self,
uri: Uri,
) -> Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send>> {
let this = self.clone();
Box::pin(async move {
let Some(permissions) = &this.permissions else {
return Ok(());
};
let Some((bare_host, port)) = bare_host_and_port(&uri) else {
return Ok(());
};
if let Ok(ip) = bare_host.parse::<IpAddr>() {
return check_resolved(permissions, &ip, port);
}
let Ok(name) = Name::from_str(bare_host) else {
return Ok(());
};
let Ok(addrs) = this.resolver.clone().call(name).await else {
return Ok(());
};
for addr in addrs {
check_resolved(permissions, &addr.ip(), port)?;
}
Ok(())
})
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[derive(Debug)]
struct DebugResolver(SocketAddr);
impl Resolve for DebugResolver {
fn resolve(&self, _name: Name) -> Resolving {
let addr = self.0;
Box::pin(async move { Ok(vec![addr].into_iter()) })
}
}
#[tokio::test]
async fn custom_dns_resolver() {
let mut resolver = Resolver::custom(Arc::new(DebugResolver(
"127.0.0.1:8080".parse().unwrap(),
)));
let mut addr = resolver
.call(Name::from_str("foo.com").unwrap())
.await
.unwrap();
let addr = addr.next().unwrap();
assert_eq!(addr, "127.0.0.1:8080".parse().unwrap());
}
}