diff --git a/Cargo.toml b/Cargo.toml index f441aea..863517c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,34 +10,37 @@ homepage = "https://github.com/ctz/hyper-rustls" repository = "https://github.com/ctz/hyper-rustls" [dependencies] -log = "0.4.4" -ct-logs = { version = "^0.8", optional = true } -hyper = { version = "0.14", default-features = false, features = ["client", "http1"] } -rustls = "0.19" -rustls-native-certs = { version = "0.5.0", optional = true } +http = "0.2" +hyper = { version = "0.14", default-features = false, features = ["client"] } +log = { version = "0.4.4", optional = true } +rustls-native-certs = { version = "0.6", optional = true } +rustls = { version = "0.20.1", default-features = false } tokio = "1.0" -tokio-rustls = "0.22" -webpki = "0.21.0" -webpki-roots = { version = "0.21", optional = true } +tokio-rustls = { version = "0.23", default-features = false } +webpki-roots = { version = "0.22", optional = true } [dev-dependencies] async-stream = "0.3.0" -tokio = { version = "1.0", features = ["io-std", "macros", "net", "rt-multi-thread"] } -hyper = { version = "0.14", features = ["full"] } futures-util = { version = "0.3.1", default-features = false } +hyper = { version = "0.14", features = ["full"] } +rustls = { version = "0.20.1", default-features = false, features = ["tls12"] } +rustls-pemfile = "0.2.1" +tokio = { version = "1.0", features = ["io-std", "macros", "net", "rt-multi-thread"] } [features] -default = ["native-tokio", "http1"] +default = ["native-tokio", "http1", "tls12", "logging"] http1 = ["hyper/http1"] http2 = ["hyper/http2"] webpki-tokio = ["tokio-runtime", "webpki-roots"] native-tokio = ["tokio-runtime", "rustls-native-certs"] -tokio-runtime = ["hyper/runtime", "ct-logs"] +tokio-runtime = ["hyper/runtime"] +tls12 = ["tokio-rustls/tls12", "rustls/tls12"] +logging = ["log", "tokio-rustls/logging", "rustls/logging"] [[example]] name = "client" path = "examples/client.rs" -required-features = ["native-tokio", "tokio-runtime"] +required-features = ["native-tokio", "http1"] [[example]] name = "server" diff --git a/examples/client.rs b/examples/client.rs index 4e97d9d..4659b6d 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -3,6 +3,9 @@ //! First parameter is the mandatory URL to GET. //! Second parameter is an optional path to CA store. use hyper::{body::to_bytes, client, Body, Uri}; +use hyper_rustls::ConfigBuilderExt; +use rustls::RootCertStore; + use std::str::FromStr; use std::{env, fs, io}; @@ -40,23 +43,32 @@ async fn run_client() -> io::Result<()> { None => None, }; - // Prepare the HTTPS connector. - let https = match ca { + // Prepare the TLS client config + let tls = match ca { Some(ref mut rd) => { - // Build an HTTP connector which supports HTTPS too. - let mut http = client::HttpConnector::new(); - http.enforce_http(false); - // Build a TLS client, using the custom CA store for lookups. - let mut tls = rustls::ClientConfig::new(); - tls.root_store - .add_pem_file(rd) + // Read trust roots + let certs = rustls_pemfile::certs(rd) .map_err(|_| error("failed to load custom CA store".into()))?; - // Join the above part into an HTTPS connector. - hyper_rustls::HttpsConnector::from((http, tls)) + let mut roots = RootCertStore::empty(); + roots.add_parsable_certificates(&certs); + // TLS client config using the custom CA store for lookups + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth() } - // Default HTTPS connector. - None => hyper_rustls::HttpsConnector::with_native_roots(), + // Default TLS client config with native roots + None => rustls::ClientConfig::builder() + .with_safe_defaults() + .with_native_roots() + .with_no_client_auth(), }; + // Prepare the HTTPS connector + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(tls) + .https_or_http() + .enable_http1() + .build(); // Build the hyper client from the HTTPS connector. let client: client::Client<_, hyper::Body> = client::Client::builder().build(https); diff --git a/examples/server.rs b/examples/server.rs index a64f2a4..004deeb 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -4,14 +4,13 @@ //! Certificate and private key are hardcoded to sample files. //! hyper will automatically use HTTP/2 if a client starts talking HTTP/2, //! otherwise HTTP/1.1 will be used. +use std::{env, fs, io, sync}; + use async_stream::stream; use futures_util::future::TryFutureExt; use hyper::server::accept; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server, StatusCode}; -use rustls::internal::pemfile; -use std::vec::Vec; -use std::{env, fs, io, sync}; use tokio::net::TcpListener; use tokio_rustls::TlsAcceptor; @@ -43,12 +42,13 @@ async fn run_server() -> Result<(), Box> { // Load private key. let key = load_private_key("examples/sample.rsa")?; // Do not use client certificate authentication. - let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new()); - // Select a certificate to use. - cfg.set_single_cert(certs, key) + let mut cfg = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, key) .map_err(|e| error(format!("{}", e)))?; // Configure ALPN to accept HTTP/2, HTTP/1.1 in that order. - cfg.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]); + cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; sync::Arc::new(cfg) }; @@ -107,7 +107,9 @@ fn load_certs(filename: &str) -> io::Result> { let mut reader = io::BufReader::new(certfile); // Load and return certificate. - pemfile::certs(&mut reader).map_err(|_| error("failed to load certificate".into())) + let certs = rustls_pemfile::certs(&mut reader) + .map_err(|_| error("failed to load certificate".into()))?; + Ok(certs.into_iter().map(rustls::Certificate).collect()) } // Load private key from file. @@ -118,10 +120,11 @@ fn load_private_key(filename: &str) -> io::Result { let mut reader = io::BufReader::new(keyfile); // Load and return a single private key. - let keys = pemfile::rsa_private_keys(&mut reader) + let keys = rustls_pemfile::rsa_private_keys(&mut reader) .map_err(|_| error("failed to load private key".into()))?; if keys.len() != 1 { return Err(error("expected a single private key".into())); } - Ok(keys[0].clone()) + + Ok(rustls::PrivateKey(keys[0].clone())) } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..b1b0ad2 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,66 @@ +use rustls::client::WantsTransparencyPolicyOrClientCert; +use rustls::{ClientConfig, ConfigBuilder, WantsVerifier}; + +/// Methods for configuring roots +/// +/// This adds methods (gated by crate features) for easily configuring +/// TLS server roots a rustls ClientConfig will trust. +pub trait ConfigBuilderExt { + /// This configures the platform's trusted certs, as implemented by + /// rustls-native-certs + #[cfg(feature = "rustls-native-certs")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))] + fn with_native_roots(self) -> ConfigBuilder; + + /// This configures the webpki roots, which are Mozilla's set of + /// trusted roots as packaged by webpki-roots. + #[cfg(feature = "webpki-roots")] + #[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))] + fn with_webpki_roots(self) -> ConfigBuilder; +} + +impl ConfigBuilderExt for ConfigBuilder { + #[cfg(feature = "rustls-native-certs")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))] + #[cfg_attr(not(feature = "logging"), allow(unused_variables))] + fn with_native_roots(self) -> ConfigBuilder { + let mut roots = rustls::RootCertStore::empty(); + let mut valid_count = 0; + let mut invalid_count = 0; + + for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") + { + let cert = rustls::Certificate(cert.0); + match roots.add(&cert) { + Ok(_) => valid_count += 1, + Err(err) => { + crate::log::trace!("invalid cert der {:?}", cert.0); + crate::log::debug!("certificate parsing failed: {:?}", err); + invalid_count += 1 + } + } + } + crate::log::debug!( + "with_native_roots processed {} valid and {} invalid certs", + valid_count, + invalid_count + ); + assert!(!roots.is_empty(), "no CA certificates found"); + + self.with_root_certificates(roots) + } + + #[cfg(feature = "webpki-roots")] + #[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))] + fn with_webpki_roots(self) -> ConfigBuilder { + let mut roots = rustls::RootCertStore::empty(); + roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { + rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); + self.with_root_certificates(roots) + } +} diff --git a/src/connector.rs b/src/connector.rs index deaa35c..e78d55d 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -1,18 +1,18 @@ -#[cfg(feature = "tokio-runtime")] -use hyper::client::connect::HttpConnector; -use hyper::{client::connect::Connection, service::Service, Uri}; -use rustls::ClientConfig; +use std::convert::TryFrom; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::{fmt, io}; + +use hyper::{client::connect::Connection, service::Service, Uri}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_rustls::TlsConnector; -use webpki::DNSNameRef; use crate::stream::MaybeHttpsStream; +pub mod builder; + type BoxError = Box; /// A Connector for the `https` scheme. @@ -20,69 +20,7 @@ type BoxError = Box; pub struct HttpsConnector { force_https: bool, http: T, - tls_config: Arc, -} - -#[cfg(all( - any(feature = "rustls-native-certs", feature = "webpki-roots"), - feature = "tokio-runtime" -))] -impl HttpsConnector { - /// Construct a new `HttpsConnector` using the OS root store - #[cfg(feature = "rustls-native-certs")] - #[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))] - pub fn with_native_roots() -> Self { - let mut config = ClientConfig::new(); - config.root_store = match rustls_native_certs::load_native_certs() { - Ok(store) => store, - Err((Some(store), err)) => { - log::warn!("Could not load all certificates: {:?}", err); - store - } - Err((None, err)) => Err(err).expect("cannot access native cert store"), - }; - if config.root_store.is_empty() { - panic!("no CA certificates found"); - } - Self::build(config) - } - - /// Construct a new `HttpsConnector` using the `webpki_roots` - #[cfg(feature = "webpki-roots")] - #[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))] - pub fn with_webpki_roots() -> Self { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - Self::build(config) - } - - /// Force the use of HTTPS when connecting. - /// - /// If a URL is not `https` when connecting, an error is returned. Disabled by default. - pub fn https_only(&mut self, enable: bool) { - self.force_https = enable; - } - - fn build(mut config: ClientConfig) -> Self { - let mut http = HttpConnector::new(); - http.enforce_http(false); - - config.alpn_protocols.clear(); - #[cfg(feature = "http2")] - { - config.alpn_protocols.push(b"h2".to_vec()); - } - - #[cfg(feature = "http1")] - { - config.alpn_protocols.push(b"http/1.1".to_vec()); - } - - config.ct_logs = Some(&ct_logs::LOGS); - (http, config).into() - } + tls_config: Arc, } impl fmt::Debug for HttpsConnector { @@ -95,7 +33,7 @@ impl fmt::Debug for HttpsConnector { impl From<(H, C)> for HttpsConnector where - C: Into>, + C: Into>, { fn from((http, cfg): (H, C)) -> Self { HttpsConnector { @@ -129,38 +67,43 @@ where } fn call(&mut self, dst: Uri) -> Self::Future { - let is_https = dst.scheme_str() == Some("https"); - - if !is_https && self.force_https { - // Early abort if HTTPS is forced but can't be used - let err = io::Error::new(io::ErrorKind::Other, "https required but URI was not https"); - Box::pin(async move { Err(err.into()) }) - } else if !is_https { - let connecting_future = self.http.call(dst); - - let f = async move { - let tcp = connecting_future.await.map_err(Into::into)?; - - Ok(MaybeHttpsStream::Http(tcp)) - }; - Box::pin(f) + // dst.scheme() would need to derive Eq to be matchable; + // use an if cascade instead + if let Some(sch) = dst.scheme() { + if sch == &http::uri::Scheme::HTTP && !self.force_https { + let connecting_future = self.http.call(dst); + + let f = async move { + let tcp = connecting_future.await.map_err(Into::into)?; + + Ok(MaybeHttpsStream::Http(tcp)) + }; + Box::pin(f) + } else if sch == &http::uri::Scheme::HTTPS { + let cfg = self.tls_config.clone(); + let hostname = dst.host().unwrap_or_default().to_string(); + let connecting_future = self.http.call(dst); + + let f = async move { + let tcp = connecting_future.await.map_err(Into::into)?; + let connector = TlsConnector::from(cfg); + let dnsname = rustls::ServerName::try_from(hostname.as_str()) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid dnsname"))?; + let tls = connector + .connect(dnsname, tcp) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + Ok(MaybeHttpsStream::Https(tls)) + }; + Box::pin(f) + } else { + let err = + io::Error::new(io::ErrorKind::Other, format!("Unsupported scheme {}", sch)); + Box::pin(async move { Err(err.into()) }) + } } else { - let cfg = self.tls_config.clone(); - let hostname = dst.host().unwrap_or_default().to_string(); - let connecting_future = self.http.call(dst); - - let f = async move { - let tcp = connecting_future.await.map_err(Into::into)?; - let connector = TlsConnector::from(cfg); - let dnsname = DNSNameRef::try_from_ascii_str(&hostname) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid dnsname"))?; - let tls = connector - .connect(dnsname, tcp) - .await - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - Ok(MaybeHttpsStream::Https(tls)) - }; - Box::pin(f) + let err = io::Error::new(io::ErrorKind::Other, "Missing scheme"); + Box::pin(async move { Err(err.into()) }) } } } diff --git a/src/connector/builder.rs b/src/connector/builder.rs new file mode 100644 index 0000000..de8ae3e --- /dev/null +++ b/src/connector/builder.rs @@ -0,0 +1,238 @@ +use rustls::ClientConfig; + +use super::HttpsConnector; +#[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))] +use crate::config::ConfigBuilderExt; + +#[cfg(feature = "tokio-runtime")] +use hyper::client::HttpConnector; + +/// A builder for an [`HttpsConnector`] +/// +/// This makes configuration flexible and explicit and ensures connector +/// features match crate features +/// +/// # Examples +/// +/// ``` +/// use hyper_rustls::HttpsConnectorBuilder; +/// +/// # #[cfg(all(feature = "webpki-roots", feature = "tokio-runtime", feature = "http1"))] +/// let https = HttpsConnectorBuilder::new() +/// .with_webpki_roots() +/// .https_only() +/// .enable_http1() +/// .build(); +/// ``` +pub struct ConnectorBuilder(State); + +/// State of a builder that needs a TLS client config next +pub struct WantsTlsConfig(()); + +impl ConnectorBuilder { + /// Creates a new [`ConnectorBuilder`] + pub fn new() -> Self { + Self(WantsTlsConfig(())) + } + + /// Passes a rustls [`ClientConfig`] to configure the TLS connection + /// + /// The [`alpn_protocols`](ClientConfig::alpn_protocols) field is + /// required to be empty (or the function will panic) and will be + /// rewritten to match the enabled schemes (see + /// [`enable_http1`](ConnectorBuilder::enable_http1), + /// [`enable_http2`](ConnectorBuilder::enable_http2)) before the + /// connector is built. + pub fn with_tls_config(self, config: ClientConfig) -> ConnectorBuilder { + assert!(config.alpn_protocols.is_empty()); + ConnectorBuilder(WantsSchemes { tls_config: config }) + } + + /// Shorthand for using rustls' [safe defaults][with_safe_defaults] + /// and native roots + /// + /// See [`ConfigBuilderExt::with_native_roots`] + /// + /// [with_safe_defaults]: rustls::ConfigBuilder::with_safe_defaults + #[cfg(feature = "rustls-native-certs")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))] + pub fn with_native_roots(self) -> ConnectorBuilder { + self.with_tls_config( + ClientConfig::builder() + .with_safe_defaults() + .with_native_roots() + .with_no_client_auth(), + ) + } + + /// Shorthand for using rustls' [safe defaults][with_safe_defaults] + /// and Mozilla roots + /// + /// See [`ConfigBuilderExt::with_webpki_roots`] + /// + /// [with_safe_defaults]: rustls::ConfigBuilder::with_safe_defaults + #[cfg(feature = "webpki-roots")] + #[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))] + pub fn with_webpki_roots(self) -> ConnectorBuilder { + self.with_tls_config( + ClientConfig::builder() + .with_safe_defaults() + .with_webpki_roots() + .with_no_client_auth(), + ) + } +} + +impl Default for ConnectorBuilder { + fn default() -> Self { + Self::new() + } +} + +/// State of a builder that needs schemes (https:// and http://) to be +/// configured next +pub struct WantsSchemes { + tls_config: ClientConfig, +} + +impl ConnectorBuilder { + /// Enforce the use of HTTPS when connecting + /// + /// Only URLs using the HTTPS scheme will be connectable. + pub fn https_only(self) -> ConnectorBuilder { + ConnectorBuilder(WantsProtocols1 { + tls_config: self.0.tls_config, + https_only: true, + }) + } + + /// Allow both HTTPS and HTTP when connecting + /// + /// HTTPS URLs will be handled through rustls, + /// HTTP URLs will be handled by the lower-level connector. + pub fn https_or_http(self) -> ConnectorBuilder { + ConnectorBuilder(WantsProtocols1 { + tls_config: self.0.tls_config, + https_only: false, + }) + } +} + +/// State of a builder that needs to have some protocols (HTTP1 or later) +/// enabled next +/// +/// No protocol has been enabled at this point. +pub struct WantsProtocols1 { + tls_config: ClientConfig, + https_only: bool, +} + +impl WantsProtocols1 { + fn wrap_connector(self, conn: H) -> HttpsConnector { + assert!(self.tls_config.alpn_protocols.is_empty()); + HttpsConnector { + force_https: self.https_only, + http: conn, + tls_config: std::sync::Arc::new(self.tls_config), + } + } + + #[cfg(feature = "tokio-runtime")] + fn build(self) -> HttpsConnector { + let mut http = HttpConnector::new(); + // HttpConnector won't enforce scheme, but HttpsConnector will + http.enforce_http(false); + self.wrap_connector(http) + } +} + +impl ConnectorBuilder { + /// Enable HTTP1 + /// + /// This needs to be called explicitly, no protocol is enabled by default + #[cfg(feature = "http1")] + pub fn enable_http1(self) -> ConnectorBuilder { + ConnectorBuilder(WantsProtocols2 { inner: self.0 }) + } + + /// Enable HTTP2 + /// + /// This needs to be called explicitly, no protocol is enabled by default + #[cfg(feature = "http2")] + pub fn enable_http2(mut self) -> ConnectorBuilder { + self.0.tls_config.alpn_protocols = vec![b"h2".to_vec()]; + ConnectorBuilder(WantsProtocols3 { + inner: self.0, + enable_http1: false, + }) + } +} + +/// State of a builder with HTTP1 enabled, that may have some other +/// protocols (HTTP2 or later) enabled next +/// +/// At this point a connector can be built, see +/// [`build`](ConnectorBuilder::build) and +/// [`wrap_connector`](ConnectorBuilder::wrap_connector). +pub struct WantsProtocols2 { + inner: WantsProtocols1, +} + +impl ConnectorBuilder { + /// Enable HTTP2 + /// + /// This needs to be called explicitly, no protocol is enabled by default + #[cfg(feature = "http2")] + pub fn enable_http2(mut self) -> ConnectorBuilder { + self.0.inner.tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + ConnectorBuilder(WantsProtocols3 { + inner: self.0.inner, + enable_http1: true, + }) + } + + /// This builds an [`HttpsConnector`] built on hyper's default [`HttpConnector`] + #[cfg(feature = "tokio-runtime")] + pub fn build(self) -> HttpsConnector { + self.0.inner.build() + } + + /// This wraps an arbitrary low-level connector into an [`HttpsConnector`] + pub fn wrap_connector(self, conn: H) -> HttpsConnector { + // HTTP1-only, alpn_protocols stays empty + // HttpConnector doesn't have a way to say http1-only; + // its connection pool may still support HTTP2 + // though it won't be used + self.0.inner.wrap_connector(conn) + } +} + +/// State of a builder with HTTP2 (and possibly HTTP1) enabled +/// +/// At this point a connector can be built, see +/// [`build`](ConnectorBuilder::build) and +/// [`wrap_connector`](ConnectorBuilder::wrap_connector). +#[cfg(feature = "http2")] +pub struct WantsProtocols3 { + inner: WantsProtocols1, + // ALPN is built piecemeal without the need to read back this field + #[allow(dead_code)] + enable_http1: bool, +} + +#[cfg(feature = "http2")] +impl ConnectorBuilder { + /// This builds an [`HttpsConnector`] built on hyper's default [`HttpConnector`] + #[cfg(feature = "tokio-runtime")] + pub fn build(self) -> HttpsConnector { + self.0.inner.build() + } + + /// This wraps an arbitrary low-level connector into an [`HttpsConnector`] + pub fn wrap_connector(self, conn: H) -> HttpsConnector { + // If HTTP1 is disabled, we can set http2_only + // on the Client (a higher-level object that uses the connector) + // client.http2_only(!self.0.enable_http1); + self.0.inner.wrap_connector(conn) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5374c06..9be896e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,61 @@ //! # hyper-rustls //! -//! A pure-Rust HTTPS connector for [hyper](https://hyper.rs), based on [Rustls](https://github.com/ctz/rustls). +//! A pure-Rust HTTPS connector for [hyper](https://hyper.rs), based on +//! [Rustls](https://github.com/ctz/rustls). //! //! ## Example //! //! ```no_run -//! # #[cfg(all(feature = "rustls-native-certs", feature = "tokio-runtime"))] +//! # #[cfg(all(feature = "rustls-native-certs", feature = "tokio-runtime", feature = "http1"))] //! # fn main() { //! use hyper::{Body, Client, StatusCode, Uri}; //! //! let mut rt = tokio::runtime::Runtime::new().unwrap(); //! let url = ("https://hyper.rs").parse().unwrap(); -//! let https = hyper_rustls::HttpsConnector::with_native_roots(); +//! let https = hyper_rustls::HttpsConnectorBuilder::new() +//! .with_native_roots() +//! .https_only() +//! .enable_http1() +//! .build(); //! //! let client: Client<_, hyper::Body> = Client::builder().build(https); //! //! let res = rt.block_on(client.get(url)).unwrap(); //! assert_eq!(res.status(), StatusCode::OK); //! # } -//! # #[cfg(not(all(feature = "rustls-native-certs", feature = "tokio-runtime")))] +//! # #[cfg(not(all(feature = "rustls-native-certs", feature = "tokio-runtime", feature = "http1")))] //! # fn main() {} //! ``` +#![warn(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg))] +mod config; mod connector; mod stream; +#[cfg(feature = "logging")] +mod log { + pub use log::{debug, trace}; +} + +#[cfg(not(feature = "logging"))] +mod log { + macro_rules! trace ( ($($tt:tt)*) => {{}} ); + macro_rules! debug ( ($($tt:tt)*) => {{}} ); + pub(crate) use {debug, trace}; +} + +pub use crate::config::ConfigBuilderExt; +pub use crate::connector::builder::ConnectorBuilder as HttpsConnectorBuilder; pub use crate::connector::HttpsConnector; pub use crate::stream::MaybeHttpsStream; + +/// The various states of the [`HttpsConnectorBuilder`] +pub mod builderstates { + #[cfg(feature = "http2")] + pub use crate::connector::builder::WantsProtocols3; + pub use crate::connector::builder::{ + WantsProtocols1, WantsProtocols2, WantsSchemes, WantsTlsConfig, + }; +} diff --git a/src/stream.rs b/src/stream.rs index ff5c094..96e544e 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -6,7 +6,6 @@ use std::task::{Context, Poll}; use hyper::client::connect::{Connected, Connection}; -use rustls::Session; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_rustls::client::TlsStream; @@ -24,7 +23,7 @@ impl Connection for MaybeHttpsSt MaybeHttpsStream::Http(s) => s.connected(), MaybeHttpsStream::Https(s) => { let (tcp, tls) = s.get_ref(); - if tls.get_alpn_protocol() == Some(b"h2") { + if tls.alpn_protocol() == Some(b"h2") { tcp.connected().negotiated_h2() } else { tcp.connected()