use core::pin::Pin;
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_rustls::rustls::pki_types::ServerName;
use super::{Target, tls_connector};
use crate::terminal_safe;
pub(crate) type ReplyStream = Pin<Box<dyn AsyncRead + Send>>;
pub(crate) async fn connect_and_send(
target: &Target,
request: &[u8],
) -> Result<ReplyStream, std::io::Error> {
let tcp = TcpStream::connect((target.host.as_str(), target.port)).await?;
if target.https {
let domain = ServerName::try_from(target.host.clone()).map_err(std::io::Error::other)?;
let mut tls = tls_connector()
.connect(domain, tcp)
.await
.map_err(peer_transport_error)?;
send(&mut tls, request).await?;
Ok(Box::pin(tls))
} else {
let mut tcp = tcp;
send(&mut tcp, request).await?;
Ok(Box::pin(tcp))
}
}
async fn send<S: AsyncWrite + Unpin>(stream: &mut S, request: &[u8]) -> Result<(), std::io::Error> {
stream.write_all(request).await?;
stream.flush().await
}
fn peer_transport_error(source: std::io::Error) -> std::io::Error {
std::io::Error::new(
source.kind(),
terminal_safe::sanitise(&source.to_string()).0,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_handshake_error_the_peer_worded_cannot_drive_a_terminal() {
let source = std::io::Error::new(
std::io::ErrorKind::InvalidData,
"certificate not valid for name \"evil\u{1b}[2J.example\u{7}\"",
);
let kept = peer_transport_error(source);
assert_eq!(kept.kind(), std::io::ErrorKind::InvalidData);
assert_eq!(
kept.to_string(),
"certificate not valid for name \"evil[2J.example\""
);
}
}