Documentation
use std::{net::SocketAddr, sync::Arc};

use reqwest::{ClientBuilder, Response};
use rustls::{
  DigitallySignedStruct, SignatureScheme,
  client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
  pki_types::{CertificateDer, UnixTime},
};

#[derive(Debug)]
struct NoopServerCertVerifier;

impl ServerCertVerifier for NoopServerCertVerifier {
  fn verify_server_cert(
    &self,
    _end_entity: &CertificateDer<'_>,
    _intermediates: &[CertificateDer<'_>],
    _server_name: &rustls::pki_types::ServerName<'_>,
    _scts: &mut dyn Iterator<Item = &[u8]>,
    _ocsp_response: &[u8],
    _now: UnixTime,
  ) -> Result<ServerCertVerified, rustls::Error> {
    Ok(ServerCertVerified::assertion())
  }

  fn verify_handshake_signature(
    &self,
    _msg: &[u8],
    _cert: &CertificateDer<'_>,
    _dss: &DigitallySignedStruct,
  ) -> Result<HandshakeSignatureValid, rustls::Error> {
    Ok(HandshakeSignatureValid::assertion())
  }

  fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
    vec![
      SignatureScheme::RSA_PKCS1_SHA1,
      SignatureScheme::ECDSA_SHA1_Legacy,
      SignatureScheme::RSA_PKCS1_SHA256,
      SignatureScheme::ECDSA_NISTP256_SHA256,
      SignatureScheme::RSA_PKCS1_SHA384,
      SignatureScheme::ECDSA_NISTP384_SHA384,
      SignatureScheme::RSA_PKCS1_SHA512,
      SignatureScheme::ECDSA_NISTP521_SHA512,
      SignatureScheme::RSA_PSS_SHA256,
      SignatureScheme::RSA_PSS_SHA384,
      SignatureScheme::RSA_PSS_SHA512,
      SignatureScheme::ED25519,
      SignatureScheme::ED448,
    ]
  }
}

pub async fn get_with_builder(
  url_str: &str,
  addr: SocketAddr,
  builder: impl FnOnce(ClientBuilder) -> ClientBuilder,
) -> anyhow::Result<Response> {
  let url = url::Url::parse(url_str)?;
  let host = url
    .host_str()
    .ok_or_else(|| anyhow::anyhow!("URL does not have a host"))?;

  let client_config = rustls::ClientConfig::builder()
    .dangerous()
    .with_custom_certificate_verifier(Arc::new(NoopServerCertVerifier))
    .with_no_client_auth();

  let client_builder = builder(reqwest::Client::builder())
    .use_rustls_tls()
    .rustls_client_config(client_config);

  let client = client_builder
    .redirect(reqwest::redirect::Policy::none())
    .resolve(host, addr)
    .no_proxy()
    .build()?;

  client.get(url_str).send().await.map_err(Into::into)
}

#[allow(dead_code)]
pub async fn get(url_str: &str, addr: SocketAddr) -> anyhow::Result<Response> {
  get_with_builder(url_str, addr, |c| c).await
}

#[allow(dead_code)]
pub async fn get_body(url_str: &str, addr: SocketAddr) -> anyhow::Result<String> {
  let res = get(url_str, addr).await?;
  res.text().await.map_err(Into::into)
}

#[allow(dead_code)]
pub async fn get_body_h2(url_str: &str, addr: SocketAddr) -> anyhow::Result<String> {
  let res = get_with_builder(url_str, addr, |c| c.http2_prior_knowledge()).await?;
  res.text().await.map_err(Into::into)
}

#[allow(dead_code)]
pub async fn get_response_h2(url_str: &str, addr: SocketAddr) -> anyhow::Result<Response> {
  get_with_builder(url_str, addr, |c| c.http2_prior_knowledge()).await
}

#[allow(dead_code)]
pub async fn get_body_h3(url_str: &str, addr: SocketAddr) -> anyhow::Result<String> {
  let res = get_with_builder(url_str, addr, |c| c.http3_prior_knowledge()).await?;
  res.text().await.map_err(Into::into)
}

#[allow(dead_code)]
pub async fn get_response_h3(url_str: &str, addr: SocketAddr) -> anyhow::Result<Response> {
  get_with_builder(url_str, addr, |c| c.http3_prior_knowledge()).await
}