diff --git a/lettre/benches/transport_smtp.rs b/lettre/benches/transport_smtp.rs index 7a1a556..560acbe 100644 --- a/lettre/benches/transport_smtp.rs +++ b/lettre/benches/transport_smtp.rs @@ -9,17 +9,19 @@ use lettre::smtp::ConnectionReuseParameters; #[bench] fn bench_simple_send(b: &mut test::Bencher) { - let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None).unwrap() - .build(); + let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None) + .unwrap() + .build(); b.iter(|| { - let email = - SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "id".to_string(), - "Hello world".to_string()); - let result = sender.send(&email); - assert!(result.is_ok()); - }); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "id".to_string(), + "Hello world".to_string(), + ); + let result = sender.send(&email); + assert!(result.is_ok()); + }); } #[bench] @@ -29,13 +31,14 @@ fn bench_reuse_send(b: &mut test::Bencher) { .connection_reuse(ConnectionReuseParameters::ReuseUnlimited) .build(); b.iter(|| { - let email = - SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "id".to_string(), - "Hello world".to_string()); - let result = sender.send(&email); - assert!(result.is_ok()); - }); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "id".to_string(), + "Hello world".to_string(), + ); + let result = sender.send(&email); + assert!(result.is_ok()); + }); sender.close() } diff --git a/lettre/examples/smtp.rs b/lettre/examples/smtp.rs index cac97ce..2d585a2 100644 --- a/lettre/examples/smtp.rs +++ b/lettre/examples/smtp.rs @@ -6,14 +6,17 @@ use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail, SmtpTransport}; fn main() { env_logger::init(); - let email = SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "file_id".to_string(), - "Hello ß☺ example".to_string()); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "file_id".to_string(), + "Hello ß☺ example".to_string(), + ); // Open a local connection on port 25 - let mut mailer = SmtpTransport::builder_unencrypted_localhost().unwrap() - .build(); + let mut mailer = SmtpTransport::builder_unencrypted_localhost() + .unwrap() + .build(); // Send the email let result = mailer.send(&email); diff --git a/lettre/src/file/mod.rs b/lettre/src/file/mod.rs index d394c8b..0d3fcbb 100644 --- a/lettre/src/file/mod.rs +++ b/lettre/src/file/mod.rs @@ -40,10 +40,12 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, FileResult> for FileEmailTransport let mut message_content = String::new(); let _ = email.message().read_to_string(&mut message_content); - let simple_email = SimpleSendableEmail::new(email.from().clone(), - email.to().clone(), - email.message_id().clone(), - message_content); + let simple_email = SimpleSendableEmail::new( + email.from().clone(), + email.to().clone(), + email.message_id().clone(), + message_content, + ); f.write_all(serde_json::to_string(&simple_email)?.as_bytes())?; diff --git a/lettre/src/lib.rs b/lettre/src/lib.rs index 95a3a92..0f609b0 100644 --- a/lettre/src/lib.rs +++ b/lettre/src/lib.rs @@ -101,15 +101,18 @@ pub struct SimpleSendableEmail { impl SimpleSendableEmail { /// Returns a new email - pub fn new(from_address: EmailAddress, - to_addresses: Vec, - message_id: String, - message: String) - -> SimpleSendableEmail { - SimpleSendableEmail { from: from_address, - to: to_addresses, - message_id: message_id, - message: message.into_bytes(), } + pub fn new( + from_address: EmailAddress, + to_addresses: Vec, + message_id: String, + message: String, + ) -> SimpleSendableEmail { + SimpleSendableEmail { + from: from_address, + to: to_addresses, + message_id, + message: message.into_bytes(), + } } } diff --git a/lettre/src/sendmail/mod.rs b/lettre/src/sendmail/mod.rs index 86ebc9e..03dcb26 100644 --- a/lettre/src/sendmail/mod.rs +++ b/lettre/src/sendmail/mod.rs @@ -18,12 +18,16 @@ pub struct SendmailTransport { impl SendmailTransport { /// Creates a new transport with the default `/usr/sbin/sendmail` command pub fn new() -> SendmailTransport { - SendmailTransport { command: "/usr/sbin/sendmail".to_string(), } + SendmailTransport { + command: "/usr/sbin/sendmail".to_string(), + } } /// Creates a new transport to the given sendmail command pub fn new_with_command>(command: S) -> SendmailTransport { - SendmailTransport { command: command.into(), } + SendmailTransport { + command: command.into(), + } } } @@ -31,21 +35,25 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, SendmailResult> for SendmailTranspo fn send + 'a>(&mut self, email: &'a U) -> SendmailResult { // Spawn the sendmail command let to_addresses: Vec = email.to().iter().map(|x| x.to_string()).collect(); - let mut process = Command::new(&self.command).args(&["-i", - "-f", - &email.from().to_string(), - &to_addresses.join(" ")]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; + let mut process = Command::new(&self.command) + .args(&[ + "-i", + "-f", + &email.from().to_string(), + &to_addresses.join(" "), + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; let mut message_content = String::new(); let _ = email.message().read_to_string(&mut message_content); - match process.stdin - .as_mut() - .unwrap() - .write_all(message_content.as_bytes()) + match process + .stdin + .as_mut() + .unwrap() + .write_all(message_content.as_bytes()) { Ok(_) => (), Err(error) => return Err(From::from(error)), diff --git a/lettre/src/smtp/authentication.rs b/lettre/src/smtp/authentication.rs index ce2e17a..7d8886d 100644 --- a/lettre/src/smtp/authentication.rs +++ b/lettre/src/smtp/authentication.rs @@ -59,8 +59,7 @@ pub struct Credentials { impl Credentials { /// Create a `Credentials` struct from username and password pub fn new(username: String, password: String) -> Credentials { - Credentials { username: username, - password: password, } + Credentials { username, password } } } @@ -82,12 +81,16 @@ pub enum Mechanism { impl Display for Mechanism { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", match *self { - Mechanism::Plain => "PLAIN", - Mechanism::Login => "LOGIN", - #[cfg(feature = "crammd5-auth")] - Mechanism::CramMd5 => "CRAM-MD5", - }) + write!( + f, + "{}", + match *self { + Mechanism::Plain => "PLAIN", + Mechanism::Login => "LOGIN", + #[cfg(feature = "crammd5-auth")] + Mechanism::CramMd5 => "CRAM-MD5", + } + ) } } @@ -105,23 +108,19 @@ impl Mechanism { /// Returns the string to send to the server, using the provided username, password and /// challenge in some cases - pub fn response(&self, - credentials: &Credentials, - challenge: Option<&str>) - -> Result { + pub fn response( + &self, + credentials: &Credentials, + challenge: Option<&str>, + ) -> Result { match *self { - Mechanism::Plain => { - match challenge { - Some(_) => Err(Error::Client("This mechanism does not expect a challenge")), - None => { - Ok(format!("{}{}{}{}", - NUL, - credentials.username, - NUL, - credentials.password)) - } - } - } + Mechanism::Plain => match challenge { + Some(_) => Err(Error::Client("This mechanism does not expect a challenge")), + None => Ok(format!( + "{}{}{}{}", + NUL, credentials.username, NUL, credentials.password + )), + }, Mechanism::Login => { let decoded_challenge = match challenge { Some(challenge) => challenge, @@ -148,9 +147,11 @@ impl Mechanism { let mut hmac = Hmac::new(Md5::new(), credentials.password.as_bytes()); hmac.input(decoded_challenge.as_bytes()); - Ok(format!("{} {}", - credentials.username, - hex::encode(hmac.result().code()))) + Ok(format!( + "{} {}", + credentials.username, + hex::encode(hmac.result().code()) + )) } } } @@ -166,8 +167,10 @@ mod test { let credentials = Credentials::new("username".to_string(), "password".to_string()); - assert_eq!(mechanism.response(&credentials, None).unwrap(), - "\u{0}username\u{0}password"); + assert_eq!( + mechanism.response(&credentials, None).unwrap(), + "\u{0}username\u{0}password" + ); assert!(mechanism.response(&credentials, Some("test")).is_err()); } @@ -177,10 +180,14 @@ mod test { let credentials = Credentials::new("alice".to_string(), "wonderland".to_string()); - assert_eq!(mechanism.response(&credentials, Some("Username")).unwrap(), - "alice"); - assert_eq!(mechanism.response(&credentials, Some("Password")).unwrap(), - "wonderland"); + assert_eq!( + mechanism.response(&credentials, Some("Username")).unwrap(), + "alice" + ); + assert_eq!( + mechanism.response(&credentials, Some("Password")).unwrap(), + "wonderland" + ); assert!(mechanism.response(&credentials, None).is_err()); } diff --git a/lettre/src/smtp/client/mock.rs b/lettre/src/smtp/client/mock.rs index 8104a4c..e7989af 100644 --- a/lettre/src/smtp/client/mock.rs +++ b/lettre/src/smtp/client/mock.rs @@ -20,13 +20,17 @@ impl Default for MockStream { impl MockStream { pub fn new() -> MockStream { - MockStream { reader: Arc::new(Mutex::new(MockCursor::new(Vec::new()))), - writer: Arc::new(Mutex::new(MockCursor::new(Vec::new()))), } + MockStream { + reader: Arc::new(Mutex::new(MockCursor::new(Vec::new()))), + writer: Arc::new(Mutex::new(MockCursor::new(Vec::new()))), + } } pub fn with_vec(vec: Vec) -> MockStream { - MockStream { reader: Arc::new(Mutex::new(MockCursor::new(vec))), - writer: Arc::new(Mutex::new(MockCursor::new(Vec::new()))), } + MockStream { + reader: Arc::new(Mutex::new(MockCursor::new(vec))), + writer: Arc::new(Mutex::new(MockCursor::new(Vec::new()))), + } } pub fn take_vec(&mut self) -> Vec { diff --git a/lettre/src/smtp/client/mod.rs b/lettre/src/smtp/client/mod.rs index 9b8564d..944c976 100644 --- a/lettre/src/smtp/client/mod.rs +++ b/lettre/src/smtp/client/mod.rs @@ -139,10 +139,11 @@ impl Client { } /// Connects to the configured server - pub fn connect(&mut self, - addr: &A, - tls_parameters: Option<&ClientTlsParameters>) - -> SmtpResult { + pub fn connect( + &mut self, + addr: &A, + tls_parameters: Option<&ClientTlsParameters>, + ) -> SmtpResult { // Connect should not be called when the client is already connected if self.stream.is_some() { return_err!("The connection is already established", self); @@ -177,9 +178,11 @@ impl Client { while challenges > 0 && response.has_code(334) { challenges -= 1; - response = self.command(AuthCommand::new_from_response(mechanism, - credentials.clone(), - &response)?)?; + response = self.command(AuthCommand::new_from_response( + mechanism, + credentials.clone(), + &response, + )?)?; } if challenges == 0 { @@ -233,8 +236,10 @@ impl Client { self.stream.as_mut().unwrap().write_all(string)?; self.stream.as_mut().unwrap().flush()?; - debug!("Wrote: {}", - escape_crlf(String::from_utf8_lossy(string).as_ref())); + debug!( + "Wrote: {}", + escape_crlf(String::from_utf8_lossy(string).as_ref()) + ); Ok(()) } @@ -282,15 +287,19 @@ mod test { assert!(codec.encode(b"test\n", &mut buf).is_ok()); assert!(codec.encode(b".test\n", &mut buf).is_ok()); assert!(codec.encode(b"test", &mut buf).is_ok()); - assert_eq!(String::from_utf8(buf).unwrap(), - "test\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest"); + assert_eq!( + String::from_utf8(buf).unwrap(), + "test\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest" + ); } #[test] fn test_escape_crlf() { assert_eq!(escape_crlf("\r\n"), ""); assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name"); - assert_eq!(escape_crlf("EHLO my_name\r\nSIZE 42\r\n"), - "EHLO my_nameSIZE 42"); + assert_eq!( + escape_crlf("EHLO my_name\r\nSIZE 42\r\n"), + "EHLO my_nameSIZE 42" + ); } } diff --git a/lettre/src/smtp/client/net.rs b/lettre/src/smtp/client/net.rs index a0863b7..c89c5ce 100644 --- a/lettre/src/smtp/client/net.rs +++ b/lettre/src/smtp/client/net.rs @@ -18,8 +18,7 @@ pub struct ClientTlsParameters { impl ClientTlsParameters { /// Creates a `ClientTlsParameters` pub fn new(domain: String, connector: TlsConnector) -> ClientTlsParameters { - ClientTlsParameters { connector: connector, - domain: domain, } + ClientTlsParameters { connector, domain } } } @@ -44,13 +43,10 @@ impl NetworkStream { match *self { NetworkStream::Tcp(ref s) => s.peer_addr(), NetworkStream::Tls(ref s) => s.get_ref().peer_addr(), - NetworkStream::Mock(_) => { - Ok(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, - 0, - 0, - 1), - 80))) - } + NetworkStream::Mock(_) => Ok(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(127, 0, 0, 1), + 80, + ))), } } @@ -96,7 +92,7 @@ impl Write for NetworkStream { pub trait Connector: Sized { /// Opens a connection to the given IP socket fn connect(addr: &SocketAddr, tls_parameters: Option<&ClientTlsParameters>) - -> io::Result; + -> io::Result; /// Upgrades to TLS connection fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()>; /// Is the NetworkStream encrypted @@ -104,18 +100,18 @@ pub trait Connector: Sized { } impl Connector for NetworkStream { - fn connect(addr: &SocketAddr, - tls_parameters: Option<&ClientTlsParameters>) - -> io::Result { + fn connect( + addr: &SocketAddr, + tls_parameters: Option<&ClientTlsParameters>, + ) -> io::Result { let tcp_stream = TcpStream::connect(addr)?; match tls_parameters { - Some(context) => { - context.connector - .connect(context.domain.as_ref(), tcp_stream) - .map(NetworkStream::Tls) - .map_err(|e| io::Error::new(ErrorKind::Other, e)) - } + Some(context) => context + .connector + .connect(context.domain.as_ref(), tcp_stream) + .map(NetworkStream::Tls) + .map_err(|e| io::Error::new(ErrorKind::Other, e)), None => Ok(NetworkStream::Tcp(tcp_stream)), } } @@ -123,15 +119,13 @@ impl Connector for NetworkStream { #[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))] fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> { *self = match *self { - NetworkStream::Tcp(ref mut stream) => { - match tls_parameters.connector - .connect(tls_parameters.domain.as_ref(), - stream.try_clone().unwrap()) - { - Ok(tls_stream) => NetworkStream::Tls(tls_stream), - Err(err) => return Err(io::Error::new(ErrorKind::Other, err)), - } - } + NetworkStream::Tcp(ref mut stream) => match tls_parameters + .connector + .connect(tls_parameters.domain.as_ref(), stream.try_clone().unwrap()) + { + Ok(tls_stream) => NetworkStream::Tls(tls_stream), + Err(err) => return Err(io::Error::new(ErrorKind::Other, err)), + }, NetworkStream::Tls(_) => return Ok(()), NetworkStream::Mock(_) => return Ok(()), }; diff --git a/lettre/src/smtp/commands.rs b/lettre/src/smtp/commands.rs index 818fbf9..357133f 100644 --- a/lettre/src/smtp/commands.rs +++ b/lettre/src/smtp/commands.rs @@ -26,7 +26,7 @@ impl Display for EhloCommand { impl EhloCommand { /// Creates a EHLO command pub fn new(client_id: ClientId) -> EhloCommand { - EhloCommand { client_id: client_id, } + EhloCommand { client_id } } } @@ -44,16 +44,20 @@ impl Display for StarttlsCommand { /// MAIL command #[derive(PartialEq, Clone, Debug)] pub struct MailCommand { - sender: Option, + sender: Option, parameters: Vec, } impl Display for MailCommand { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "MAIL FROM:<{}>", match self.sender { - Some(ref address) => address.to_string(), - None => "".to_string(), - })?; + write!( + f, + "MAIL FROM:<{}>", + match self.sender { + Some(ref address) => address.to_string(), + None => "".to_string(), + } + )?; for parameter in &self.parameters { write!(f, " {}", parameter)?; } @@ -64,15 +68,14 @@ impl Display for MailCommand { impl MailCommand { /// Creates a MAIL command pub fn new(sender: Option, parameters: Vec) -> MailCommand { - MailCommand { sender: sender, - parameters: parameters, } + MailCommand { sender, parameters } } } /// RCPT command #[derive(PartialEq, Clone, Debug)] pub struct RcptCommand { - recipient: EmailAddress, + recipient: EmailAddress, parameters: Vec, } @@ -89,8 +92,10 @@ impl Display for RcptCommand { impl RcptCommand { /// Creates an RCPT command pub fn new(recipient: EmailAddress, parameters: Vec) -> RcptCommand { - RcptCommand { recipient: recipient, - parameters: parameters, } + RcptCommand { + recipient, + parameters, + } } } @@ -146,7 +151,7 @@ impl Display for HelpCommand { impl HelpCommand { /// Creates an HELP command pub fn new(argument: Option) -> HelpCommand { - HelpCommand { argument: argument } + HelpCommand { argument } } } @@ -166,7 +171,7 @@ impl Display for VrfyCommand { impl VrfyCommand { /// Creates a VRFY command pub fn new(argument: String) -> VrfyCommand { - VrfyCommand { argument: argument } + VrfyCommand { argument } } } @@ -186,7 +191,7 @@ impl Display for ExpnCommand { impl ExpnCommand { /// Creates an EXPN command pub fn new(argument: String) -> ExpnCommand { - ExpnCommand { argument: argument } + ExpnCommand { argument } } } @@ -204,17 +209,19 @@ impl Display for RsetCommand { /// AUTH command #[derive(PartialEq, Clone, Debug)] pub struct AuthCommand { - mechanism: Mechanism, + mechanism: Mechanism, credentials: Credentials, - challenge: Option, - response: Option, + challenge: Option, + response: Option, } impl Display for AuthCommand { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let encoded_response = if self.response.is_some() { - Some(base64::encode_config(self.response.as_ref().unwrap().as_bytes(), - base64::STANDARD)) + Some(base64::encode_config( + self.response.as_ref().unwrap().as_bytes(), + base64::STANDARD, + )) } else { None }; @@ -233,27 +240,31 @@ impl Display for AuthCommand { impl AuthCommand { /// Creates an AUTH command (from a challenge if provided) - pub fn new(mechanism: Mechanism, - credentials: Credentials, - challenge: Option) - -> Result { + pub fn new( + mechanism: Mechanism, + credentials: Credentials, + challenge: Option, + ) -> Result { let response = if mechanism.supports_initial_response() || challenge.is_some() { Some(mechanism.response(&credentials, challenge.as_ref().map(String::as_str))?) } else { None }; - Ok(AuthCommand { mechanism: mechanism, - credentials: credentials, - challenge: challenge, - response: response, }) + Ok(AuthCommand { + mechanism, + credentials, + challenge, + response, + }) } /// Creates an AUTH command from a response that needs to be a /// valid challenge (with 334 response code) - pub fn new_from_response(mechanism: Mechanism, - credentials: Credentials, - response: &Response) - -> Result { + pub fn new_from_response( + mechanism: Mechanism, + credentials: Credentials, + response: &Response, + ) -> Result { if !response.has_code(334) { return Err(Error::ResponseParsing("Expecting a challenge")); } @@ -266,12 +277,10 @@ impl AuthCommand { debug!("auth encoded challenge: {}", encoded_challenge); let decoded_challenge = match base64::decode(&encoded_challenge) { - Ok(challenge) => { - match String::from_utf8(challenge) { - Ok(value) => value, - Err(error) => return Err(Error::Utf8Parsing(error)), - } - } + Ok(challenge) => match String::from_utf8(challenge) { + Ok(value) => value, + Err(error) => return Err(Error::Utf8Parsing(error)), + }, Err(error) => return Err(Error::ChallengeParsing(error)), }; @@ -279,10 +288,12 @@ impl AuthCommand { let response = Some(mechanism.response(&credentials, Some(decoded_challenge.as_ref()))?); - Ok(AuthCommand { mechanism: mechanism, - credentials: credentials, - challenge: Some(decoded_challenge), - response: response, }) + Ok(AuthCommand { + mechanism, + credentials, + challenge: Some(decoded_challenge), + response, + }) } } @@ -297,49 +308,77 @@ mod test { fn test_display() { let id = ClientId::Domain("localhost".to_string()); let email = EmailAddress::new("test@example.com".to_string()); - let mail_parameter = MailParameter::Other { keyword: "TEST".to_string(), - value: Some("value".to_string()), }; - let rcpt_parameter = RcptParameter::Other { keyword: "TEST".to_string(), - value: Some("value".to_string()), }; + let mail_parameter = MailParameter::Other { + keyword: "TEST".to_string(), + value: Some("value".to_string()), + }; + let rcpt_parameter = RcptParameter::Other { + keyword: "TEST".to_string(), + value: Some("value".to_string()), + }; assert_eq!(format!("{}", EhloCommand::new(id)), "EHLO localhost\r\n"); - assert_eq!(format!("{}", MailCommand::new(Some(email.clone()), vec![])), - "MAIL FROM:\r\n"); - assert_eq!(format!("{}", MailCommand::new(None, vec![])), - "MAIL FROM:<>\r\n"); - assert_eq!(format!("{}", - MailCommand::new(Some(email.clone()), vec![MailParameter::Size(42)])), - "MAIL FROM: SIZE=42\r\n"); + assert_eq!( + format!("{}", MailCommand::new(Some(email.clone()), vec![])), + "MAIL FROM:\r\n" + ); + assert_eq!( + format!("{}", MailCommand::new(None, vec![])), + "MAIL FROM:<>\r\n" + ); + assert_eq!( + format!( + "{}", + MailCommand::new(Some(email.clone()), vec![MailParameter::Size(42)]) + ), + "MAIL FROM: SIZE=42\r\n" + ); assert_eq!( format!( "{}", MailCommand::new( Some(email.clone()), - vec![MailParameter::Size(42), - MailParameter::Body(MailBodyParameter::EightBitMime), - mail_parameter], + vec![ + MailParameter::Size(42), + MailParameter::Body(MailBodyParameter::EightBitMime), + mail_parameter, + ], ) ), "MAIL FROM: SIZE=42 BODY=8BITMIME TEST=value\r\n" ); - assert_eq!(format!("{}", RcptCommand::new(email.clone(), vec![])), - "RCPT TO:\r\n"); - assert_eq!(format!("{}", RcptCommand::new(email.clone(), vec![rcpt_parameter])), - "RCPT TO: TEST=value\r\n"); + assert_eq!( + format!("{}", RcptCommand::new(email.clone(), vec![])), + "RCPT TO:\r\n" + ); + assert_eq!( + format!("{}", RcptCommand::new(email.clone(), vec![rcpt_parameter])), + "RCPT TO: TEST=value\r\n" + ); assert_eq!(format!("{}", QuitCommand), "QUIT\r\n"); assert_eq!(format!("{}", DataCommand), "DATA\r\n"); assert_eq!(format!("{}", NoopCommand), "NOOP\r\n"); assert_eq!(format!("{}", HelpCommand::new(None)), "HELP\r\n"); - assert_eq!(format!("{}", HelpCommand::new(Some("test".to_string()))), - "HELP test\r\n"); - assert_eq!(format!("{}", VrfyCommand::new("test".to_string())), - "VRFY test\r\n"); - assert_eq!(format!("{}", ExpnCommand::new("test".to_string())), - "EXPN test\r\n"); + assert_eq!( + format!("{}", HelpCommand::new(Some("test".to_string()))), + "HELP test\r\n" + ); + assert_eq!( + format!("{}", VrfyCommand::new("test".to_string())), + "VRFY test\r\n" + ); + assert_eq!( + format!("{}", ExpnCommand::new("test".to_string())), + "EXPN test\r\n" + ); assert_eq!(format!("{}", RsetCommand), "RSET\r\n"); let credentials = Credentials::new("user".to_string(), "password".to_string()); - assert_eq!(format!("{}", - AuthCommand::new(Mechanism::Plain, credentials.clone(), None).unwrap()), - "AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=\r\n"); + assert_eq!( + format!( + "{}", + AuthCommand::new(Mechanism::Plain, credentials.clone(), None).unwrap() + ), + "AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=\r\n" + ); #[cfg(feature = "crammd5-auth")] assert_eq!( format!( @@ -352,9 +391,13 @@ mod test { ), "dXNlciAzMTYxY2NmZDdmMjNlMzJiYmMzZTQ4NjdmYzk0YjE4Nw==\r\n" ); - assert_eq!(format!("{}", - AuthCommand::new(Mechanism::Login, credentials.clone(), None).unwrap()), - "AUTH LOGIN\r\n"); + assert_eq!( + format!( + "{}", + AuthCommand::new(Mechanism::Login, credentials.clone(), None).unwrap() + ), + "AUTH LOGIN\r\n" + ); #[cfg(feature = "crammd5-auth")] assert_eq!( format!( diff --git a/lettre/src/smtp/error.rs b/lettre/src/smtp/error.rs index b05b904..c2e592f 100644 --- a/lettre/src/smtp/error.rs +++ b/lettre/src/smtp/error.rs @@ -52,18 +52,14 @@ impl StdError for Error { match *self { // Try to display the first line of the server's response that usually // contains a short humanly readable error message - Transient(ref err) => { - match err.first_line() { - Some(line) => line, - None => "undetailed transient error during SMTP transaction", - } - } - Permanent(ref err) => { - match err.first_line() { - Some(line) => line, - None => "undetailed permanent error during SMTP transaction", - } - } + Transient(ref err) => match err.first_line() { + Some(line) => line, + None => "undetailed transient error during SMTP transaction", + }, + Permanent(ref err) => match err.first_line() { + Some(line) => line, + None => "undetailed permanent error during SMTP transaction", + }, ResponseParsing(err) => err, ChallengeParsing(ref err) => err.description(), Utf8Parsing(ref err) => err.description(), diff --git a/lettre/src/smtp/extension.rs b/lettre/src/smtp/extension.rs index 5b5b8bd..3a06d8f 100644 --- a/lettre/src/smtp/extension.rs +++ b/lettre/src/smtp/extension.rs @@ -44,9 +44,9 @@ impl ClientId { /// found pub fn hostname() -> ClientId { ClientId::Domain(match get_hostname() { - Some(name) => name, - None => DEFAULT_EHLO_HOSTNAME.to_string(), - }) + Some(name) => name, + None => DEFAULT_EHLO_HOSTNAME.to_string(), + }) } } @@ -95,14 +95,16 @@ pub struct ServerInfo { impl Display for ServerInfo { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, - "{} with {}", - self.name, - if self.features.is_empty() { - "no supported features".to_string() - } else { - format!("{:?}", self.features) - }) + write!( + f, + "{} with {}", + self.name, + if self.features.is_empty() { + "no supported features".to_string() + } else { + format!("{:?}", self.features) + } + ) } } @@ -132,29 +134,29 @@ impl ServerInfo { "STARTTLS" => { features.insert(Extension::StartTls); } - "AUTH" => { - for &mechanism in &splitted[1..] { - match mechanism { - "PLAIN" => { - features.insert(Extension::Authentication(Mechanism::Plain)); - } - "LOGIN" => { - features.insert(Extension::Authentication(Mechanism::Login)); - } - #[cfg(feature = "crammd5-auth")] - "CRAM-MD5" => { - features.insert(Extension::Authentication(Mechanism::CramMd5)); - } - _ => (), + "AUTH" => for &mechanism in &splitted[1..] { + match mechanism { + "PLAIN" => { + features.insert(Extension::Authentication(Mechanism::Plain)); } + "LOGIN" => { + features.insert(Extension::Authentication(Mechanism::Login)); + } + #[cfg(feature = "crammd5-auth")] + "CRAM-MD5" => { + features.insert(Extension::Authentication(Mechanism::CramMd5)); + } + _ => (), } - } + }, _ => (), }; } - Ok(ServerInfo { name: name.to_string(), - features: features, }) + Ok(ServerInfo { + name: name.to_string(), + features, + }) } /// Checks if the server supports an ESMTP feature @@ -164,7 +166,8 @@ impl ServerInfo { /// Checks if the server supports an ESMTP feature pub fn supports_auth_mechanism(&self, mechanism: Mechanism) -> bool { - self.features.contains(&Extension::Authentication(mechanism)) + self.features + .contains(&Extension::Authentication(mechanism)) } } @@ -192,12 +195,14 @@ impl Display for MailParameter { MailParameter::Body(ref value) => write!(f, "BODY={}", value), MailParameter::Size(size) => write!(f, "SIZE={}", size), MailParameter::SmtpUtfEight => f.write_str("SMTPUTF8"), - MailParameter::Other { ref keyword, - value: Some(ref value), } => { - write!(f, "{}={}", keyword, XText(value)) - } - MailParameter::Other { ref keyword, - value: None, } => f.write_str(keyword), + MailParameter::Other { + ref keyword, + value: Some(ref value), + } => write!(f, "{}={}", keyword, XText(value)), + MailParameter::Other { + ref keyword, + value: None, + } => f.write_str(keyword), } } } @@ -235,12 +240,14 @@ pub enum RcptParameter { impl Display for RcptParameter { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { - RcptParameter::Other { ref keyword, - value: Some(ref value), } => { - write!(f, "{}={}", keyword, XText(value)) - } - RcptParameter::Other { ref keyword, - value: None, } => f.write_str(keyword), + RcptParameter::Other { + ref keyword, + value: Some(ref value), + } => write!(f, "{}={}", keyword, XText(value)), + RcptParameter::Other { + ref keyword, + value: None, + } => f.write_str(keyword), } } } @@ -255,16 +262,22 @@ mod test { #[test] fn test_clientid_fmt() { - assert_eq!(format!("{}", ClientId::new("test".to_string())), - "test".to_string()); + assert_eq!( + format!("{}", ClientId::new("test".to_string())), + "test".to_string() + ); } #[test] fn test_extension_fmt() { - assert_eq!(format!("{}", Extension::EightBitMime), - "8BITMIME".to_string()); - assert_eq!(format!("{}", Extension::Authentication(Mechanism::Plain)), - "AUTH PLAIN".to_string()); + assert_eq!( + format!("{}", Extension::EightBitMime), + "8BITMIME".to_string() + ); + assert_eq!( + format!("{}", Extension::Authentication(Mechanism::Plain)), + "AUTH PLAIN".to_string() + ); } #[test] @@ -272,41 +285,67 @@ mod test { let mut eightbitmime = HashSet::new(); assert!(eightbitmime.insert(Extension::EightBitMime)); - assert_eq!(format!("{}", - ServerInfo { name: "name".to_string(), - features: eightbitmime.clone(), }), - "name with {EightBitMime}".to_string()); + assert_eq!( + format!( + "{}", + ServerInfo { + name: "name".to_string(), + features: eightbitmime.clone(), + } + ), + "name with {EightBitMime}".to_string() + ); let empty = HashSet::new(); - assert_eq!(format!("{}", - ServerInfo { name: "name".to_string(), - features: empty, }), - "name with no supported features".to_string()); + assert_eq!( + format!( + "{}", + ServerInfo { + name: "name".to_string(), + features: empty, + } + ), + "name with no supported features".to_string() + ); let mut plain = HashSet::new(); assert!(plain.insert(Extension::Authentication(Mechanism::Plain))); - assert_eq!(format!("{}", - ServerInfo { name: "name".to_string(), - features: plain.clone(), }), - "name with {Authentication(Plain)}".to_string()); + assert_eq!( + format!( + "{}", + ServerInfo { + name: "name".to_string(), + features: plain.clone(), + } + ), + "name with {Authentication(Plain)}".to_string() + ); } #[test] fn test_serverinfo() { - let response = Response::new(Code::new(Severity::PositiveCompletion, - Category::Unspecified4, - Detail::One), - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]); + let response = Response::new( + Code::new( + Severity::PositiveCompletion, + Category::Unspecified4, + Detail::One, + ), + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ); let mut features = HashSet::new(); assert!(features.insert(Extension::EightBitMime)); - let server_info = ServerInfo { name: "me".to_string(), - features: features, }; + let server_info = ServerInfo { + name: "me".to_string(), + features: features, + }; assert_eq!(ServerInfo::from_response(&response).unwrap(), server_info); @@ -315,13 +354,19 @@ mod test { #[cfg(feature = "crammd5-auth")] assert!(!server_info.supports_auth_mechanism(Mechanism::CramMd5)); - let response2 = Response::new(Code::new(Severity::PositiveCompletion, - Category::Unspecified4, - Detail::One), - vec!["me".to_string(), - "AUTH PLAIN CRAM-MD5 OTHER".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]); + let response2 = Response::new( + Code::new( + Severity::PositiveCompletion, + Category::Unspecified4, + Detail::One, + ), + vec![ + "me".to_string(), + "AUTH PLAIN CRAM-MD5 OTHER".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ); let mut features2 = HashSet::new(); assert!(features2.insert(Extension::EightBitMime)); @@ -329,8 +374,10 @@ mod test { #[cfg(feature = "crammd5-auth")] assert!(features2.insert(Extension::Authentication(Mechanism::CramMd5),)); - let server_info2 = ServerInfo { name: "me".to_string(), - features: features2, }; + let server_info2 = ServerInfo { + name: "me".to_string(), + features: features2, + }; assert_eq!(ServerInfo::from_response(&response2).unwrap(), server_info2); diff --git a/lettre/src/smtp/mod.rs b/lettre/src/smtp/mod.rs index 1e821bb..2eded34 100644 --- a/lettre/src/smtp/mod.rs +++ b/lettre/src/smtp/mod.rs @@ -120,22 +120,23 @@ impl SmtpTransportBuilder { /// * No authentication /// * No SMTPUTF8 support /// * A 60 seconds timeout for smtp commands - pub fn new(addr: A, - security: ClientSecurity) - -> Result { + pub fn new( + addr: A, + security: ClientSecurity, + ) -> Result { let mut addresses = addr.to_socket_addrs()?; match addresses.next() { - Some(addr) => { - Ok(SmtpTransportBuilder { server_addr: addr, - security: security, - smtp_utf8: false, - credentials: None, - connection_reuse: ConnectionReuseParameters::NoReuse, - hello_name: ClientId::hostname(), - authentication_mechanism: None, - timeout: Some(Duration::new(60, 0)), }) - } + Some(addr) => Ok(SmtpTransportBuilder { + server_addr: addr, + security, + smtp_utf8: false, + credentials: None, + connection_reuse: ConnectionReuseParameters::NoReuse, + hello_name: ClientId::hostname(), + authentication_mechanism: None, + timeout: Some(Duration::new(60, 0)), + }), None => Err(Error::Resolution), } } @@ -153,9 +154,10 @@ impl SmtpTransportBuilder { } /// Enable connection reuse - pub fn connection_reuse(mut self, - parameters: ConnectionReuseParameters) - -> SmtpTransportBuilder { + pub fn connection_reuse( + mut self, + parameters: ConnectionReuseParameters, + ) -> SmtpTransportBuilder { self.connection_reuse = parameters; self } @@ -234,14 +236,17 @@ impl<'a> SmtpTransport { let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_builder.build().unwrap()); - SmtpTransportBuilder::new((domain, SUBMISSION_PORT), - ClientSecurity::Required(tls_parameters)) + SmtpTransportBuilder::new( + (domain, SUBMISSION_PORT), + ClientSecurity::Required(tls_parameters), + ) } /// Creates a new configurable builder - pub fn builder(addr: A, - security: ClientSecurity) - -> Result { + pub fn builder( + addr: A, + security: ClientSecurity, + ) -> Result { SmtpTransportBuilder::new(addr, security) } @@ -256,20 +261,24 @@ impl<'a> SmtpTransport { pub fn new(builder: SmtpTransportBuilder) -> SmtpTransport { let client = Client::new(); - SmtpTransport { client: client, + SmtpTransport { + client, server_info: None, client_info: builder, - state: State { panic: false, - connection_reuse_count: 0, }, } + state: State { + panic: false, + connection_reuse_count: 0, + }, + } } /// Gets the EHLO response and updates server information fn ehlo(&mut self) -> SmtpResult { // Extended Hello let ehlo_response = try_smtp!( - self.client.command(EhloCommand::new( - ClientId::new(self.client_info.hello_name.to_string()), - )), + self.client.command(EhloCommand::new(ClientId::new( + self.client_info.hello_name.to_string() + ),)), self ); @@ -306,13 +315,13 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, SmtpResult> for SmtpTransport { } if self.state.connection_reuse_count == 0 { - self.client.connect(&self.client_info.server_addr, - match self.client_info.security { - ClientSecurity::Wrapper(ref tls_parameters) => { - Some(tls_parameters) - } - _ => None, - })?; + self.client.connect( + &self.client_info.server_addr, + match self.client_info.security { + ClientSecurity::Wrapper(ref tls_parameters) => Some(tls_parameters), + _ => None, + }, + )?; self.client.set_timeout(self.client_info.timeout)?; @@ -321,11 +330,13 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, SmtpResult> for SmtpTransport { self.ehlo()?; - match (&self.client_info.security.clone(), - self.server_info.as_ref() - .unwrap() - .supports_feature(Extension::StartTls)) - { + match ( + &self.client_info.security.clone(), + self.server_info + .as_ref() + .unwrap() + .supports_feature(Extension::StartTls), + ) { (&ClientSecurity::Required(_), false) => { return Err(From::from("Could not encrypt connection, aborting")) } @@ -360,9 +371,10 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, SmtpResult> for SmtpTransport { }; for mechanism in accepted_mechanisms { - if self.server_info.as_ref() - .unwrap() - .supports_auth_mechanism(mechanism) + if self.server_info + .as_ref() + .unwrap() + .supports_auth_mechanism(mechanism) { found = true; try_smtp!( @@ -383,30 +395,38 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, SmtpResult> for SmtpTransport { // Mail let mut mail_options = vec![]; - if self.server_info.as_ref() - .unwrap() - .supports_feature(Extension::EightBitMime) + if self.server_info + .as_ref() + .unwrap() + .supports_feature(Extension::EightBitMime) { mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); } - if self.server_info.as_ref() - .unwrap() - .supports_feature(Extension::SmtpUtfEight) && self.client_info.smtp_utf8 + if self.server_info + .as_ref() + .unwrap() + .supports_feature(Extension::SmtpUtfEight) && self.client_info.smtp_utf8 { mail_options.push(MailParameter::SmtpUtfEight); } - try_smtp!(self.client.command(MailCommand::new(Some(email.from().clone()), mail_options,)), - self); + try_smtp!( + self.client + .command(MailCommand::new(Some(email.from().clone()), mail_options,)), + self + ); // Log the mail command info!("{}: from=<{}>", message_id, email.from()); // Recipient for to_address in &email.to() { - try_smtp!(self.client.command(RcptCommand::new(to_address.clone(), vec![]),), - self); + try_smtp!( + self.client + .command(RcptCommand::new(to_address.clone(), vec![]),), + self + ); // Log the rcpt command info!("{}: to=<{}>", message_id, to_address); } @@ -422,16 +442,19 @@ impl<'a, T: Read + 'a> EmailTransport<'a, T, SmtpResult> for SmtpTransport { self.state.connection_reuse_count += 1; // Log the message - info!("{}: conn_use={}, status=sent ({})", - message_id, - self.state.connection_reuse_count, - result.as_ref() - .ok() - .unwrap() - .message - .iter() - .next() - .unwrap_or(&"no response".to_string())); + info!( + "{}: conn_use={}, status=sent ({})", + message_id, + self.state.connection_reuse_count, + result + .as_ref() + .ok() + .unwrap() + .message + .iter() + .next() + .unwrap_or(&"no response".to_string()) + ); } // Test if we can reuse the existing connection diff --git a/lettre/src/smtp/response.rs b/lettre/src/smtp/response.rs index ef3c3e6..d632a70 100644 --- a/lettre/src/smtp/response.rs +++ b/lettre/src/smtp/response.rs @@ -91,9 +91,11 @@ impl Display for Code { impl Code { /// Creates a new `Code` structure pub fn new(severity: Severity, category: Category, detail: Detail) -> Code { - Code { severity: severity, - category: category, - detail: detail, } + Code { + severity, + category, + detail, + } } } @@ -124,8 +126,7 @@ impl FromStr for Response { impl Response { /// Creates a new `Response` pub fn new(code: Code, message: Vec) -> Response { - Response { code: code, - message: message, } + Response { code, message } } /// Tells if the response is positive @@ -143,7 +144,8 @@ impl Response { /// Returns only the first word of the message if possible pub fn first_word(&self) -> Option<&str> { - self.message.get(0) + self.message + .get(0) .and_then(|line| line.split_whitespace().next()) } @@ -155,11 +157,17 @@ impl Response { // Parsers (originaly from tokio-smtp) -named!(parse_code, - map!(tuple!(parse_severity, parse_category, parse_detail), - |(severity, category, detail)| Code { severity: severity, - category: category, - detail: detail, })); +named!( + parse_code, + map!( + tuple!(parse_severity, parse_category, parse_detail), + |(severity, category, detail)| Code { + severity, + category, + detail, + } + ) +); named!( parse_severity, @@ -199,34 +207,47 @@ named!( ) ); -named!(parse_response, - map_res!(tuple!(// Parse any number of continuation lines. - many0!(tuple!(parse_code, - preceded!(char!('-'), - take_until_and_consume!(b"\r\n".as_ref())))), - // Parse the final line. - tuple!(parse_code, - terminated!(opt!(preceded!(char!(' '), - take_until!(b"\r\n".as_ref()))), - crlf))), - |(lines, (last_code, last_line)): (Vec<_>, _)| { - // Check that all codes are equal. - if !lines.iter().all(|&(ref code, _)| *code == last_code) { - return Err(()); - } +named!( + parse_response, + map_res!( + tuple!( + // Parse any number of continuation lines. + many0!(tuple!( + parse_code, + preceded!(char!('-'), take_until_and_consume!(b"\r\n".as_ref())) + )), + // Parse the final line. + tuple!( + parse_code, + terminated!( + opt!(preceded!(char!(' '), take_until!(b"\r\n".as_ref()))), + crlf + ) + ) + ), + |(lines, (last_code, last_line)): (Vec<_>, _)| { + // Check that all codes are equal. + if !lines.iter().all(|&(ref code, _)| *code == last_code) { + return Err(()); + } - // Extract text from lines, and append last line. - let mut lines = lines.into_iter().map(|(_, text)| text).collect::>(); - if let Some(text) = last_line { - lines.push(text); - } + // Extract text from lines, and append last line. + let mut lines = lines.into_iter().map(|(_, text)| text).collect::>(); + if let Some(text) = last_line { + lines.push(text); + } - Ok(Response { code: last_code, - message: lines.into_iter() - .map(|line| from_utf8(line).map(|s| s.to_string())) - .collect::, _>>() - .map_err(|_| ())?, }) - })); + Ok(Response { + code: last_code, + message: lines + .into_iter() + .map(|line| from_utf8(line).map(|s| s.to_string())) + .collect::, _>>() + .map_err(|_| ())?, + }) + } + ) +); #[cfg(test)] mod test { @@ -244,19 +265,27 @@ mod test { #[test] fn test_code_new() { - assert_eq!(Code::new(Severity::TransientNegativeCompletion, - Category::Connections, - Detail::Zero,), - Code { severity: Severity::TransientNegativeCompletion, - category: Category::Connections, - detail: Detail::Zero, }); + assert_eq!( + Code::new( + Severity::TransientNegativeCompletion, + Category::Connections, + Detail::Zero, + ), + Code { + severity: Severity::TransientNegativeCompletion, + category: Category::Connections, + detail: Detail::Zero, + } + ); } #[test] fn test_code_display() { - let code = Code { severity: Severity::TransientNegativeCompletion, + let code = Code { + severity: Severity::TransientNegativeCompletion, category: Category::Connections, - detail: Detail::One, }; + detail: Detail::One, + }; assert_eq!(code.to_string(), "421"); } @@ -264,14 +293,22 @@ mod test { #[test] fn test_response_from_str() { let raw_response = "250-me\r\n250-8BITMIME\r\n250-SIZE 42\r\n250 AUTH PLAIN CRAM-MD5\r\n"; - assert_eq!(raw_response.parse::().unwrap(), - Response { code: Code { severity: Severity::PositiveCompletion, - category: Category::MailSystem, - detail: Detail::Zero, }, - message: vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string(), - "AUTH PLAIN CRAM-MD5".to_string()], }); + assert_eq!( + raw_response.parse::().unwrap(), + Response { + code: Code { + severity: Severity::PositiveCompletion, + category: Category::MailSystem, + detail: Detail::Zero, + }, + message: vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + "AUTH PLAIN CRAM-MD5".to_string(), + ], + } + ); let wrong_code = "2506-me\r\n250-8BITMIME\r\n250-SIZE 42\r\n250 AUTH PLAIN CRAM-MD5\r\n"; assert!(wrong_code.parse::().is_err()); diff --git a/lettre/src/smtp/util.rs b/lettre/src/smtp/util.rs index 0f6abe5..06e5593 100644 --- a/lettre/src/smtp/util.rs +++ b/lettre/src/smtp/util.rs @@ -33,11 +33,12 @@ mod tests { #[test] fn test() { - for (input, expect) in vec![("bjorn", "bjorn"), - ("bjørn", "bjørn"), - ("Ø+= ❤️‰", "Ø+2B+3D+20❤️‰"), - ("+", "+2B")] - { + for (input, expect) in vec![ + ("bjorn", "bjorn"), + ("bjørn", "bjørn"), + ("Ø+= ❤️‰", "Ø+2B+3D+20❤️‰"), + ("+", "+2B"), + ] { assert_eq!(format!("{}", XText(input)), expect); } } diff --git a/lettre/src/stub/mod.rs b/lettre/src/stub/mod.rs index 4ef8425..3bf5b9b 100644 --- a/lettre/src/stub/mod.rs +++ b/lettre/src/stub/mod.rs @@ -15,7 +15,7 @@ pub struct StubEmailTransport { impl StubEmailTransport { /// Creates a new transport that always returns the given response pub fn new(response: StubResult) -> StubEmailTransport { - StubEmailTransport { response: response } + StubEmailTransport { response } } /// Creates a new transport that always returns a success response @@ -29,10 +29,12 @@ pub type StubResult = Result<(), ()>; impl<'a, T: Read + 'a> EmailTransport<'a, T, StubResult> for StubEmailTransport { fn send>(&mut self, email: &'a U) -> StubResult { - info!("{}: from=<{}> to=<{:?}>", - email.message_id(), - email.from(), - email.to()); + info!( + "{}: from=<{}> to=<{:?}>", + email.message_id(), + email.from(), + email.to() + ); self.response } } diff --git a/lettre/tests/transport_file.rs b/lettre/tests/transport_file.rs index 259e1f4..f8bb3a3 100644 --- a/lettre/tests/transport_file.rs +++ b/lettre/tests/transport_file.rs @@ -14,10 +14,12 @@ mod test { #[test] fn file_transport() { let mut sender = FileEmailTransport::new(temp_dir()); - let email = SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "file_id".to_string(), - "Hello file".to_string()); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "file_id".to_string(), + "Hello file".to_string(), + ); let result = sender.send(&email); assert!(result.is_ok()); @@ -27,9 +29,11 @@ mod test { let mut buffer = String::new(); let _ = f.read_to_string(&mut buffer); - assert_eq!(buffer, - "{\"to\":[\"root@localhost\"],\"from\":\"user@localhost\",\"message_id\":\ - \"file_id\",\"message\":[72,101,108,108,111,32,102,105,108,101]}"); + assert_eq!( + buffer, + "{\"to\":[\"root@localhost\"],\"from\":\"user@localhost\",\"message_id\":\ + \"file_id\",\"message\":[72,101,108,108,111,32,102,105,108,101]}" + ); remove_file(file).unwrap(); } diff --git a/lettre/tests/transport_sendmail.rs b/lettre/tests/transport_sendmail.rs index 701d684..8c7cc80 100644 --- a/lettre/tests/transport_sendmail.rs +++ b/lettre/tests/transport_sendmail.rs @@ -10,10 +10,12 @@ mod test { #[test] fn sendmail_transport_simple() { let mut sender = SendmailTransport::new(); - let email = SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "sendmail_id".to_string(), - "Hello sendmail".to_string()); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "sendmail_id".to_string(), + "Hello sendmail".to_string(), + ); let result = sender.send(&email); println!("{:?}", result); diff --git a/lettre/tests/transport_smtp.rs b/lettre/tests/transport_smtp.rs index ce29a63..236b382 100644 --- a/lettre/tests/transport_smtp.rs +++ b/lettre/tests/transport_smtp.rs @@ -8,12 +8,15 @@ mod test { #[test] fn smtp_transport_simple() { - let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None).unwrap() - .build(); - let email = SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "smtp_id".to_string(), - "Hello smtp".to_string()); + let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None) + .unwrap() + .build(); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "smtp_id".to_string(), + "Hello smtp".to_string(), + ); sender.send(&email).unwrap(); } diff --git a/lettre/tests/transport_stub.rs b/lettre/tests/transport_stub.rs index 27ca504..23976cf 100644 --- a/lettre/tests/transport_stub.rs +++ b/lettre/tests/transport_stub.rs @@ -8,10 +8,12 @@ fn stub_transport() { let mut sender_ok = StubEmailTransport::new_positive(); let mut sender_ko = StubEmailTransport::new(Err(())); - let email = SimpleSendableEmail::new(EmailAddress::new("user@localhost".to_string()), - vec![EmailAddress::new("root@localhost".to_string())], - "stub_id".to_string(), - "Hello stub".to_string()); + let email = SimpleSendableEmail::new( + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "stub_id".to_string(), + "Hello stub".to_string(), + ); sender_ok.send(&email).unwrap(); sender_ko.send(&email).unwrap_err(); diff --git a/lettre_email/Cargo.toml b/lettre_email/Cargo.toml index 5c2cb73..721eeb9 100644 --- a/lettre_email/Cargo.toml +++ b/lettre_email/Cargo.toml @@ -28,4 +28,4 @@ mime = "^0.3" time = "^0.1" uuid = { version = "^0.6", features = ["v4"] } lettre = { version = "^0.8", path = "../lettre", default-features = false } -base64 = "0.9.0" +base64 = "^0.9" diff --git a/lettre_email/examples/smtp.rs b/lettre_email/examples/smtp.rs index 502c3b0..761fbe9 100644 --- a/lettre_email/examples/smtp.rs +++ b/lettre_email/examples/smtp.rs @@ -19,8 +19,9 @@ fn main() { .unwrap(); // Open a local connection on port 25 - let mut mailer = SmtpTransport::builder_unencrypted_localhost().unwrap() - .build(); + let mut mailer = SmtpTransport::builder_unencrypted_localhost() + .unwrap() + .build(); // Send the email let result = mailer.send(&email); diff --git a/lettre_email/src/lib.rs b/lettre_email/src/lib.rs index 39327de..8d3b7df 100644 --- a/lettre_email/src/lib.rs +++ b/lettre_email/src/lib.rs @@ -4,12 +4,12 @@ #![doc(html_root_url = "https://docs.rs/lettre_email/0.8.0")] #![deny(missing_docs, unsafe_code, unstable_features, warnings, missing_debug_implementations)] +extern crate base64; extern crate email as email_format; extern crate lettre; extern crate mime; extern crate time; extern crate uuid; -extern crate base64; pub mod error; @@ -123,17 +123,17 @@ impl IntoEmail for SimpleEmail { /// Simple representation of an email, useful for some transports #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct SimpleEmail { - from: Option, - to: Vec, - cc: Vec, - bcc: Vec, - reply_to: Option, - subject: Option, - date: Option, - html: Option, - text: Option, + from: Option, + to: Vec, + cc: Vec, + bcc: Vec, + reply_to: Option, + subject: Option, + date: Option, + html: Option, + text: Option, attachments: Vec, - headers: Vec
, + headers: Vec
, } impl SimpleEmail { @@ -307,8 +307,10 @@ pub struct Envelope { impl Envelope { /// Constructs an envelope with no receivers and an empty sender pub fn new() -> Self { - Envelope { to: vec![], - from: String::new(), } + Envelope { + to: vec![], + from: String::new(), + } } /// Adds a receiver pub fn to>(mut self, address: S) -> Self { @@ -344,7 +346,9 @@ pub struct Email { impl PartBuilder { /// Creates a new empty part pub fn new() -> PartBuilder { - PartBuilder { message: MimeMessage::new_blank_message(), } + PartBuilder { + message: MimeMessage::new_blank_message(), + } } /// Adds a generic header @@ -412,15 +416,17 @@ impl PartBuilder { impl EmailBuilder { /// Creates a new empty email pub fn new() -> EmailBuilder { - EmailBuilder { message: PartBuilder::new(), - to_header: vec![], - from_header: vec![], - cc_header: vec![], - bcc_header: vec![], + EmailBuilder { + message: PartBuilder::new(), + to_header: vec![], + from_header: vec![], + cc_header: vec![], + bcc_header: vec![], reply_to_header: vec![], - sender_header: None, - envelope: None, - date_issued: false, } + sender_header: None, + envelope: None, + date_issued: false, + } } /// Sets the email body @@ -525,7 +531,8 @@ impl EmailBuilder { /// Adds a `Subject` header pub fn set_subject>(&mut self, subject: S) { - self.message.add_header(("Subject".to_string(), subject.into())); + self.message + .add_header(("Subject".to_string(), subject.into())); } /// Adds a `Date` header with the given date @@ -536,27 +543,30 @@ impl EmailBuilder { /// Adds a `Date` header with the given date pub fn set_date(&mut self, date: &Tm) { - self.message.add_header(("Date", Tm::rfc822z(date).to_string())); + self.message + .add_header(("Date", Tm::rfc822z(date).to_string())); self.date_issued = true; } /// Adds an attachment to the email - pub fn attachment(mut self, - path: &Path, - filename: Option<&str>, - content_type: &Mime) - -> Result { + pub fn attachment( + mut self, + path: &Path, + filename: Option<&str>, + content_type: &Mime, + ) -> Result { self.set_attachment(path, filename, content_type)?; Ok(self) } /// Adds an attachment to the email /// If filename is not provided, the name of the file will be used. - pub fn set_attachment(&mut self, - path: &Path, - filename: Option<&str>, - content_type: &Mime) - -> Result<(), Error> { + pub fn set_attachment( + &mut self, + path: &Path, + filename: Option<&str>, + content_type: &Mime, + ) -> Result<(), Error> { let file = File::open(path); let body = match file { Ok(mut f) => { @@ -576,27 +586,25 @@ impl EmailBuilder { let actual_filename = match filename { Some(name) => name, - None => { - match path.file_name() { - Some(name) => { - match name.to_str() { - Some(name) => name, - None => return Err(Error::CannotParseFilename), - } - } + None => match path.file_name() { + Some(name) => match name.to_str() { + Some(name) => name, None => return Err(Error::CannotParseFilename), - } - } + }, + None => return Err(Error::CannotParseFilename), + }, }; let encoded_body = base64::encode(&body); - let content = PartBuilder::new().body(encoded_body) - .header(("Content-Disposition", - format!("attachment; filename=\"{}\"", - actual_filename))) - .header(("Content-Type", content_type.to_string())) - .header(("Content-Transfer-Encoding", "base64")) - .build(); + let content = PartBuilder::new() + .body(encoded_body) + .header(( + "Content-Disposition", + format!("attachment; filename=\"{}\"", actual_filename), + )) + .header(("Content-Type", content_type.to_string())) + .header(("Content-Transfer-Encoding", "base64")) + .build(); self.set_message_type(MimeMultipartType::Mixed); self.add_child(content); @@ -635,7 +643,10 @@ impl EmailBuilder { /// Sets the email body to plain text content pub fn set_text>(&mut self, body: S) { self.message.set_body(body); - self.message.add_header(("Content-Type", format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref())); + self.message.add_header(( + "Content-Type", + format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(), + )); } /// Sets the email body to HTML content @@ -647,34 +658,41 @@ impl EmailBuilder { /// Sets the email body to HTML content pub fn set_html>(&mut self, body: S) { self.message.set_body(body); - self.message.add_header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())); + self.message + .add_header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())); } /// Sets the email content - pub fn alternative, T: Into>(mut self, - body_html: S, - body_text: T) - -> EmailBuilder { + pub fn alternative, T: Into>( + mut self, + body_html: S, + body_text: T, + ) -> EmailBuilder { self.set_alternative(body_html, body_text); self } /// Sets the email content - pub fn set_alternative, T: Into>(&mut self, - body_html: S, - body_text: T) { + pub fn set_alternative, T: Into>( + &mut self, + body_html: S, + body_text: T, + ) { let mut alternate = PartBuilder::new(); alternate.set_message_type(MimeMultipartType::Alternative); - let text = PartBuilder::new().body(body_text) - .header(("Content-Type", - format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref())) - .build(); + let text = PartBuilder::new() + .body(body_text) + .header(( + "Content-Type", + format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(), + )) + .build(); - let html = PartBuilder::new().body(body_html) - .header(("Content-Type", - format!("{}", mime::TEXT_HTML).as_ref())) - .build(); + let html = PartBuilder::new() + .body(body_html) + .header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())) + .build(); alternate.add_child(text); alternate.add_child(html); @@ -726,17 +744,16 @@ impl EmailBuilder { // we need to generate the envelope let mut e = Envelope::new(); // add all receivers in to_header and cc_header - for receiver in self.to_header.iter() - .chain(self.cc_header.iter()) - .chain(self.bcc_header.iter()) + for receiver in self.to_header + .iter() + .chain(self.cc_header.iter()) + .chain(self.bcc_header.iter()) { match *receiver { Address::Mailbox(ref m) => e.add_to(m.address.clone()), - Address::Group(_, ref ms) => { - for m in ms.iter() { - e.add_to(m.address.clone()); - } - } + Address::Group(_, ref ms) => for m in ms.iter() { + e.add_to(m.address.clone()); + }, } } if e.to.is_empty() { @@ -769,7 +786,8 @@ impl EmailBuilder { // Add the collected addresses as mailbox-list all at once. // The unwraps are fine because the conversions for Vec
never errs. if !self.to_header.is_empty() { - self.message.add_header(Header::new_with_value("To".into(), self.to_header).unwrap()); + self.message + .add_header(Header::new_with_value("To".into(), self.to_header).unwrap()); } if !self.from_header.is_empty() { self.message @@ -778,10 +796,12 @@ impl EmailBuilder { return Err(Error::MissingFrom); } if !self.cc_header.is_empty() { - self.message.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap()); + self.message + .add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap()); } if !self.bcc_header.is_empty() { - self.message.add_header(Header::new_with_value("Bcc".into(), self.bcc_header).unwrap()); + self.message + .add_header(Header::new_with_value("Bcc".into(), self.bcc_header).unwrap()); } if !self.reply_to_header.is_empty() { self.message.add_header( @@ -790,28 +810,33 @@ impl EmailBuilder { } if !self.date_issued { - self.message.add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); + self.message + .add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); } self.message.add_header(("MIME-Version", "1.0")); let message_id = Uuid::new_v4(); - if let Ok(header) = Header::new_with_value("Message-ID".to_string(), - format!("<{}.lettre@localhost>", message_id)) - { + if let Ok(header) = Header::new_with_value( + "Message-ID".to_string(), + format!("<{}.lettre@localhost>", message_id), + ) { self.message.add_header(header) } - Ok(Email { message: self.message.build().as_string().into_bytes(), - envelope: envelope, - message_id: message_id, }) + Ok(Email { + message: self.message.build().as_string().into_bytes(), + envelope: envelope, + message_id: message_id, + }) } } impl<'a> SendableEmail<'a, &'a [u8]> for Email { fn to(&self) -> Vec { - self.envelope.to + self.envelope + .to .iter() .map(|x| EmailAddress::new(x.clone())) .collect() @@ -866,46 +891,56 @@ mod test { let email_builder = SimpleEmail::default(); let date_now = now(); - let email = email_builder.to("user@localhost") - .from("user@localhost") - .cc(("cc@localhost", "Alias")) - .reply_to("reply@localhost") - .text("Hello World!") - .date(date_now.clone()) - .subject("Hello") - .header(("X-test", "value")) - .into_email() - .unwrap(); + let email = email_builder + .to("user@localhost") + .from("user@localhost") + .cc(("cc@localhost", "Alias")) + .reply_to("reply@localhost") + .text("Hello World!") + .date(date_now.clone()) + .subject("Hello") + .header(("X-test", "value")) + .into_email() + .unwrap(); - assert_eq!(format!("{}", String::from_utf8_lossy(email.message().as_ref())), - format!("Subject: Hello\r\nContent-Type: text/plain; \ - charset=utf-8\r\nX-test: value\r\nTo: \r\nFrom: \ - \r\nCc: \"Alias\" \r\nReply-To: \ - \r\nDate: {}\r\nMIME-Version: 1.0\r\nMessage-ID: \ - <{}.lettre@localhost>\r\n\r\nHello World!\r\n", - date_now.rfc822z(), - email.message_id())); + assert_eq!( + format!("{}", String::from_utf8_lossy(email.message().as_ref())), + format!( + "Subject: Hello\r\nContent-Type: text/plain; \ + charset=utf-8\r\nX-test: value\r\nTo: \r\nFrom: \ + \r\nCc: \"Alias\" \r\nReply-To: \ + \r\nDate: {}\r\nMIME-Version: 1.0\r\nMessage-ID: \ + <{}.lettre@localhost>\r\n\r\nHello World!\r\n", + date_now.rfc822z(), + email.message_id() + ) + ); } #[test] fn test_multiple_from() { let email_builder = EmailBuilder::new(); let date_now = now(); - let email = email_builder.to("anna@example.com") - .from("dieter@example.com") - .from("joachim@example.com") - .date(&date_now) - .subject("Invitation") - .body("We invite you!") - .build() - .unwrap(); - assert_eq!(format!("{}", String::from_utf8_lossy(email.message().as_ref())), - format!("Date: {}\r\nSubject: Invitation\r\nSender: \ - \r\nTo: \r\nFrom: \ - , \r\nMIME-Version: \ - 1.0\r\nMessage-ID: <{}.lettre@localhost>\r\n\r\nWe invite you!\r\n", - date_now.rfc822z(), - email.message_id())); + let email = email_builder + .to("anna@example.com") + .from("dieter@example.com") + .from("joachim@example.com") + .date(&date_now) + .subject("Invitation") + .body("We invite you!") + .build() + .unwrap(); + assert_eq!( + format!("{}", String::from_utf8_lossy(email.message().as_ref())), + format!( + "Date: {}\r\nSubject: Invitation\r\nSender: \ + \r\nTo: \r\nFrom: \ + , \r\nMIME-Version: \ + 1.0\r\nMessage-ID: <{}.lettre@localhost>\r\n\r\nWe invite you!\r\n", + date_now.rfc822z(), + email.message_id() + ) + ); } #[test] @@ -913,28 +948,33 @@ mod test { let email_builder = EmailBuilder::new(); let date_now = now(); - let email = email_builder.to("user@localhost") - .from("user@localhost") - .cc(("cc@localhost", "Alias")) - .bcc("bcc@localhost") - .reply_to("reply@localhost") - .sender("sender@localhost") - .body("Hello World!") - .date(&date_now) - .subject("Hello") - .header(("X-test", "value")) - .build() - .unwrap(); + let email = email_builder + .to("user@localhost") + .from("user@localhost") + .cc(("cc@localhost", "Alias")) + .bcc("bcc@localhost") + .reply_to("reply@localhost") + .sender("sender@localhost") + .body("Hello World!") + .date(&date_now) + .subject("Hello") + .header(("X-test", "value")) + .build() + .unwrap(); - assert_eq!(format!("{}", String::from_utf8_lossy(email.message().as_ref())), - format!("Date: {}\r\nSubject: Hello\r\nX-test: value\r\nSender: \ - \r\nTo: \r\nFrom: \ - \r\nCc: \"Alias\" \r\n\ - Bcc: \r\nReply-To: \r\n\ - MIME-Version: 1.0\r\nMessage-ID: \ - <{}.lettre@localhost>\r\n\r\nHello World!\r\n", - date_now.rfc822z(), - email.message_id())); + assert_eq!( + format!("{}", String::from_utf8_lossy(email.message().as_ref())), + format!( + "Date: {}\r\nSubject: Hello\r\nX-test: value\r\nSender: \ + \r\nTo: \r\nFrom: \ + \r\nCc: \"Alias\" \r\n\ + Bcc: \r\nReply-To: \r\n\ + MIME-Version: 1.0\r\nMessage-ID: \ + <{}.lettre@localhost>\r\n\r\nHello World!\r\n", + date_now.rfc822z(), + email.message_id() + ) + ); } #[test] @@ -942,24 +982,29 @@ mod test { let email_builder = EmailBuilder::new(); let date_now = now(); - let email = email_builder.to("user@localhost") - .from("user@localhost") - .cc(("cc@localhost", "Alias")) - .bcc("bcc@localhost") - .reply_to("reply@localhost") - .sender("sender@localhost") - .body("Hello World!") - .date(&date_now) - .subject("Hello") - .header(("X-test", "value")) - .build() - .unwrap(); + let email = email_builder + .to("user@localhost") + .from("user@localhost") + .cc(("cc@localhost", "Alias")) + .bcc("bcc@localhost") + .reply_to("reply@localhost") + .sender("sender@localhost") + .body("Hello World!") + .date(&date_now) + .subject("Hello") + .header(("X-test", "value")) + .build() + .unwrap(); assert_eq!(email.from().to_string(), "sender@localhost".to_string()); - assert_eq!(email.to(), - vec![EmailAddress::new("user@localhost".to_string()), - EmailAddress::new("cc@localhost".to_string()), - EmailAddress::new("bcc@localhost".to_string())]); + assert_eq!( + email.to(), + vec![ + EmailAddress::new("user@localhost".to_string()), + EmailAddress::new("cc@localhost".to_string()), + EmailAddress::new("bcc@localhost".to_string()), + ] + ); } } diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 1c211c2..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,6 +0,0 @@ -write_mode = "Overwrite" -reorder_imports = true -reorder_imported_names = true -reorder_imports_in_group = true -struct_field_align_threshold = 20 -indent_style = "Visual"