1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
11
12pub fn is_localhost_name(name: &str) -> bool {
14 let name = name.strip_suffix('.').unwrap_or(name).to_ascii_lowercase();
15 name == "localhost" || name.ends_with(".localhost")
16}
17
18pub async fn lookup_host(name: &str, port: u16) -> std::io::Result<Vec<SocketAddr>> {
22 if is_localhost_name(name) {
23 return Ok([
24 IpAddr::V4(Ipv4Addr::LOCALHOST),
25 IpAddr::V6(Ipv6Addr::LOCALHOST),
26 ]
27 .into_iter()
28 .map(|address| SocketAddr::new(address, port))
29 .collect());
30 }
31 Ok(tokio::net::lookup_host((name, port)).await?.collect())
32}
33
34pub(crate) struct Resolver;
36
37impl reqwest::dns::Resolve for Resolver {
38 fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
39 Box::pin(async move {
40 let addresses = lookup_host(name.as_str(), 0).await?;
41 Ok(Box::new(addresses.into_iter()) as reqwest::dns::Addrs)
42 })
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn localhost_and_every_name_under_it() {
52 for name in [
53 "localhost",
54 "LocalHost",
55 "layout.localhost",
56 "a.b.localhost.",
57 ] {
58 assert!(is_localhost_name(name), "{name}");
59 }
60 for name in ["notlocalhost", "localhost.example", "example.com", "host"] {
61 assert!(!is_localhost_name(name), "{name}");
62 }
63 }
64}