use crate::Response; use rustls::{ClientConfig, ClientSession, Stream, TLSError}; use std::{io::prelude::*, net::TcpStream, sync::Arc}; use url::Url; fn config() -> ClientConfig { let mut config = ClientConfig::new(); config .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); config } #[derive(thiserror::Error, Debug)] pub enum Error { #[error("TLS error: {0:?}")] TLS(#[from] TLSError), #[error("URL error: {0:?}")] URL(#[from] url::ParseError), #[error("Invalid DNS name: {0:?}")] InvalidDNSName(#[from] webpki::InvalidDNSNameError), #[error("IO error: {0:?}")] IO(#[from] std::io::Error), #[error("Response parsing error: {0:?}")] ResponseParse(#[from] crate::ResponseError), #[error("Invalid URL scheme {0:?}")] InvalidScheme(String), } pub fn get(u: T) -> Result where T: Into, { let u = u.into(); let mut ur = Url::parse(&u.clone())?; if ur.port().is_none() { ur.set_port(Some(1965)).unwrap(); } if ur.scheme() == "" { let _ = ur.set_scheme("gemini"); } if ur.scheme() != "gemini" { return Err(Error::InvalidScheme(ur.scheme().to_string())); } let cfg = Arc::new(config()); let host = ur.host_str().unwrap(); let mut sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap()))?; let name_ref = webpki::DNSNameRef::try_from_ascii_str(host)?; let mut client = ClientSession::new(&cfg, name_ref); let mut tls = Stream::new(&mut client, &mut sock); let req = format!("{}\r\n", u); log::trace!("writing request {:?}", req); tls.write(req.as_bytes())?; Ok(Response::parse(&mut tls)?) } #[cfg(test)] mod tests { use super::*; use crate::*; #[test] fn gemini_homepage() -> Result<(), Error> { let _ = pretty_env_logger::try_init(); let resp = get("gemini://gemini.circumlunar.space/".to_string())?; assert_eq!(resp.status, StatusCode::Success); assert_eq!(resp.meta, "text/gemini"); assert_ne!(resp.body.len(), 0); Ok(()) } }