Skip to content

CI and test fixes #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,12 @@ stop-token = { version = "0.1.1", features = ["unstable"] }
byte-pool = "0.2.1"
lazy_static = "1.4.0"
log = "0.4.8"
thiserror = "1.0.9"

[dev-dependencies]
lettre = "0.9"
lettre_email = "0.9"
rustls-connector = "0.8.0"
rustls = { version = "0.16.0", features = ["dangerous_configuration"] }
webpki = "0.21.0"
pretty_assertions = "0.6.1"
native-tls = "0.2.3"
async-smtp = "0.2.0"

[[example]]
name = "basic"
Expand Down
2 changes: 1 addition & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ impl<T: Read + Write + Unpin + fmt::Debug> Client<T> {
let challenge = if let Some(text) = information {
ok_or_unauth_client_err!(
base64::decode(text).map_err(|e| Error::Parse(
ParseError::Authentication(text.to_string(), Some(e))
ParseError::Authentication((*text).to_string(), Some(e))
)),
self
)
Expand Down
139 changes: 23 additions & 116 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,159 +1,66 @@
//! IMAP error types.
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::result;
use std::str::Utf8Error;

use base64::DecodeError;
use imap_proto::Response;

/// A convenience wrapper around `Result` for `imap::Error`.
pub type Result<T> = result::Result<T, Error>;

/// A set of errors that can occur in the IMAP client
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
pub enum Error {
/// An `io::Error` that occurred while trying to read or write to a network stream.
Io(IoError),
#[error("io: {0}")]
Io(#[from] IoError),
/// A BAD response from the IMAP server.
#[error("bad response: {0}")]
Bad(String),
/// A NO response from the IMAP server.
#[error("no response: {0}")]
No(String),
/// The connection was terminated unexpectedly.
#[error("connection lost")]
ConnectionLost,
/// Error parsing a server response.
Parse(ParseError),
#[error("parse: {0}")]
Parse(#[from] ParseError),
/// Command inputs were not valid [IMAP
/// strings](https://tools.ietf.org/html/rfc3501#section-4.3).
Validate(ValidateError),
/// `native_tls` error
NativeTlsError(async_native_tls::Error),
#[error("validate: {0}")]
Validate(#[from] ValidateError),
/// `async_native_tls` error
#[error("async_native_tls: {0}")]
NativeTlsError(#[from] async_native_tls::Error),
/// Error appending an e-mail.
#[error("could not append mail to mailbox")]
Append,
#[doc(hidden)]
#[error("unknown")]
__Nonexhaustive,
}

impl From<IoError> for Error {
fn from(err: IoError) -> Error {
Error::Io(err)
}
}

impl From<ParseError> for Error {
fn from(err: ParseError) -> Error {
Error::Parse(err)
}
}

impl<'a> From<&'a Response<'a>> for Error {
fn from(err: &'a Response<'a>) -> Error {
Error::Parse(ParseError::Unexpected(format!("{:?}", err)))
}
}

impl From<async_native_tls::Error> for Error {
fn from(err: async_native_tls::Error) -> Error {
Error::NativeTlsError(err)
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Io(ref e) => fmt::Display::fmt(e, f),
Error::Validate(ref e) => fmt::Display::fmt(e, f),
Error::No(ref data) | Error::Bad(ref data) => {
write!(f, "{}: {}", &String::from(self.description()), data)
}
ref e => f.write_str(e.description()),
}
}
}

impl StdError for Error {
fn description(&self) -> &str {
match *self {
Error::Io(ref e) => e.description(),
Error::Parse(ref e) => e.description(),
Error::Validate(ref e) => e.description(),
Error::NativeTlsError(ref e) => e.description(),
Error::Bad(_) => "Bad Response",
Error::No(_) => "No Response",
Error::ConnectionLost => "Connection lost",
Error::Append => "Could not append mail to mailbox",
Error::__Nonexhaustive => "Unknown",
}
}

fn cause(&self) -> Option<&dyn StdError> {
match *self {
Error::Io(ref e) => Some(e),
Error::Parse(ParseError::DataNotUtf8(_, ref e)) => Some(e),
_ => None,
}
}
}

/// An error occured while trying to parse a server response.
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
pub enum ParseError {
/// Indicates an error parsing the status response. Such as OK, NO, and BAD.
#[error("unable to parse status response")]
Invalid(Vec<u8>),
/// An unexpected response was encountered.
#[error("encountered unexpected parsed response: {0}")]
Unexpected(String),
/// The client could not find or decode the server's authentication challenge.
#[error("unable to parse authentication response: {0} - {1:?}")]
Authentication(String, Option<DecodeError>),
/// The client received data that was not UTF-8 encoded.
DataNotUtf8(Vec<u8>, Utf8Error),
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ref e => f.write_str(e.description()),
}
}
}

impl StdError for ParseError {
fn description(&self) -> &str {
match *self {
ParseError::Invalid(_) => "Unable to parse status response",
ParseError::Unexpected(_) => "Encountered unexpected parsed response",
ParseError::Authentication(_, _) => "Unable to parse authentication response",
ParseError::DataNotUtf8(_, _) => "Unable to parse data as UTF-8 text",
}
}

fn cause(&self) -> Option<&dyn StdError> {
match *self {
ParseError::Authentication(_, Some(ref e)) => Some(e),
_ => None,
}
}
#[error("unable to parse data ({0:?}) as UTF-8 text: {1:?}")]
DataNotUtf8(Vec<u8>, #[source] Utf8Error),
}

/// An [invalid character](https://tools.ietf.org/html/rfc3501#section-4.3) was found in an input
/// string.
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
#[error("invalid character in input: '{0}'")]
pub struct ValidateError(pub char);

impl fmt::Display for ValidateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// print character in debug form because invalid ones are often whitespaces
write!(f, "{}: {:?}", self.description(), self.0)
}
}

impl StdError for ValidateError {
fn description(&self) -> &str {
"Invalid character in input"
}

fn cause(&self) -> Option<&dyn StdError> {
None
}
}
76 changes: 31 additions & 45 deletions tests/imap_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,11 @@ use async_native_tls::TlsConnector;
use async_std::net::TcpStream;
use async_std::prelude::*;
use async_std::task;
use lettre::Transport;

fn native_tls() -> native_tls::TlsConnector {
native_tls::TlsConnector::builder()
fn native_tls() -> async_native_tls::TlsConnector {
async_native_tls::TlsConnector::new()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()
.unwrap()
}

pub 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())
}
}

