From 43437abae73898da7971d168e8307cdb0fda9661 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Fri, 24 Oct 2025 08:01:12 -0700 Subject: [PATCH] Add custom DNS resolver for *.localhost (#280) --- src-tauri/Cargo.lock | 10 ++++--- src-tauri/Cargo.toml | 2 ++ src-tauri/src/dns.rs | 54 +++++++++++++++++++++++++++++++++++ src-tauri/src/http_request.rs | 2 ++ src-tauri/src/lib.rs | 1 + 5 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 src-tauri/src/dns.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1339cfdd..35246708 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1259,7 +1259,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2352,9 +2352,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -2368,7 +2368,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.1", "system-configuration", "tokio", "tower-service", @@ -7816,6 +7816,7 @@ dependencies = [ "cookie", "eventsource-client", "http", + "hyper-util", "log", "md5 0.8.0", "mime_guess", @@ -7841,6 +7842,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-stream", + "tower-service", "ts-rs", "uuid", "yaak-common", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 58a275e4..0c3b9086 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -68,6 +68,8 @@ tauri-plugin-shell = { workspace = true } tauri-plugin-single-instance = { version = "2.3.4", features = ["deep-link"] } tauri-plugin-updater = "2.9.0" tauri-plugin-window-state = "2.4.0" +hyper-util = { version = "0.1.17", default-features = false, features = ["client-legacy"] } +tower-service = "0.3.3" thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"] } tokio-stream = "0.1.17" diff --git a/src-tauri/src/dns.rs b/src-tauri/src/dns.rs new file mode 100644 index 00000000..ef65d058 --- /dev/null +++ b/src-tauri/src/dns.rs @@ -0,0 +1,54 @@ +use hyper_util::client::legacy::connect::dns::{ + GaiResolver as HyperGaiResolver, Name as HyperName, +}; +use reqwest::dns::{Addrs, Name, Resolve, Resolving}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::str::FromStr; +use std::sync::Arc; +use tower_service::Service; + +#[derive(Clone)] +pub(crate) struct LocalhostResolver { + fallback: HyperGaiResolver, +} + +impl LocalhostResolver { + pub fn new() -> Arc { + let resolver = HyperGaiResolver::new(); + Arc::new(Self { fallback: resolver }) + } +} + +impl Resolve for LocalhostResolver { + fn resolve(&self, name: Name) -> Resolving { + let host = name.as_str().to_lowercase(); + + let is_localhost = host.ends_with(".localhost"); + if is_localhost { + // Port 0 is fine; reqwest replaces it with the URL's explicit + // port or the scheme’s default (80/443, etc.). + // (See docs note below.) + let addrs: Vec = vec![ + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), + SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0), + ]; + + return Box::pin(async move { + Ok::>(Box::new(addrs.into_iter())) + }); + } + + let mut fallback = self.fallback.clone(); + let name_str = name.as_str().to_string(); + Box::pin(async move { + match HyperName::from_str(&name_str) { + Ok(n) => fallback + .call(n) + .await + .map(|addrs| Box::new(addrs) as Addrs) + .map_err(|err| Box::new(err) as Box), + Err(e) => Err(Box::new(e) as Box), + } + }) + } +} diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 959596e0..35b23567 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -33,6 +33,7 @@ use yaak_plugins::events::{ use yaak_plugins::manager::PluginManager; use yaak_plugins::template_callback::PluginTemplateCallback; use yaak_templates::{RenderErrorBehavior, RenderOptions}; +use crate::dns::LocalhostResolver; pub async fn send_http_request( window: &WebviewWindow, @@ -110,6 +111,7 @@ pub async fn send_http_request( .gzip(true) .brotli(true) .deflate(true) + .dns_resolver(LocalhostResolver::new()) .referer(false) .tls_info(true); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f422d2bb..8d1e67d2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -60,6 +60,7 @@ mod updates; mod uri_scheme; mod window; mod window_menu; +mod dns; #[derive(serde::Serialize)] #[serde(default, rename_all = "camelCase")]