use std::{
io,
io::{Read, Write},
net::TcpStream,
};
use native_tls::{TlsConnector, TlsStream};
use url::Url;
#[derive(thiserror::Error, Debug)]
pub enum GopherError {
#[error("Invalid host")]
InvalidHost,
#[error("Unsupported protocol")]
UnsupportedProtocol,
#[error(transparent)]
HandshakeError(#[from] native_tls::HandshakeError<TcpStream>),
#[error(transparent)]
TlsError(#[from] native_tls::Error),
#[error(transparent)]
IoError(#[from] io::Error),
#[error(transparent)]
UrlParseError(#[from] url::ParseError),
}
pub struct Gopher {
host: String,
port: u16,
tls: bool,
}
impl Gopher {
pub fn new(endpoint: &str) -> Result<Self, GopherError> {
let url = Url::parse(endpoint)?;
if url.host().is_none() {
return Err(GopherError::InvalidHost);
}
let (host, tls) = match url.scheme() {
"gopher" => (url.host().unwrap(), false),
"gophers" => (url.host().unwrap(), true),
_ => return Err(GopherError::UnsupportedProtocol),
};
Ok(Self {
host: host.to_string(),
port: url.port().unwrap_or(70),
tls,
})
}
pub fn connect(&self) -> Result<GopherConnection, GopherError> {
let tcp_conn = TcpStream::connect(format!("{}:{}", self.host, self.port))?;
if !self.tls {
return Ok(GopherConnection::Tcp(tcp_conn));
}
let tls_conn = TlsConnector::new()?;
let stream = tls_conn.connect(&self.host, tcp_conn)?;
Ok(GopherConnection::Tls(stream))
}
}
pub enum GopherConnection {
Tcp(TcpStream),
Tls(TlsStream<TcpStream>),
}
impl Write for GopherConnection {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Self::Tcp(c) => c.write(buf),
Self::Tls(c) => c.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Self::Tcp(c) => c.flush(),
Self::Tls(c) => c.flush(),
}
}
}
impl Read for GopherConnection {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Tcp(c) => c.read(buf),
Self::Tls(c) => c.read(buf),
}
}
}
impl GopherConnection {
pub fn fetch(&mut self, path: &str) -> Result<Vec<u8>, io::Error> {
let req = format!("{}\r\n", path);
self.write_all(req.as_bytes())?;
let mut buf = vec![];
self.read_to_end(&mut buf)?;
Ok(buf)
}
}