fn tls() -> TlsConnector {
Expand Down Expand Up @@ -54,21 +37,23 @@ async fn session(user: &str) -> Session<async_native_tls::TlsStream<TcpStream>>
s
}

fn smtp(user: &str) -> lettre::SmtpTransport {
let creds = lettre::smtp::authentication::Credentials::new(user.to_string(), user.to_string());
lettre::SmtpClient::new(
async fn smtp(user: &str) -> async_smtp::SmtpTransport {
let creds =
async_smtp::smtp::authentication::Credentials::new(user.to_string(), user.to_string());
async_smtp::SmtpClient::with_security(
&format!(
"{}:3465",
std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string())
),
lettre::ClientSecurity::Wrapper(lettre::ClientTlsParameters {
async_smtp::ClientSecurity::Wrapper(async_smtp::ClientTlsParameters {
connector: native_tls(),
domain: "smpt.example.com".to_string(),
domain: "localhost".to_string(),
}),
)
.unwrap()
.await
.expect("Failed to connect to smtp server")
.credentials(creds)
.transport()
.into_transport()
}

// #[test]
Expand Down Expand Up @@ -130,6 +115,18 @@ fn inbox_zero() {
assert_eq!(inbox.len(), 0);
});
}
fn make_email(to: &str) -> async_smtp::SendableEmail {
let message_id = "abc";
async_smtp::SendableEmail::new(
async_smtp::Envelope::new(
Some("sender@localhost".parse().unwrap()),
vec![to.parse().unwrap()],
)
.unwrap(),
message_id.to_string(),
format!("To: <{}>\r\nFrom: <sender@localhost>\r\nMessage-ID: <{}.msg@localhost>\r\nSubject: My first e-mail\r\n\r\nHello world from SMTP", to, message_id),
)
}

#[test]
#[ignore]
Expand All @@ -142,16 +139,11 @@ fn inbox() {
c.select("INBOX").await.unwrap();

println!("sending");
let mut s = smtp(to).await;

// then send the e-mail
let mut s = smtp(to);
let e = lettre_email::Email::builder()
.from("sender@localhost")
.to(to)
.subject("My first e-mail")
.text("Hello world from SMTP")
.build()
.unwrap();
s.send(e.into()).unwrap();
let mail = make_email(to);
s.connect_and_send(mail).await.unwrap();

println!("searching");

Expand Down Expand Up @@ -191,7 +183,7 @@ fn inbox() {
let fetch = &fetch[0];
assert_eq!(fetch.message, 1);
assert_ne!(fetch.uid, None);
assert_eq!(fetch.size, Some(138));
assert_eq!(fetch.size, Some(21));
let e = fetch.envelope().unwrap();
assert_eq!(e.subject, Some(&b"My first e-mail"[..]));
assert_ne!(e.from, None);
Expand Down Expand Up @@ -232,15 +224,9 @@ fn inbox_uid() {
c.select("INBOX").await.unwrap();

// then send the e-mail
let mut s = smtp(to);
let e = lettre_email::Email::builder()
.from("sender@localhost")
.to(to)
.subject("My first e-mail")
.text("Hello world from SMTP")
.build()
.unwrap();
s.send(e.into()).unwrap();
let mut s = smtp(to).await;
let e = make_email(to);
s.connect_and_send(e).await.unwrap();

// now we should see the e-mail!
let inbox = c.uid_search("ALL").await.unwrap();
Expand Down