use super::*;
use std::io::BufRead;
use std::io::{BufReader, Write};
use std::net::TcpStream;
use bytes::{Bytes, BytesMut, Buf, BufMut};
pub struct SyncClient {
#[cfg(feature = "with-rustls")]
client: BufReader<StreamOwned<ClientSession, TcpStream>>,
#[cfg(not(feature = "with-rustls"))]
client: BufReader<TcpStream>,
authorized: bool,
}
impl SyncClient {
pub fn connect(host: &str, port: u16) -> Result<Self> {
let mut client = TcpStream::connect((host, port))
.map(|client| Self {
client: BufReader::new(client),
authorized: false,
})
.map_err(Pop3Error::Io)?;
client.read_response(false)?;
Ok(client)
}
pub fn login(&mut self, username: &str, password: &str) -> Result<()> {
if self.authorized {
return Err(Pop3Error::AlreadyAuthenticated);
}
self.request(&Command::User { data: username })?;
self.request(&Command::Pass { data: password })
.map(|_| {
self.authorized = true;
()
})
}
pub fn quit(mut self) -> Result<()> {
self.request(&Command::Quit)
.map(|_| ())
}
pub fn stat(&mut self) -> Result<(u64, u64)> {
let stat = self.request(&Command::Stat)
.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 fn list(&mut self, id: Option<u64>) -> Result<Response> {
self.request(&Command::List { id })
}
pub fn retr(&mut self, id: u64) -> Result<Bytes> {
self.request(&Command::Retr { id })
.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 fn dele(&mut self, id: u64) -> Result<Response> {
self.request(&Command::Dele { id })
}
pub fn noop(&mut self) -> Result<()> {
self.request(&Command::Noop)
.map(|_| ())
}
pub fn rset(&mut self) -> Result<Response> {
self.request(&Command::Rset)
}
pub fn top(&mut self, id: u64, lines: u64) -> Result<Response> {
self.request(&Command::Top { id, lines })
}
pub fn uidl(&mut self, id: Option<u64>) -> Result<Response> {
self.request(&Command::Uidl { id })
}
pub fn apop(&mut self, id: &str, token: &str) -> Result<Response> {
if self.authorized {
return Err(Pop3Error::AlreadyAuthenticated);
}
self.request(&Command::Apop { id, token })
.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(Pop3Error::Io)
.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,
})
}
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)
.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)
.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()))
}
fn request(&mut self, cmd: &Command<'_>) -> Result<Response> {
self.client
.get_mut()
.write_all(cmd.to_request().as_bytes())
.map_err(Pop3Error::Io)?;
self.read_response(cmd.is_response_multiline())
}
}