libdd_common/connector/
mod.rs1use futures::future::BoxFuture;
5use futures::{future, FutureExt};
6use hyper_util::client::legacy::connect;
7
8use std::future::Future;
9use std::pin::Pin;
10use std::sync::LazyLock;
11use std::task::{Context, Poll};
12
13#[cfg(unix)]
14pub mod uds;
15
16pub mod named_pipe;
17
18pub mod errors;
19
20mod conn_stream;
21use conn_stream::{ConnStream, ConnStreamError};
22
23#[derive(Clone)]
24pub enum Connector {
25 Http(connect::HttpConnector),
26 #[cfg(feature = "tls-core")]
27 Https(hyper_rustls::HttpsConnector<connect::HttpConnector>),
28}
29
30static DEFAULT_CONNECTOR: LazyLock<Connector> = LazyLock::new(Connector::new);
31
32impl Default for Connector {
33 fn default() -> Self {
34 DEFAULT_CONNECTOR.clone()
35 }
36}
37
38impl Connector {
39 fn new() -> Self {
42 #[cfg(feature = "tls-core")]
43 {
44 #[cfg(feature = "use_webpki_roots")]
45 let https_connector_fn = https::build_https_connector_with_webpki_roots;
46 #[cfg(not(feature = "use_webpki_roots"))]
47 let https_connector_fn = https::build_https_connector;
48
49 match https_connector_fn() {
50 Ok(connector) => Connector::Https(connector),
51 Err(_) => Connector::Http(connect::HttpConnector::new()),
52 }
53 }
54 #[cfg(not(feature = "tls-core"))]
55 {
56 Connector::Http(connect::HttpConnector::new())
57 }
58 }
59
60 fn build_conn_stream(
61 &mut self,
62 uri: hyper::Uri,
63 require_tls: bool,
64 ) -> BoxFuture<'static, Result<ConnStream, ConnStreamError>> {
65 match self {
66 Self::Http(c) => {
67 if require_tls {
68 future::err::<ConnStream, ConnStreamError>(
69 errors::Error::CannotEstablishTlsConnection.into(),
70 )
71 .boxed()
72 } else {
73 ConnStream::from_http_connector_with_uri(c, uri).boxed()
74 }
75 }
76 #[cfg(feature = "tls-core")]
77 Self::Https(c) => {
78 ConnStream::from_https_connector_with_uri(c, uri, require_tls).boxed()
79 }
80 }
81 }
82}
83
84#[cfg(feature = "tls-core")]
85mod https {
86 #[cfg(feature = "use_webpki_roots")]
87 use hyper_rustls::ConfigBuilderExt;
88
89 use rustls::ClientConfig;
90
91 #[cfg(feature = "https")]
94 fn ensure_crypto_provider_initialized() {
95 use std::sync::Once;
96
97 static INIT_CRYPTO_PROVIDER: Once = Once::new();
98
99 INIT_CRYPTO_PROVIDER.call_once(|| {
100 let _ = rustls::crypto::ring::default_provider().install_default();
101 });
102 }
103
104 #[cfg(not(feature = "https"))]
107 fn ensure_crypto_provider_initialized() {}
108
109 #[cfg(feature = "use_webpki_roots")]
110 pub(super) fn build_https_connector_with_webpki_roots() -> anyhow::Result<
111 hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>,
112 > {
113 ensure_crypto_provider_initialized(); let client_config = ClientConfig::builder()
116 .with_webpki_roots()
117 .with_no_client_auth();
118 Ok(hyper_rustls::HttpsConnectorBuilder::new()
119 .with_tls_config(client_config)
120 .https_or_http()
121 .enable_http1()
122 .build())
123 }
124
125 #[cfg(not(feature = "use_webpki_roots"))]
126 pub(super) fn build_https_connector() -> anyhow::Result<
127 hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>,
128 > {
129 ensure_crypto_provider_initialized(); let certs = load_root_certs()?;
132 let client_config = ClientConfig::builder()
133 .with_root_certificates(certs)
134 .with_no_client_auth();
135 Ok(hyper_rustls::HttpsConnectorBuilder::new()
136 .with_tls_config(client_config)
137 .https_or_http()
138 .enable_http1()
139 .build())
140 }
141
142 #[cfg(not(feature = "use_webpki_roots"))]
143 fn load_root_certs() -> anyhow::Result<rustls::RootCertStore> {
144 use super::errors;
145
146 let mut roots = rustls::RootCertStore::empty();
147
148 let cert_result = rustls_native_certs::load_native_certs();
149 if cert_result.certs.is_empty() {
150 if let Some(err) = cert_result.errors.into_iter().next() {
151 return Err(err.into());
152 }
153 }
154 for cert in cert_result.certs {
157 roots.add(cert).ok();
159 }
160 if roots.is_empty() {
161 return Err(errors::Error::NoValidCertifacteRootsFound.into());
162 }
163 Ok(roots)
164 }
165}
166
167impl tower_service::Service<hyper::Uri> for Connector {
168 type Response = ConnStream;
169 type Error = ConnStreamError;
170
171 #[allow(clippy::type_complexity)]
174 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
175
176 fn call(&mut self, uri: hyper::Uri) -> Self::Future {
177 match uri.scheme_str() {
178 Some("unix") => conn_stream::ConnStream::from_uds_uri(uri).boxed(),
179 Some("windows") => conn_stream::ConnStream::from_named_pipe_uri(uri).boxed(),
180 Some("https") => self.build_conn_stream(uri, true),
181 _ => self.build_conn_stream(uri, false),
182 }
183 }
184
185 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
186 match self {
187 Connector::Http(c) => c.poll_ready(cx).map_err(|e| e.into()),
188 #[cfg(feature = "tls-core")]
189 Connector::Https(c) => c.poll_ready(cx),
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use crate::http_common;
197 use std::env;
198
199 use super::*;
200
201 #[test]
202 #[cfg_attr(miri, ignore)]
203 #[cfg(not(feature = "use_webpki_roots"))]
204 fn test_hyper_client_from_connector() {
207 let _ = http_common::new_default_client();
208 }
209
210 #[test]
211 #[cfg_attr(miri, ignore)]
212 #[cfg(feature = "use_webpki_roots")]
213 fn test_hyper_client_from_connector_with_webpki_roots() {
214 let _ = http_common::new_default_client();
215 }
216
217 #[test]
218 #[cfg_attr(miri, ignore)]
219 #[cfg(not(feature = "use_webpki_roots"))]
220 fn test_missing_root_certificates_only_allow_http_connections() {
223 const ENV_SSL_CERT_FILE: &str = "SSL_CERT_FILE";
224 const ENV_SSL_CERT_DIR: &str = "SSL_CERT_DIR";
225 let old_value = env::var(ENV_SSL_CERT_FILE).unwrap_or_default();
226 let old_dir_value = env::var(ENV_SSL_CERT_DIR).unwrap_or_default();
227
228 env::set_var(ENV_SSL_CERT_FILE, "this/folder/does/not/exist");
229 env::set_var(ENV_SSL_CERT_DIR, "this/folder/does/not/exist");
230 let connector = Connector::new();
231
232 assert!(matches!(connector, Connector::Http(_)));
233
234 env::set_var(ENV_SSL_CERT_FILE, old_value);
235 env::set_var(ENV_SSL_CERT_DIR, old_dir_value);
236 }
237
238 #[test]
239 #[cfg_attr(miri, ignore)]
240 #[cfg(feature = "use_webpki_roots")]
241 #[cfg(feature = "tls-core")]
242 fn test_missing_root_certificates_use_webpki_certificates() {
245 const ENV_SSL_CERT_FILE: &str = "SSL_CERT_FILE";
246 let old_value = env::var(ENV_SSL_CERT_FILE).unwrap_or_default();
247
248 env::set_var(ENV_SSL_CERT_FILE, "this/folder/does/not/exist");
249 let connector = Connector::new();
250 assert!(matches!(connector, Connector::Https(_)));
251
252 env::set_var(ENV_SSL_CERT_FILE, old_value);
253 }
254}