hyper_rustls/connector/
builder.rs1use std::sync::Arc;
2
3use hyper_util::client::legacy::connect::HttpConnector;
4#[cfg(any(
5 feature = "rustls-native-certs",
6 feature = "rustls-platform-verifier",
7 feature = "webpki-roots"
8))]
9use rustls::crypto::CryptoProvider;
10use rustls::ClientConfig;
11
12use super::{DefaultServerNameResolver, HttpsConnector, ResolveServerName};
13#[cfg(any(
14 feature = "rustls-native-certs",
15 feature = "webpki-roots",
16 feature = "rustls-platform-verifier"
17))]
18use crate::config::ConfigBuilderExt;
19use pki_types::ServerName;
20
21pub struct ConnectorBuilder<State>(State);
42
43pub struct WantsTlsConfig(());
45
46impl ConnectorBuilder<WantsTlsConfig> {
47 pub fn new() -> Self {
49 Self(WantsTlsConfig(()))
50 }
51
52 pub fn with_tls_config(self, config: ClientConfig) -> ConnectorBuilder<WantsSchemes> {
61 assert!(
62 config.alpn_protocols.is_empty(),
63 "ALPN protocols should not be pre-defined"
64 );
65 ConnectorBuilder(WantsSchemes { tls_config: config })
66 }
67
68 #[cfg(all(
73 any(feature = "ring", feature = "aws-lc-rs"),
74 feature = "rustls-platform-verifier"
75 ))]
76 pub fn with_platform_verifier(self) -> ConnectorBuilder<WantsSchemes> {
77 self.with_tls_config(
78 ClientConfig::builder()
79 .with_platform_verifier()
80 .with_no_client_auth(),
81 )
82 }
83
84 #[cfg(feature = "rustls-platform-verifier")]
88 pub fn with_provider_and_platform_verifier(
89 self,
90 provider: impl Into<Arc<CryptoProvider>>,
91 ) -> std::io::Result<ConnectorBuilder<WantsSchemes>> {
92 Ok(self.with_tls_config(
93 ClientConfig::builder_with_provider(provider.into())
94 .with_safe_default_protocol_versions()
95 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
96 .with_platform_verifier()
97 .with_no_client_auth(),
98 ))
99 }
100
101 #[cfg(all(
106 any(feature = "ring", feature = "aws-lc-rs"),
107 feature = "rustls-native-certs"
108 ))]
109 pub fn with_native_roots(self) -> std::io::Result<ConnectorBuilder<WantsSchemes>> {
110 Ok(self.with_tls_config(
111 ClientConfig::builder()
112 .with_native_roots()?
113 .with_no_client_auth(),
114 ))
115 }
116
117 #[cfg(feature = "rustls-native-certs")]
121 pub fn with_provider_and_native_roots(
122 self,
123 provider: impl Into<Arc<CryptoProvider>>,
124 ) -> std::io::Result<ConnectorBuilder<WantsSchemes>> {
125 Ok(self.with_tls_config(
126 ClientConfig::builder_with_provider(provider.into())
127 .with_safe_default_protocol_versions()
128 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
129 .with_native_roots()?
130 .with_no_client_auth(),
131 ))
132 }
133
134 #[cfg(all(any(feature = "ring", feature = "aws-lc-rs"), feature = "webpki-roots"))]
139 pub fn with_webpki_roots(self) -> ConnectorBuilder<WantsSchemes> {
140 self.with_tls_config(
141 ClientConfig::builder()
142 .with_webpki_roots()
143 .with_no_client_auth(),
144 )
145 }
146
147 #[cfg(feature = "webpki-roots")]
152 pub fn with_provider_and_webpki_roots(
153 self,
154 provider: impl Into<Arc<CryptoProvider>>,
155 ) -> Result<ConnectorBuilder<WantsSchemes>, rustls::Error> {
156 Ok(self.with_tls_config(
157 ClientConfig::builder_with_provider(provider.into())
158 .with_safe_default_protocol_versions()?
159 .with_webpki_roots()
160 .with_no_client_auth(),
161 ))
162 }
163}
164
165impl Default for ConnectorBuilder<WantsTlsConfig> {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171pub struct WantsSchemes {
174 tls_config: ClientConfig,
175}
176
177impl ConnectorBuilder<WantsSchemes> {
178 pub fn https_only(self) -> ConnectorBuilder<WantsProtocols1> {
182 ConnectorBuilder(WantsProtocols1 {
183 tls_config: self.0.tls_config,
184 https_only: true,
185 server_name_resolver: None,
186 })
187 }
188
189 pub fn https_or_http(self) -> ConnectorBuilder<WantsProtocols1> {
194 ConnectorBuilder(WantsProtocols1 {
195 tls_config: self.0.tls_config,
196 https_only: false,
197 server_name_resolver: None,
198 })
199 }
200}
201
202pub struct WantsProtocols1 {
207 tls_config: ClientConfig,
208 https_only: bool,
209 server_name_resolver: Option<Arc<dyn ResolveServerName + Sync + Send>>,
210}
211
212impl WantsProtocols1 {
213 fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> {
214 HttpsConnector {
215 force_https: self.https_only,
216 http: conn,
217 tls_config: std::sync::Arc::new(self.tls_config),
218 server_name_resolver: self
219 .server_name_resolver
220 .unwrap_or_else(|| Arc::new(DefaultServerNameResolver::default())),
221 }
222 }
223
224 fn build(self) -> HttpsConnector<HttpConnector> {
225 let mut http = HttpConnector::new();
226 http.enforce_http(false);
228 self.wrap_connector(http)
229 }
230}
231
232impl ConnectorBuilder<WantsProtocols1> {
233 #[cfg(feature = "http1")]
237 pub fn enable_http1(self) -> ConnectorBuilder<WantsProtocols2> {
238 ConnectorBuilder(WantsProtocols2 { inner: self.0 })
239 }
240
241 #[cfg(feature = "http2")]
245 pub fn enable_http2(mut self) -> ConnectorBuilder<WantsProtocols3> {
246 self.0.tls_config.alpn_protocols = vec![b"h2".to_vec()];
247 ConnectorBuilder(WantsProtocols3 {
248 inner: self.0,
249 enable_http1: false,
250 })
251 }
252
253 #[cfg(feature = "http2")]
258 pub fn enable_all_versions(mut self) -> ConnectorBuilder<WantsProtocols3> {
259 #[cfg(feature = "http1")]
260 let alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
261 #[cfg(not(feature = "http1"))]
262 let alpn_protocols = vec![b"h2".to_vec()];
263
264 self.0.tls_config.alpn_protocols = alpn_protocols;
265 ConnectorBuilder(WantsProtocols3 {
266 inner: self.0,
267 enable_http1: cfg!(feature = "http1"),
268 })
269 }
270
271 pub fn with_server_name_resolver(
280 mut self,
281 resolver: impl ResolveServerName + 'static + Sync + Send,
282 ) -> Self {
283 self.0.server_name_resolver = Some(Arc::new(resolver));
284 self
285 }
286
287 #[deprecated(
297 since = "0.27.1",
298 note = "use Self::with_server_name_resolver with FixedServerNameResolver instead"
299 )]
300 pub fn with_server_name(self, mut override_server_name: String) -> Self {
301 if let Some(trimmed) = override_server_name
303 .strip_prefix('[')
304 .and_then(|s| s.strip_suffix(']'))
305 {
306 override_server_name = trimmed.to_string();
307 }
308
309 self.with_server_name_resolver(move |_: &_| {
310 ServerName::try_from(override_server_name.clone())
311 })
312 }
313}
314
315pub struct WantsProtocols2 {
322 inner: WantsProtocols1,
323}
324
325impl ConnectorBuilder<WantsProtocols2> {
326 #[cfg(feature = "http2")]
330 pub fn enable_http2(mut self) -> ConnectorBuilder<WantsProtocols3> {
331 self.0.inner.tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
332 ConnectorBuilder(WantsProtocols3 {
333 inner: self.0.inner,
334 enable_http1: true,
335 })
336 }
337
338 pub fn build(self) -> HttpsConnector<HttpConnector> {
340 self.0.inner.build()
341 }
342
343 pub fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> {
345 self.0.inner.wrap_connector(conn)
350 }
351}
352
353#[cfg(feature = "http2")]
359pub struct WantsProtocols3 {
360 inner: WantsProtocols1,
361 #[allow(dead_code)]
363 enable_http1: bool,
364}
365
366#[cfg(feature = "http2")]
367impl ConnectorBuilder<WantsProtocols3> {
368 pub fn build(self) -> HttpsConnector<HttpConnector> {
370 self.0.inner.build()
371 }
372
373 pub fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> {
375 self.0.inner.wrap_connector(conn)
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 #[test]
386 #[cfg(all(feature = "webpki-roots", feature = "http1"))]
387 fn test_builder() {
388 ensure_global_state();
389 let _connector = super::ConnectorBuilder::new()
390 .with_webpki_roots()
391 .https_only()
392 .enable_http1()
393 .build();
394 }
395
396 #[test]
397 #[cfg(feature = "http1")]
398 #[should_panic(expected = "ALPN protocols should not be pre-defined")]
399 fn test_reject_predefined_alpn() {
400 ensure_global_state();
401 let roots = rustls::RootCertStore::empty();
402 let mut config_with_alpn = rustls::ClientConfig::builder()
403 .with_root_certificates(roots)
404 .with_no_client_auth();
405 config_with_alpn.alpn_protocols = vec![b"fancyprotocol".to_vec()];
406 let _connector = super::ConnectorBuilder::new()
407 .with_tls_config(config_with_alpn)
408 .https_only()
409 .enable_http1()
410 .build();
411 }
412
413 #[test]
414 #[cfg(all(feature = "http1", feature = "http2"))]
415 fn test_alpn() {
416 ensure_global_state();
417 let roots = rustls::RootCertStore::empty();
418 let tls_config = rustls::ClientConfig::builder()
419 .with_root_certificates(roots)
420 .with_no_client_auth();
421 let connector = super::ConnectorBuilder::new()
422 .with_tls_config(tls_config.clone())
423 .https_only()
424 .enable_http1()
425 .build();
426 assert!(connector
427 .tls_config
428 .alpn_protocols
429 .is_empty());
430 let connector = super::ConnectorBuilder::new()
431 .with_tls_config(tls_config.clone())
432 .https_only()
433 .enable_http2()
434 .build();
435 assert_eq!(&connector.tls_config.alpn_protocols, &[b"h2".to_vec()]);
436 let connector = super::ConnectorBuilder::new()
437 .with_tls_config(tls_config.clone())
438 .https_only()
439 .enable_http1()
440 .enable_http2()
441 .build();
442 assert_eq!(
443 &connector.tls_config.alpn_protocols,
444 &[b"h2".to_vec(), b"http/1.1".to_vec()]
445 );
446 let connector = super::ConnectorBuilder::new()
447 .with_tls_config(tls_config)
448 .https_only()
449 .enable_all_versions()
450 .build();
451 assert_eq!(
452 &connector.tls_config.alpn_protocols,
453 &[b"h2".to_vec(), b"http/1.1".to_vec()]
454 );
455 }
456
457 #[test]
458 #[cfg(all(not(feature = "http1"), feature = "http2"))]
459 fn test_alpn_http2() {
460 let roots = rustls::RootCertStore::empty();
461 let tls_config = rustls::ClientConfig::builder()
462 .with_safe_defaults()
463 .with_root_certificates(roots)
464 .with_no_client_auth();
465 let connector = super::ConnectorBuilder::new()
466 .with_tls_config(tls_config.clone())
467 .https_only()
468 .enable_http2()
469 .build();
470 assert_eq!(&connector.tls_config.alpn_protocols, &[b"h2".to_vec()]);
471 let connector = super::ConnectorBuilder::new()
472 .with_tls_config(tls_config)
473 .https_only()
474 .enable_all_versions()
475 .build();
476 assert_eq!(&connector.tls_config.alpn_protocols, &[b"h2".to_vec()]);
477 }
478
479 fn ensure_global_state() {
480 #[cfg(feature = "ring")]
481 let _ = rustls::crypto::ring::default_provider().install_default();
482 #[cfg(feature = "aws-lc-rs")]
483 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
484 }
485}