terrazzo-terminal 0.2.7

A simple web-based terminal emulator built on Terrazzo.
use std::sync::Arc;

use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
use tokio_rustls::client::TlsStream;
use tokio_rustls::rustls::ClientConfig;
use tokio_rustls::rustls::RootCertStore;
use tokio_rustls::rustls::client::Resumption;
use tokio_rustls::rustls::pki_types::ServerName;
use tokio_rustls::rustls::version::TLS12;
use tracing::debug;
use url::Url;

use self::buffered_stream::BufferedStream;
use super::AddConversionFn;

pub async fn add_tls_info(input: &str, add: &mut impl AddConversionFn) -> bool {
    add_tls_info_impl(input, add).await.is_ok()
}

async fn add_tls_info_impl(input: &str, add: &mut impl AddConversionFn) -> Result<(), ()> {
    let input = input.trim();
    let url = Url::parse(input).ignore_err("url")?;
    let host = url.host_str().ignore_err("host")?;
    let port = url.port_or_known_default().ignore_err("port")?;
    super::dns::add_dns_impl(host, add).await;

    let tcp = TcpStream::connect((host, port))
        .await
        .ignore_err("TCP connect")?;
    let mut tcp_buffered = BufferedStream::from(tcp);

    let tls: TlsStream<&mut BufferedStream> = {
        let mut root_store = RootCertStore::empty();
        root_store
            .add_parsable_certificates(rustls_native_certs::load_native_certs().certs.into_iter());

        let mut client_config = ClientConfig::builder_with_protocol_versions(&[&TLS12])
            .with_root_certificates(root_store)
            .with_no_client_auth();
        client_config.resumption = Resumption::disabled();

        let connector = TlsConnector::from(Arc::new(client_config));
        let server_name = ServerName::try_from(host)
            .ignore_err("server_name")?
            .to_owned();

        let tls_stream = connector
            .connect(server_name, &mut tcp_buffered)
            .await
            .ignore_err("TLS connect");
        if let Err(error) = tls_stream {
            drop(tls_stream);
            self::tls_handshake::add_tls_handshake("TLS Server", &tcp_buffered.read_buffer, add);
            self::tls_handshake::add_tls_handshake("TLS Client", &tcp_buffered.write_buffer, add);
            return Err(error)?;
        }
        tls_stream?
    };

    let (tcp_stream, session) = tls.get_ref();
    let certificates = session
        .peer_certificates()
        .ignore_err("peer_certificates")?;

    for certificate in certificates {
        super::x509::add_x509_base64(certificate.as_ref(), add);
    }

    self::tls_handshake::add_tls_handshake("TLS Server", &tcp_stream.read_buffer, add);
    self::tls_handshake::add_tls_handshake("TLS Client", &tcp_stream.write_buffer, add);

    Ok(())
}

trait IgnoreErr<T> {
    fn ignore_err(self, error: &'static str) -> Result<T, ()>;
}

impl<T, E> IgnoreErr<T> for Result<T, E> {
    fn ignore_err(self, error: &'static str) -> Result<T, ()> {
        self.map_err(|_| debug!("Failled to parse https TLS info: {error}"))
    }
}

impl<T> IgnoreErr<T> for Option<T> {
    fn ignore_err(self, error: &'static str) -> Result<T, ()> {
        match self {
            Some(v) => Ok(v),
            None => Err(()),
        }
        .ignore_err(error)
    }
}

mod buffered_stream;
mod indented_writer;
mod tls_handshake;