netget-tool 0.3.5

Network protocol stubbing toolkit: ~280 protocol servers and clients whose responses come from an LLM, a script, a static rule or a person.
use super::wire::Request;
use crate::protocol::ConnectContext;
use anyhow::{ensure, Context, Result};
use rustls::{pki_types::ServerName, ClientConfig, RootCertStore};
use std::sync::Arc;
#[derive(Clone)]
pub struct Config {
    pub tls: Arc<ClientConfig>,
    pub name: ServerName<'static>,
    pub host: String,
    pub port: u16,
}
impl Config {
    pub fn from_context(ctx: &ConnectContext) -> Result<Self> {
        let optional = |name| {
            ctx.startup_params
                .as_ref()
                .map(|p| p.get_optional_string(name))
                .transpose()
                .map(Option::flatten)
        };
        let remote = url::Url::parse(&format!("gemini://{}", ctx.remote_addr))
            .context("Invalid remote address")?;
        ensure!(
            remote.username().is_empty()
                && remote.password().is_none()
                && remote.path().is_empty()
                && remote.query().is_none()
                && remote.fragment().is_none(),
            "Remote address must contain only host:port"
        );
        let host = optional("server_name")?
            .unwrap_or_else(|| remote.host().map(|h| h.to_string()).unwrap_or_default());
        let host = host
            .trim_start_matches('[')
            .trim_end_matches(']')
            .to_string();
        let name = ServerName::try_from(host.clone()).context("Invalid TLS server name")?;
        let mut roots = RootCertStore::empty();
        if let Some(pem) = optional("custom_ca_cert_pem")? {
            ensure!(pem.len() <= 256 * 1024, "CA PEM exceeds256KiB");
            let certs = rustls_pemfile::certs(&mut pem.as_bytes())
                .collect::<std::result::Result<Vec<_>, _>>()?;
            ensure!(
                !certs.is_empty() && certs.len() <= 32,
                "Expected1..32 CA certificates"
            );
            for cert in certs {
                roots.add(cert)?;
            }
        } else {
            roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
        }
        let tls =
            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
                .with_safe_default_protocol_versions()?
                .with_root_certificates(roots)
                .with_no_client_auth();
        Ok(Self {
            tls: Arc::new(tls),
            name,
            host,
            port: remote.port().unwrap_or(1965),
        })
    }
    pub fn validate(&self, r: &Request) -> Result<()> {
        let host = r.url.host().context("Missing URL host")?.to_string();
        ensure!(host.trim_start_matches('[').trim_end_matches(']').eq_ignore_ascii_case(&self.host)&&r.url.port().unwrap_or(1965)==self.port,"Request URL must match the configured server_name and remote port; open another client for another endpoint");
        Ok(())
    }
}