use super::*;
use ::tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use ::tokio::net::TcpStream;
use bytes::{Bytes, BytesMut, Buf, BufMut};
#[cfg(feature = "with-rustls")]
use {
rustls::StreamOwned,
rustls::{ClientConfig, ClientSession},
std::sync::Arc,
webpki::DNSNameRef,
};
use crate::{Builder, Result};
pub struct AsyncClient {
#[cfg(feature = "with-rustls")]
client: BufReader<StreamOwned<ClientSession, TcpStream>>,
#[cfg(not(feature = "with-rustls"))]
client: BufReader<TcpStream>,
authorized: bool,
}
impl AsyncClient {
pub async fn connect(host: &str, port: u16) -> Result<Self> {
let mut client = TcpStream::connect((host, port))
.await
.map(|client| Self {
client: BufReader::new(client),
authorized: false,
})
.map_err(Pop3Error::Io)?;
client.read_response(false)
.await?;
Ok(client)
}
pub async fn login(&mut self, username: &str, password: &str) -> Result<()> {
if self.authorized {
return Err(Pop3Error::AlreadyAuthenticated);
}
self.request(&Command::User { data: username }).await?;
self.request(&Command::Pass { data: password })
.await
.map(|_| {
self.authorized = true;
()
})
}
pub async fn quit(mut self) -> Result<()> {
self.request(&Command::Quit)
.await
.map(|_| ())
}
pub async fn stat(&mut self) -> Result<(u64, u64)> {
let stat = self.request(&Command::Stat).await
.and_then(|r| r.to_string())?;
let mut s = stat
.trim()
.split(' ')
.map(|i| i.parse::<u64>().map_err(Pop3Error::InvalidNumber));
Ok((
s.next().ok_or(Pop3Error::InvalidResponse)??,
s.next().ok_or(Pop3Error::InvalidResponse)??,
))
}
pub async fn list(&mut self, id: Option<u64>) -> Result<Response> {
self.request(&Command::List { id }).await
}
pub async fn retr(&mut self, id: u64) -> Result<Bytes> {
self.request(&Command::Retr { id })
.await
.map(|s| {
let tmp = join_bytes(
&s.raw()[..]
.split(|&b| b == b'\n')
.skip(1)
.collect::<Vec<&[u8]>>(),
b'\n'
);
Bytes::copy_from_slice(&tmp)
})
}
pub async fn dele(&mut self, id: u64) -> Result<Response> {
self.request(&Command::Dele { id }).await
}
pub async fn noop(&mut self) -> Result<()> {
self.request(&Command::Noop)
.await
.map(|_| ())
}
pub async fn rset(&mut self) -> Result<Response> {
self.request(&Command::Rset).await
}
pub async fn top(&mut self, id: u64, lines: u64) -> Result<Response> {
self.request(&Command::Top { id, lines }).await
}
pub async fn uidl(&mut self, id: Option<u64>) -> Result<Response> {
self.request(&Command::Uidl { id }).await
}
pub async fn apop(&mut self, id: &str, token: &str) -> Result<Response> {
if self.authorized {
return Err(Pop3Error::AlreadyAuthenticated);
}
self.request(&Command::Apop { id, token })
.await
.map(|s| {
self.authorized = true;
s
})
}
#[cfg(feature = "with-rustls")]
fn connect_rustls(host: &str, port: u16, config: Arc<ClientConfig>) -> Result<Self> {
let hostname = DNSNameRef::try_from_ascii_str(host).map_err(|_| "DNS_NAMEREF_FAILED")?;
let session = ClientSession::new(&config, hostname);
let socket = TcpStream::connect((host, port))
.map(BufReader::new)
.map_err(|e| format!("{:?}", e))
.and_then(|mut client| {
let mut buf = String::new();
client
.read_line(&mut buf)
.map_err(|e| e.to_string())
.and_then(|_| {
if buf.starts_with("+OK") {
Ok(buf[4..].to_owned())
} else {
Err(buf[5..].to_owned())
}
})
.map(|_| client)
})
.and_then(|mut client| {
client
.get_mut()
.write_all("STLS\r\n".as_bytes())
.map_err(|e| e.to_string())
.and_then(|_| {
let mut buf = String::new();
client
.read_line(&mut buf)
.map_err(|e| e.to_string())
.and_then(|_| {
println!("STLS: {}", &buf);
if buf.starts_with("+OK") {
Ok(buf[4..].to_owned())
} else {
Err(buf[5..].to_owned())
}
})
})
.map(|_| client.into_inner())
})?;
let tls_stream = StreamOwned::new(session, socket);
Ok(Self {
client: BufReader::new(tls_stream),
authorized: false,
})
}
async fn read_response(&mut self, multiline: bool) -> Result<Response> {
let mut response = BytesMut::new();
let mut buffer = vec![];
let amount = self.client
.read_until(b'\n', &mut buffer)
.await
.map_err(Pop3Error::Io)?;
if amount == 0 {
return Err(Pop3Error::ConnectionClosed)
}
if buffer.starts_with(b"+OK") {
response.put(&buffer[4..]);
} else {
let error_msg = std::str::from_utf8(
if buffer.len() < 6 { &buffer } else { &buffer[5..] },
);
let err = match error_msg {
Ok(v) => Pop3Error::other(v),
Err(e) => Pop3Error::InvalidString(e),
};
return Err(err)
}
if multiline {
loop {
buffer.clear();
let amount = self.client
.read_until(b'\n', &mut buffer)
.await
.map_err(Pop3Error::Io)?;
if amount == 0 {
return Err(Pop3Error::ConnectionClosed)
}
if buffer == b".\r\n" {
break;
}
response.put(&buffer[..]);
}
}
Ok(Response::new(response.freeze()))
}
async fn request(&mut self, cmd: &Command<'_>) -> Result<Response> {
self.client
.get_mut()
.write_all(cmd.to_request().as_bytes())
.await
.map_err(Pop3Error::Io)?;
self.read_response(cmd.is_response_multiline())
.await
}
}