mod common;
pub use common::{CertVerdict, CertVerify, ProtocolVersion, VerifyCallback};
pub(crate) mod pc_roots;
pub(crate) mod client_auth;
pub(crate) use client_auth::{cipher_names_to_ids, parse_pinned_pubkey};
#[cfg(any(feature = "purecrypto-tls", feature = "rustls-tls"))]
mod settings;
#[cfg(any(feature = "purecrypto-tls", feature = "rustls-tls"))]
pub(crate) use settings::{proxy_tls_builder_methods, verify_peer_chain, TlsSettings};
#[cfg(all(test, any(feature = "purecrypto-tls", feature = "rustls-tls")))]
pub(crate) mod test_server;
#[cfg(feature = "rustls-tls")]
mod rustls;
#[cfg(feature = "rustls-tls")]
use rustls as backend;
#[cfg(all(feature = "purecrypto-tls", not(feature = "rustls-tls")))]
mod purecrypto;
#[cfg(all(feature = "purecrypto-tls", not(feature = "rustls-tls")))]
use purecrypto as backend;
#[cfg(not(any(feature = "purecrypto-tls", feature = "rustls-tls")))]
compile_error!(
"rsurl: no TLS backend enabled. Enable either `purecrypto-tls` \
(default) or `rustls-tls`."
);
#[cfg(any(feature = "purecrypto-tls", feature = "rustls-tls"))]
pub use backend::{
connect_over, connect_over_tls, connect_over_with_alpn, load_roots_from_dir,
load_roots_from_file, RootCertStore, TlsConn, TlsOpts, TlsSessionCache, TlsStream,
};
pub(crate) fn server_name(host: &str) -> &str {
let h = crate::url::unbracket(host);
match h.find('%') {
Some(i) if h.contains(':') => &h[..i],
_ => h,
}
}
pub(crate) fn reject_pipelined_plaintext(
proto: &str,
buffer_empty: bool,
) -> crate::error::Result<()> {
if buffer_empty {
Ok(())
} else {
Err(crate::error::Error::BadResponse(format!(
"{proto}: server pipelined plaintext before the TLS handshake (STARTTLS injection)"
)))
}
}
#[cfg(feature = "rustls-tls")]
pub(crate) fn build_client_engine(
sni: &str,
opts: &mut TlsOpts,
) -> crate::error::Result<crate::proto::tls::RustlsEngine> {
Ok(crate::proto::tls::RustlsEngine::new(
backend::build_client_conn(sni, opts)?,
))
}
#[cfg(all(feature = "purecrypto-tls", not(feature = "rustls-tls")))]
pub(crate) fn build_client_engine(
sni: &str,
opts: &mut TlsOpts,
) -> crate::error::Result<crate::proto::tls::PurecryptoEngine> {
crate::proto::tls::PurecryptoEngine::new(backend::build_client_conn(sni, opts)?)
}
#[cfg(feature = "rustls-tls")]
pub(crate) type ClientEngine = crate::proto::tls::RustlsEngine;
#[cfg(all(feature = "purecrypto-tls", not(feature = "rustls-tls")))]
pub(crate) type ClientEngine = crate::proto::tls::PurecryptoEngine;
#[cfg(test)]
mod tests {
use super::{reject_pipelined_plaintext, server_name};
use crate::error::Error;
#[test]
fn server_name_unbrackets_ipv6_and_drops_zone() {
assert_eq!(server_name("[::1]"), "::1");
assert_eq!(server_name("[fe80::1%25en0]"), "fe80::1");
assert_eq!(server_name("127.0.0.1"), "127.0.0.1");
assert_eq!(server_name("example.com"), "example.com");
}
#[test]
fn client_engine_accepts_bracketed_ipv6_host() {
for verify in [true, false] {
let mut opts = super::TlsOpts::verifying();
opts.verify = verify;
assert!(
super::build_client_engine("[::1]", &mut opts).is_ok(),
"verify={verify}"
);
}
}
#[test]
fn pipelined_guard_passes_on_empty_buffer() {
assert!(reject_pipelined_plaintext("imap", true).is_ok());
}
#[test]
fn pipelined_guard_rejects_buffered_plaintext() {
match reject_pipelined_plaintext("pop3", false) {
Err(Error::BadResponse(m)) => {
assert!(m.contains("pop3"));
assert!(m.contains("STARTTLS injection"));
}
other => panic!("expected BadResponse, got {other:?}"),
}
}
}