maj/src/client.rs

117 lines
3.1 KiB
Rust
Raw Normal View History

2020-07-25 16:39:10 +00:00
use crate::Response;
2020-07-27 22:14:45 +00:00
use std::{io::Cursor, sync::Arc};
2020-07-28 00:01:09 +00:00
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
use tokio_rustls::{
rustls::{TLSError},
TlsConnector,
};
2020-07-25 14:56:46 +00:00
use url::Url;
#[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),
2020-07-25 16:39:10 +00:00
#[error("Invalid URL scheme {0:?}")]
InvalidScheme(String),
2020-07-25 14:56:46 +00:00
}
2020-07-28 00:01:09 +00:00
pub async fn get<T>(u: T, cfg: tokio_rustls::rustls::ClientConfig) -> Result<crate::Response, Error>
2020-07-25 16:39:10 +00:00
where
T: Into<String>,
{
let u = u.into();
2020-07-25 14:56:46 +00:00
let mut ur = Url::parse(&u.clone())?;
if ur.port().is_none() {
ur.set_port(Some(1965)).unwrap();
}
2020-07-25 16:39:10 +00:00
if ur.scheme() != "gemini" {
return Err(Error::InvalidScheme(ur.scheme().to_string()));
}
2020-07-27 22:14:45 +00:00
let cfg = Arc::new(cfg);
2020-07-25 14:56:46 +00:00
let host = ur.host_str().unwrap();
2020-07-28 00:01:09 +00:00
let name_ref = webpki::DNSNameRef::try_from_ascii_str(host)?;
2020-07-27 22:14:45 +00:00
let config = TlsConnector::from(cfg);
let sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap())).await?;
2020-07-28 00:01:09 +00:00
let mut tls = config.connect(name_ref, sock).await?;
2020-07-25 14:56:46 +00:00
let req = format!("{}\r\n", u);
log::trace!("writing request {:?}", req);
2020-07-27 22:14:45 +00:00
tls.write(req.as_bytes()).await?;
let mut buf: Vec<u8> = vec![];
tls.read_to_end(&mut buf).await?;
Ok(Response::parse(&mut Cursor::new(buf))?)
2020-07-25 14:56:46 +00:00
}
#[cfg(test)]
mod tests {
2020-07-28 00:01:09 +00:00
use tokio_rustls::rustls;
2020-07-27 22:14:45 +00:00
fn config() -> rustls::ClientConfig {
let mut config = rustls::ClientConfig::new();
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
config
}
struct NoCertificateVerification {}
impl rustls::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: webpki::DNSNameRef<'_>,
_ocsp: &[u8],
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
2020-07-25 14:56:46 +00:00
use super::*;
2020-07-25 15:39:23 +00:00
2020-07-27 22:14:45 +00:00
#[tokio::test]
async fn gemini_homepage() -> Result<(), Error> {
let _ = pretty_env_logger::try_init();
let resp = get("gemini://gemini.circumlunar.space/".to_string(), config()).await?;
assert_eq!(resp.status, crate::StatusCode::Success);
assert_eq!(resp.meta, "text/gemini");
assert_ne!(resp.body.len(), 0);
Ok(())
}
#[tokio::test]
async fn gus() -> Result<(), Error> {
2020-07-25 14:56:46 +00:00
let _ = pretty_env_logger::try_init();
2020-07-27 22:14:45 +00:00
let resp = get("gemini://gus.guru/".to_string(), config()).await?;
2020-07-25 14:56:46 +00:00
2020-07-27 22:14:45 +00:00
assert_eq!(resp.status, crate::StatusCode::Success);
2020-07-25 14:56:46 +00:00
assert_eq!(resp.meta, "text/gemini");
assert_ne!(resp.body.len(), 0);
Ok(())
}
}