/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hyper-rustls-0.27.9/src/connector.rs
Line | Count | Source |
1 | | use std::future::Future; |
2 | | use std::pin::Pin; |
3 | | use std::sync::Arc; |
4 | | use std::task::{Context, Poll}; |
5 | | use std::{fmt, io}; |
6 | | |
7 | | use http::Uri; |
8 | | use hyper::rt; |
9 | | use hyper_util::client::legacy::connect::Connection; |
10 | | use hyper_util::rt::TokioIo; |
11 | | use rustls::pki_types::ServerName; |
12 | | use tokio_rustls::TlsConnector; |
13 | | use tower_service::Service; |
14 | | |
15 | | use crate::stream::MaybeHttpsStream; |
16 | | |
17 | | pub(crate) mod builder; |
18 | | |
19 | | type BoxError = Box<dyn std::error::Error + Send + Sync>; |
20 | | |
21 | | /// A Connector for the `https` scheme. |
22 | | #[derive(Clone)] |
23 | | pub struct HttpsConnector<T> { |
24 | | force_https: bool, |
25 | | http: T, |
26 | | tls_config: Arc<rustls::ClientConfig>, |
27 | | server_name_resolver: Arc<dyn ResolveServerName + Sync + Send>, |
28 | | } |
29 | | |
30 | | impl<T> HttpsConnector<T> { |
31 | | /// Creates a [`crate::HttpsConnectorBuilder`] to configure a `HttpsConnector`. |
32 | | /// |
33 | | /// This is the same as [`crate::HttpsConnectorBuilder::new()`]. |
34 | 0 | pub fn builder() -> builder::ConnectorBuilder<builder::WantsTlsConfig> { |
35 | 0 | builder::ConnectorBuilder::new() |
36 | 0 | } |
37 | | |
38 | | /// Creates a new `HttpsConnector`. |
39 | | /// |
40 | | /// The recommended way to create a `HttpsConnector` is to use a [`crate::HttpsConnectorBuilder`]. See [`HttpsConnector::builder()`]. |
41 | 0 | pub fn new( |
42 | 0 | http: T, |
43 | 0 | tls_config: impl Into<Arc<rustls::ClientConfig>>, |
44 | 0 | force_https: bool, |
45 | 0 | server_name_resolver: Arc<dyn ResolveServerName + Send + Sync>, |
46 | 0 | ) -> Self { |
47 | 0 | Self { |
48 | 0 | http, |
49 | 0 | tls_config: tls_config.into(), |
50 | 0 | force_https, |
51 | 0 | server_name_resolver, |
52 | 0 | } |
53 | 0 | } |
54 | | |
55 | | /// Force the use of HTTPS when connecting. |
56 | | /// |
57 | | /// If a URL is not `https` when connecting, an error is returned. |
58 | 0 | pub fn enforce_https(&mut self) { |
59 | 0 | self.force_https = true; |
60 | 0 | } |
61 | | } |
62 | | |
63 | | impl<T> Service<Uri> for HttpsConnector<T> |
64 | | where |
65 | | T: Service<Uri>, |
66 | | T::Response: Connection + rt::Read + rt::Write + Send + Unpin + 'static, |
67 | | T::Future: Send + 'static, |
68 | | T::Error: Into<BoxError>, |
69 | | { |
70 | | type Response = MaybeHttpsStream<T::Response>; |
71 | | type Error = BoxError; |
72 | | |
73 | | #[allow(clippy::type_complexity)] |
74 | | type Future = |
75 | | Pin<Box<dyn Future<Output = Result<MaybeHttpsStream<T::Response>, BoxError>> + Send>>; |
76 | | |
77 | 0 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { |
78 | 0 | match self.http.poll_ready(cx) { |
79 | 0 | Poll::Ready(Ok(())) => Poll::Ready(Ok(())), |
80 | 0 | Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), |
81 | 0 | Poll::Pending => Poll::Pending, |
82 | | } |
83 | 0 | } |
84 | | |
85 | 0 | fn call(&mut self, dst: Uri) -> Self::Future { |
86 | | // dst.scheme() would need to derive Eq to be matchable; |
87 | | // use an if cascade instead |
88 | 0 | match dst.scheme() { |
89 | 0 | Some(scheme) if scheme == &http::uri::Scheme::HTTP && !self.force_https => { |
90 | 0 | let future = self.http.call(dst); |
91 | 0 | return Box::pin(async move { |
92 | 0 | Ok(MaybeHttpsStream::Http(future.await.map_err(Into::into)?)) |
93 | 0 | }); Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as tower_service::Service<http::uri::Uri>>::call::{closure#0}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as tower_service::Service<http::uri::Uri>>::call::{closure#0}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as tower_service::Service<http::uri::Uri>>::call::{closure#0} |
94 | | } |
95 | 0 | Some(scheme) if scheme != &http::uri::Scheme::HTTPS => { |
96 | 0 | let message = format!("unsupported scheme {scheme}"); |
97 | 0 | return Box::pin(async move { Err(io::Error::other(message).into()) });Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as tower_service::Service<http::uri::Uri>>::call::{closure#1}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as tower_service::Service<http::uri::Uri>>::call::{closure#1}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as tower_service::Service<http::uri::Uri>>::call::{closure#1} |
98 | | } |
99 | 0 | Some(_) => {} |
100 | 0 | None => return Box::pin(async move { Err(io::Error::other("missing scheme").into()) }),Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as tower_service::Service<http::uri::Uri>>::call::{closure#2}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as tower_service::Service<http::uri::Uri>>::call::{closure#2}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as tower_service::Service<http::uri::Uri>>::call::{closure#2} |
101 | | }; |
102 | | |
103 | 0 | let cfg = self.tls_config.clone(); |
104 | 0 | let hostname = match self.server_name_resolver.resolve(&dst) { |
105 | 0 | Ok(hostname) => hostname, |
106 | 0 | Err(e) => { |
107 | 0 | return Box::pin(async move { Err(e) });Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as tower_service::Service<http::uri::Uri>>::call::{closure#3}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as tower_service::Service<http::uri::Uri>>::call::{closure#3}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as tower_service::Service<http::uri::Uri>>::call::{closure#3} |
108 | | } |
109 | | }; |
110 | | |
111 | 0 | let connecting_future = self.http.call(dst); |
112 | 0 | Box::pin(async move { |
113 | 0 | let tcp = connecting_future |
114 | 0 | .await |
115 | 0 | .map_err(Into::into)?; |
116 | 0 | Ok(MaybeHttpsStream::Https(TokioIo::new( |
117 | 0 | TlsConnector::from(cfg) |
118 | 0 | .connect(hostname, TokioIo::new(tcp)) |
119 | 0 | .await |
120 | 0 | .map_err(io::Error::other)?, |
121 | | ))) |
122 | 0 | }) Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as tower_service::Service<http::uri::Uri>>::call::{closure#4}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as tower_service::Service<http::uri::Uri>>::call::{closure#4}Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as tower_service::Service<http::uri::Uri>>::call::{closure#4} |
123 | 0 | } Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as tower_service::Service<http::uri::Uri>>::callUnexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as tower_service::Service<http::uri::Uri>>::call Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as tower_service::Service<http::uri::Uri>>::call |
124 | | } |
125 | | |
126 | | impl<H, C> From<(H, C)> for HttpsConnector<H> |
127 | | where |
128 | | C: Into<Arc<rustls::ClientConfig>>, |
129 | | { |
130 | 0 | fn from((http, cfg): (H, C)) -> Self { |
131 | 0 | Self { |
132 | 0 | force_https: false, |
133 | 0 | http, |
134 | 0 | tls_config: cfg.into(), |
135 | 0 | server_name_resolver: Arc::new(DefaultServerNameResolver::default()), |
136 | 0 | } |
137 | 0 | } Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>> as core::convert::From<(tower::util::service_fn::ServiceFn<<reqwest::connect::ConnectorService>::connect_local_transport::{closure#0}::{closure#0}>, alloc::sync::Arc<rustls::client::client_conn::ClientConfig>)>>::fromUnexecuted instantiation: <hyper_rustls::connector::HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>> as core::convert::From<(hyper_util::client::legacy::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver>, alloc::sync::Arc<rustls::client::client_conn::ClientConfig>)>>::from Unexecuted instantiation: <hyper_rustls::connector::HttpsConnector<_> as core::convert::From<(_, _)>>::from |
138 | | } |
139 | | |
140 | | impl<T> fmt::Debug for HttpsConnector<T> { |
141 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
142 | 0 | f.debug_struct("HttpsConnector") |
143 | 0 | .field("force_https", &self.force_https) |
144 | 0 | .finish() |
145 | 0 | } |
146 | | } |
147 | | |
148 | | /// The default server name resolver, which uses the hostname in the URI. |
149 | | #[derive(Default)] |
150 | | pub struct DefaultServerNameResolver(()); |
151 | | |
152 | | impl ResolveServerName for DefaultServerNameResolver { |
153 | 0 | fn resolve( |
154 | 0 | &self, |
155 | 0 | uri: &Uri, |
156 | 0 | ) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>> { |
157 | 0 | let mut hostname = uri.host().unwrap_or_default(); |
158 | | |
159 | | // Remove square brackets around IPv6 address. |
160 | 0 | if let Some(trimmed) = hostname |
161 | 0 | .strip_prefix('[') |
162 | 0 | .and_then(|h| h.strip_suffix(']')) |
163 | 0 | { |
164 | 0 | hostname = trimmed; |
165 | 0 | } |
166 | | |
167 | 0 | ServerName::try_from(hostname.to_string()).map_err(|e| Box::new(e) as _) |
168 | 0 | } |
169 | | } |
170 | | |
171 | | /// A server name resolver which always returns the same fixed name. |
172 | | pub struct FixedServerNameResolver { |
173 | | name: ServerName<'static>, |
174 | | } |
175 | | |
176 | | impl FixedServerNameResolver { |
177 | | /// Creates a new resolver returning the specified name. |
178 | 0 | pub fn new(name: ServerName<'static>) -> Self { |
179 | 0 | Self { name } |
180 | 0 | } |
181 | | } |
182 | | |
183 | | impl ResolveServerName for FixedServerNameResolver { |
184 | 0 | fn resolve( |
185 | 0 | &self, |
186 | 0 | _: &Uri, |
187 | 0 | ) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>> { |
188 | 0 | Ok(self.name.clone()) |
189 | 0 | } |
190 | | } |
191 | | |
192 | | impl<F, E> ResolveServerName for F |
193 | | where |
194 | | F: Fn(&Uri) -> Result<ServerName<'static>, E>, |
195 | | E: Into<Box<dyn std::error::Error + Sync + Send>>, |
196 | | { |
197 | 0 | fn resolve( |
198 | 0 | &self, |
199 | 0 | uri: &Uri, |
200 | 0 | ) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>> { |
201 | 0 | self(uri).map_err(Into::into) |
202 | 0 | } |
203 | | } |
204 | | |
205 | | /// A trait implemented by types that can resolve a [`ServerName`] for a request. |
206 | | pub trait ResolveServerName { |
207 | | /// Maps a [`Uri`] into a [`ServerName`]. |
208 | | fn resolve( |
209 | | &self, |
210 | | uri: &Uri, |
211 | | ) -> Result<ServerName<'static>, Box<dyn std::error::Error + Sync + Send>>; |
212 | | } |
213 | | |
214 | | #[cfg(all( |
215 | | test, |
216 | | any(feature = "ring", feature = "aws-lc-rs"), |
217 | | any( |
218 | | feature = "rustls-native-certs", |
219 | | feature = "webpki-roots", |
220 | | feature = "rustls-platform-verifier", |
221 | | ) |
222 | | ))] |
223 | | mod tests { |
224 | | use std::future::poll_fn; |
225 | | |
226 | | use http::Uri; |
227 | | use hyper_util::rt::TokioIo; |
228 | | use tokio::net::TcpStream; |
229 | | use tower_service::Service; |
230 | | |
231 | | use super::*; |
232 | | use crate::{ConfigBuilderExt, HttpsConnectorBuilder, MaybeHttpsStream}; |
233 | | |
234 | | #[tokio::test] |
235 | | async fn connects_https() { |
236 | | connect(Allow::Any, Scheme::Https) |
237 | | .await |
238 | | .unwrap(); |
239 | | } |
240 | | |
241 | | #[tokio::test] |
242 | | async fn connects_http() { |
243 | | connect(Allow::Any, Scheme::Http) |
244 | | .await |
245 | | .unwrap(); |
246 | | } |
247 | | |
248 | | #[tokio::test] |
249 | | async fn connects_https_only() { |
250 | | connect(Allow::Https, Scheme::Https) |
251 | | .await |
252 | | .unwrap(); |
253 | | } |
254 | | |
255 | | #[tokio::test] |
256 | | async fn enforces_https_only() { |
257 | | let message = connect(Allow::Https, Scheme::Http) |
258 | | .await |
259 | | .unwrap_err() |
260 | | .to_string(); |
261 | | |
262 | | assert_eq!(message, "unsupported scheme http"); |
263 | | } |
264 | | |
265 | | async fn connect( |
266 | | allow: Allow, |
267 | | scheme: Scheme, |
268 | | ) -> Result<MaybeHttpsStream<TokioIo<TcpStream>>, BoxError> { |
269 | | let config_builder = rustls::ClientConfig::builder(); |
270 | | cfg_if::cfg_if! { |
271 | | if #[cfg(feature = "rustls-platform-verifier")] { |
272 | | let config_builder = config_builder.try_with_platform_verifier()?; |
273 | | } else if #[cfg(feature = "rustls-native-certs")] { |
274 | | let config_builder = config_builder.with_native_roots().unwrap(); |
275 | | } else if #[cfg(feature = "webpki-roots")] { |
276 | | let config_builder = config_builder.with_webpki_roots(); |
277 | | } |
278 | | } |
279 | | let config = config_builder.with_no_client_auth(); |
280 | | |
281 | | let builder = HttpsConnectorBuilder::new().with_tls_config(config); |
282 | | let mut service = match allow { |
283 | | Allow::Https => builder.https_only(), |
284 | | Allow::Any => builder.https_or_http(), |
285 | | } |
286 | | .enable_http1() |
287 | | .build(); |
288 | | |
289 | | poll_fn(|cx| service.poll_ready(cx)).await?; |
290 | | service |
291 | | .call(Uri::from_static(match scheme { |
292 | | Scheme::Https => "https://google.com", |
293 | | Scheme::Http => "http://google.com", |
294 | | })) |
295 | | .await |
296 | | } |
297 | | |
298 | | enum Allow { |
299 | | Https, |
300 | | Any, |
301 | | } |
302 | | |
303 | | enum Scheme { |
304 | | Https, |
305 | | Http, |
306 | | } |
307 | | } |