AirLibrary/HTTP/
client.rs
1//! HTTP Client Module with DNS Override
2//!
3//! This module provides a secured HTTP client that uses the local DNS server
4//! for all DNS resolution. This ensures that all `*.editor.land` queries go
5//! through the local Hickory DNS server, which resolves them to `127.x.x.x`
6//! addresses as a defense-in-depth measure.
7
8use std::sync::Arc;
9use std::time::Duration;
10
11use anyhow::Result;
12// Re-export types from Mist workspace dependency
13pub use mist::Resolver::LandDnsResolver;
14#[allow(unused_imports)]
15pub use mist::Resolver::TokioResolver;
16#[allow(unused_imports)]
17pub use mist::Resolver::LandResolver;
18
19/// Creates a secured reqwest ClientBuilder with DNS override configured.
20///
21/// This returns a `ClientBuilder` with the DNS resolver already set, allowing
22/// you to add additional configurations before calling `.build()`.
23///
24/// # Parameters
25///
26/// * `dns_port` - The port of the local DNS server (obtained from
27/// `mist::dns_port()`)
28///
29/// # Returns
30///
31/// Returns a configured `reqwest::ClientBuilder` with the local DNS resolver.
32///
33/// # Example
34///
35/// ```rust,no_run
36/// use std::time::Duration;
37///
38/// use AirLibrary::HTTP::secured_client_builder;
39/// use Mist;
40///
41/// #[tokio::main]
42/// async fn main() -> anyhow::Result<()> {
43/// let dns_port = mist::dns_port();
44/// let client = secured_client_builder(dns_port)?.timeout(Duration::from_secs(30)).build()?;
45///
46/// // All HTTP requests will use the local DNS server
47/// Ok(())
48/// }
49/// ```
50pub fn secured_client_builder(dns_port:u16) -> Result<reqwest::ClientBuilder> {
51 let resolver = Arc::new(LandDnsResolver::new(dns_port));
52
53 Ok(reqwest::Client::builder().dns_resolver(resolver))
54}
55
56/// Creates a secured reqwest Client with DNS override.
57///
58/// This client uses the local DNS server (running on the specified port)
59/// for all DNS resolution. This is a security measure to ensure that all
60/// `*.editor.land` queries go through the local Hickory DNS server, which
61/// validates that they only resolve to `127.x.x.x` addresses.
62///
63/// # Parameters
64///
65/// * `dns_port` - The port of the local DNS server (obtained from
66/// `mist::dns_port()`)
67///
68/// # Returns
69///
70/// Returns a configured `reqwest::Client` that uses the local DNS resolver.
71///
72/// # Example
73///
74/// ```rust,no_run
75/// use AirLibrary::HTTP::secured_client;
76/// use Mist;
77///
78/// #[tokio::main]
79/// async fn main() -> anyhow::Result<()> {
80/// let dns_port = mist::dns_port();
81/// let client = secured_client(dns_port)?;
82///
83/// // All HTTP requests will use the local DNS server
84/// let response = client.get("https://code.editor.land").send().await?;
85/// Ok(())
86/// }
87/// ```
88///
89/// # Security
90///
91/// The DNS override ensures:
92/// - All DNS queries go through the local DNS server
93/// - `*.editor.land` domains resolve only to `127.x.x.x` addresses
94/// - Protection against DNS spoofing and cache poisoning
95/// - Defense-in-depth security for the local network
96pub fn secured_client(dns_port:u16) -> Result<reqwest::Client> {
97 secured_client_builder(dns_port)?
98 .build()
99 .map_err(|e| anyhow::anyhow!("Failed to build reqwest client: {}", e))
100}
101
102/// Creates a secured reqwest Client with timeout and DNS override.
103///
104/// This client uses the local DNS server for all DNS resolution and
105/// has a default timeout configured.
106///
107/// # Parameters
108///
109/// * `dns_port` - The port of the local DNS server (obtained from `mist::dns_port()`)
110/// * `timeout` - The timeout duration for requests
111///
112/// # Returns
113///
114/// Returns a configured `reqwest::Client` with DNS override and timeout.
115///
116/// # Example
117///
118/// ```rust,no_run
119/// use std::time::Duration;
120///
121/// use AirLibrary::HTTP::secured_client_with_timeout;
122/// use Mist;
123///
124/// #[tokio::main]
125/// async fn main() -> anyhow::Result<()> {
126/// let dns_port = mist::dns_port();
127/// let client = secured_client_with_timeout(dns_port, Duration::from_secs(30))?;
128///
129/// // All HTTP requests will use the local DNS server with 30s timeout
130/// Ok(())
131/// }
132/// ```
133pub fn secured_client_with_timeout(dns_port:u16, timeout:Duration) -> Result<reqwest::Client> {
134 secured_client_builder(dns_port)?
135 .timeout(timeout)
136 .build()
137 .map_err(|e| anyhow::anyhow!("Failed to build reqwest client: {}", e))
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_secured_client_creation() {
146 let port = 15353;
147 let result = secured_client(port);
148 // Should succeed even if DNS server isn't running (client creation doesn't
149 // require DNS)
150 assert!(result.is_ok(), "Client creation should succeed");
151 }
152
153 #[test]
154 fn test_secured_client_builder_creation() {
155 let port = 15354;
156 let result = secured_client_builder(port);
157 assert!(result.is_ok(), "ClientBuilder creation should succeed");
158 }
159}