Skip to content

Commit 7d31dc5

Browse files
sfacklercpu
andauthored
Support custom server name resolution (#269)
Co-authored-by: Daniel McCarney <[email protected]>
1 parent 16d7e59 commit 7d31dc5

File tree

3 files changed

+117
-29
lines changed

3 files changed

+117
-29
lines changed

src/connector.rs

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub struct HttpsConnector<T> {
2424
force_https: bool,
2525
http: T,
2626
tls_config: Arc<rustls::ClientConfig>,
27-
override_server_name: Option<String>,
27+
server_name_resolver: Arc<dyn ResolveServerName + Sync + Send>,
2828
}
2929

3030
impl<T> HttpsConnector<T> {
@@ -90,24 +90,10 @@ where
9090
};
9191

9292
let cfg = self.tls_config.clone();
93-
let mut hostname = match self.override_server_name.as_deref() {
94-
Some(h) => h,
95-
None => dst.host().unwrap_or_default(),
96-
};
97-
98-
// Remove square brackets around IPv6 address.
99-
if let Some(trimmed) = hostname
100-
.strip_prefix('[')
101-
.and_then(|h| h.strip_suffix(']'))
102-
{
103-
hostname = trimmed;
104-
}
105-
106-
let hostname = match ServerName::try_from(hostname) {
107-
Ok(dns_name) => dns_name.to_owned(),
108-
Err(_) => {
109-
let err = io::Error::new(io::ErrorKind::Other, "invalid dnsname");
110-
return Box::pin(async move { Err(Box::new(err).into()) });
93+
let hostname = match self.server_name_resolver.resolve(&dst) {
94+
Ok(hostname) => hostname,
95+
Err(e) => {
96+
return Box::pin(async move { Err(e) });
11197
}
11298
};
11399

@@ -135,7 +121,7 @@ where
135121
force_https: false,
136122
http,
137123
tls_config: cfg.into(),
138-
override_server_name: None,
124+
server_name_resolver: Arc::new(DefaultServerNameResolver::default()),
139125
}
140126
}
141127
}
@@ -147,3 +133,69 @@ impl<T> fmt::Debug for HttpsConnector<T> {
147133
.finish()
148134
}
149135
}
136+
137+
/// The default server name resolver, which uses the hostname in the URI.
138+
#[derive(Default)]
139+
pub struct DefaultServerNameResolver(());
140+
141+
impl ResolveServerName for DefaultServerNameResolver {
142+
fn resolve(
143+
&self,
144+
uri: &Uri,
145+
) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>> {
146+
let mut hostname = uri.host().unwrap_or_default();
147+
148+
// Remove square brackets around IPv6 address.
149+
if let Some(trimmed) = hostname
150+
.strip_prefix('[')
151+
.and_then(|h| h.strip_suffix(']'))
152+
{
153+
hostname = trimmed;
154+
}
155+
156+
ServerName::try_from(hostname.to_string()).map_err(|e| Box::new(e) as _)
157+
}
158+
}
159+
160+
/// A server name resolver which always returns the same fixed name.
161+
pub struct FixedServerNameResolver {
162+
name: ServerName<'static>,
163+
}
164+
165+
impl FixedServerNameResolver {
166+
/// Creates a new resolver returning the specified name.
167+
pub fn new(name: ServerName<'static>) -> Self {
168+
Self { name }
169+
}
170+
}
171+
172+
impl ResolveServerName for FixedServerNameResolver {
173+
fn resolve(
174+
&self,
175+
_: &Uri,
176+
) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>> {
177+
Ok(self.name.clone())
178+
}
179+
}
180+
181+
impl<F, E> ResolveServerName for F
182+
where
183+
F: Fn(&Uri) -> Result<ServerName<'static>, E>,
184+
E: Into<Box<dyn std::error::Error + Sync + Send>>,
185+
{
186+
fn resolve(
187+
&self,
188+
uri: &Uri,
189+
) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>> {
190+
self(uri).map_err(Into::into)
191+
}
192+
}
193+
194+
/// A trait implemented by types that can resolve a [`ServerName`] for a request.
195+
pub trait ResolveServerName {
196+
/// Maps a [`Uri`] into a [`ServerName`].
197+
fn resolve(
198+
&self,
199+
uri: &Uri,
200+
) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>>;
201+
}

src/connector/builder.rs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
use std::sync::Arc;
2+
13
use hyper_util::client::legacy::connect::HttpConnector;
24
#[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))]
35
use rustls::crypto::CryptoProvider;
46
use rustls::ClientConfig;
57

6-
use super::HttpsConnector;
8+
use super::{DefaultServerNameResolver, HttpsConnector, ResolveServerName};
79
#[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))]
810
use crate::config::ConfigBuilderExt;
11+
use pki_types::ServerName;
912

1013
/// A builder for an [`HttpsConnector`]
1114
///
@@ -153,7 +156,7 @@ impl ConnectorBuilder<WantsSchemes> {
153156
ConnectorBuilder(WantsProtocols1 {
154157
tls_config: self.0.tls_config,
155158
https_only: true,
156-
override_server_name: None,
159+
server_name_resolver: None,
157160
})
158161
}
159162

@@ -165,7 +168,7 @@ impl ConnectorBuilder<WantsSchemes> {
165168
ConnectorBuilder(WantsProtocols1 {
166169
tls_config: self.0.tls_config,
167170
https_only: false,
168-
override_server_name: None,
171+
server_name_resolver: None,
169172
})
170173
}
171174
}
@@ -177,7 +180,7 @@ impl ConnectorBuilder<WantsSchemes> {
177180
pub struct WantsProtocols1 {
178181
tls_config: ClientConfig,
179182
https_only: bool,
180-
override_server_name: Option<String>,
183+
server_name_resolver: Option<Arc<dyn ResolveServerName + Sync + Send>>,
181184
}
182185

183186
impl WantsProtocols1 {
@@ -186,7 +189,9 @@ impl WantsProtocols1 {
186189
force_https: self.https_only,
187190
http: conn,
188191
tls_config: std::sync::Arc::new(self.tls_config),
189-
override_server_name: self.override_server_name,
192+
server_name_resolver: self
193+
.server_name_resolver
194+
.unwrap_or_else(|| Arc::new(DefaultServerNameResolver::default())),
190195
}
191196
}
192197

@@ -237,6 +242,22 @@ impl ConnectorBuilder<WantsProtocols1> {
237242
})
238243
}
239244

245+
/// Override server name for the TLS stack
246+
///
247+
/// By default, for each connection hyper-rustls will extract host portion
248+
/// of the destination URL and verify that server certificate contains
249+
/// this value.
250+
///
251+
/// If this method is called, hyper-rustls will instead use this resolver
252+
/// to compute the value used to verify the server certificate.
253+
pub fn with_server_name_resolver(
254+
mut self,
255+
resolver: impl ResolveServerName + 'static + Sync + Send,
256+
) -> Self {
257+
self.0.server_name_resolver = Some(Arc::new(resolver));
258+
self
259+
}
260+
240261
/// Override server name for the TLS stack
241262
///
242263
/// By default, for each connection hyper-rustls will extract host portion
@@ -246,9 +267,22 @@ impl ConnectorBuilder<WantsProtocols1> {
246267
/// If this method is called, hyper-rustls will instead verify that server
247268
/// certificate contains `override_server_name`. Domain name included in
248269
/// the URL will not affect certificate validation.
249-
pub fn with_server_name(mut self, override_server_name: String) -> Self {
250-
self.0.override_server_name = Some(override_server_name);
251-
self
270+
#[deprecated(
271+
since = "0.27.1",
272+
note = "use Self::with_server_name_resolver with FixedServerNameResolver instead"
273+
)]
274+
pub fn with_server_name(self, mut override_server_name: String) -> Self {
275+
// remove square brackets around IPv6 address.
276+
if let Some(trimmed) = override_server_name
277+
.strip_prefix('[')
278+
.and_then(|s| s.strip_suffix(']'))
279+
{
280+
override_server_name = trimmed.to_string();
281+
}
282+
283+
self.with_server_name_resolver(move |_: &_| {
284+
ServerName::try_from(override_server_name.clone())
285+
})
252286
}
253287
}
254288

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ mod log {
5555

5656
pub use crate::config::ConfigBuilderExt;
5757
pub use crate::connector::builder::ConnectorBuilder as HttpsConnectorBuilder;
58-
pub use crate::connector::HttpsConnector;
58+
pub use crate::connector::{
59+
DefaultServerNameResolver, FixedServerNameResolver, HttpsConnector, ResolveServerName,
60+
};
5961
pub use crate::stream::MaybeHttpsStream;
6062

6163
/// The various states of the [`HttpsConnectorBuilder`]

0 commit comments

Comments
 (0)