diff --git a/lettre/examples/smtp.rs b/lettre/examples/smtp.rs index f0427a4..57dff60 100644 --- a/lettre/examples/smtp.rs +++ b/lettre/examples/smtp.rs @@ -4,10 +4,12 @@ use lettre::{EmailTransport, SimpleSendableEmail}; use lettre::smtp::SmtpTransportBuilder; fn main() { - let email = SimpleSendableEmail::new("user@localhost", - vec!["root@localhost"], - "file_id", - "Hello file"); + let email = SimpleSendableEmail::new( + "user@localhost", + vec!["root@localhost"], + "file_id", + "Hello file", + ); // Open a local connection on port 25 let mut mailer = SmtpTransportBuilder::localhost().unwrap().build(); diff --git a/lettre/src/file/mod.rs b/lettre/src/file/mod.rs index 8047198..c11cd82 100644 --- a/lettre/src/file/mod.rs +++ b/lettre/src/file/mod.rs @@ -64,10 +64,12 @@ impl EmailTransport for FileEmailTransport { let mut f = try!(File::create(file.as_path())); - let log_line = format!("{}: from=<{}> to=<{}>\n", - email.message_id(), - email.from(), - email.to().join("> to=<")); + let log_line = format!( + "{}: from=<{}> to=<{}>\n", + email.message_id(), + email.from(), + email.to().join("> to=<") + ); try!(f.write_all(log_line.as_bytes())); try!(f.write_all(email.message().as_bytes())); diff --git a/lettre/src/lib.rs b/lettre/src/lib.rs index 6d38f9f..7d2c184 100644 --- a/lettre/src/lib.rs +++ b/lettre/src/lib.rs @@ -54,11 +54,12 @@ pub struct SimpleSendableEmail { impl SimpleSendableEmail { /// Returns a new email - pub fn new(from_address: &str, - to_addresses: Vec<&str>, - message_id: &str, - message: &str) - -> SimpleSendableEmail { + pub fn new( + from_address: &str, + to_addresses: Vec<&str>, + message_id: &str, + message: &str, + ) -> SimpleSendableEmail { SimpleSendableEmail { from: from_address.to_string(), to: to_addresses.iter().map(|s| s.to_string()).collect(), diff --git a/lettre/src/sendmail/mod.rs b/lettre/src/sendmail/mod.rs index 17500c5..9bfc2a9 100644 --- a/lettre/src/sendmail/mod.rs +++ b/lettre/src/sendmail/mod.rs @@ -45,17 +45,17 @@ impl SendmailTransport { impl EmailTransport for SendmailTransport { fn send(&mut self, email: T) -> SendmailResult { // Spawn the sendmail command - let mut process = try!(Command::new(&self.command) - .args(&["-i", "-f", &email.from(), &email.to().join(" ")]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()); + let mut process = try!( + Command::new(&self.command) + .args(&["-i", "-f", &email.from(), &email.to().join(" ")]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + ); - match process - .stdin - .as_mut() - .unwrap() - .write_all(email.message().as_bytes()) { + match process.stdin.as_mut().unwrap().write_all( + email.message().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 24cec09..9b1fb22 100644 --- a/lettre/src/smtp/authentication.rs +++ b/lettre/src/smtp/authentication.rs @@ -26,11 +26,15 @@ 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", - Mechanism::CramMd5 => "CRAM-MD5", - }) + write!( + f, + "{}", + match *self { + Mechanism::Plain => "PLAIN", + Mechanism::Login => "LOGIN", + Mechanism::CramMd5 => "CRAM-MD5", + } + ) } } @@ -46,11 +50,12 @@ impl Mechanism { /// Returns the string to send to the server, using the provided username, password and /// challenge in some cases - pub fn response(&self, - username: &str, - password: &str, - challenge: Option<&str>) - -> Result { + pub fn response( + &self, + username: &str, + password: &str, + challenge: Option<&str>, + ) -> Result { match *self { Mechanism::Plain => { match challenge { @@ -97,25 +102,33 @@ mod test { fn test_plain() { let mechanism = Mechanism::Plain; - assert_eq!(mechanism.response("username", "password", None).unwrap(), - "\u{0}username\u{0}password"); - assert!(mechanism - .response("username", "password", Some("test")) - .is_err()); + assert_eq!( + mechanism.response("username", "password", None).unwrap(), + "\u{0}username\u{0}password" + ); + assert!( + mechanism + .response("username", "password", Some("test")) + .is_err() + ); } #[test] fn test_login() { let mechanism = Mechanism::Login; - assert_eq!(mechanism - .response("alice", "wonderland", Some("Username")) - .unwrap(), - "alice"); - assert_eq!(mechanism - .response("alice", "wonderland", Some("Password")) - .unwrap(), - "wonderland"); + assert_eq!( + mechanism + .response("alice", "wonderland", Some("Username")) + .unwrap(), + "alice" + ); + assert_eq!( + mechanism + .response("alice", "wonderland", Some("Password")) + .unwrap(), + "wonderland" + ); assert!(mechanism.response("username", "password", None).is_err()); } @@ -123,12 +136,16 @@ mod test { fn test_cram_md5() { let mechanism = Mechanism::CramMd5; - assert_eq!(mechanism - .response("alice", - "wonderland", - Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==")) - .unwrap(), - "alice a540ebe4ef2304070bbc3c456c1f64c0"); + assert_eq!( + mechanism + .response( + "alice", + "wonderland", + Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="), + ) + .unwrap(), + "alice a540ebe4ef2304070bbc3c456c1f64c0" + ); assert!(mechanism.response("alice", "wonderland", None).is_err()); } } diff --git a/lettre/src/smtp/client/mod.rs b/lettre/src/smtp/client/mod.rs index 70b28f1..42d0d00 100644 --- a/lettre/src/smtp/client/mod.rs +++ b/lettre/src/smtp/client/mod.rs @@ -107,10 +107,11 @@ impl Client { } /// Connects to the configured server - pub fn connect(&mut self, - addr: &A, - ssl_context: Option<&SslContext>) - -> SmtpResult { + pub fn connect( + &mut self, + addr: &A, + ssl_context: Option<&SslContext>, + ) -> 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); @@ -202,16 +203,18 @@ impl Client { pub fn auth(&mut self, mechanism: Mechanism, username: &str, password: &str) -> SmtpResult { if mechanism.supports_initial_response() { - self.command(&format!("AUTH {} {}", - mechanism, - base64::encode_config(try!(mechanism.response(username, - password, - None)) - .as_bytes(), - base64::STANDARD))) + self.command(&format!( + "AUTH {} {}", + mechanism, + base64::encode_config( + try!(mechanism.response(username, password, None)) + .as_bytes(), + base64::STANDARD, + ) + )) } else { let encoded_challenge = match try!(self.command(&format!("AUTH {}", mechanism))) - .first_word() { + .first_word() { Some(challenge) => challenge, None => return Err(Error::ResponseParsing("Could not read auth challenge")), }; @@ -233,12 +236,16 @@ impl Client { let mut challenge_expected = 3; while challenge_expected > 0 { - let response = - try!(self.command(&base64::encode_config(&try!(mechanism.response(username, - password, - Some(&decoded_challenge))) - .as_bytes(), - base64::STANDARD))); + let response = try!( + self.command(&base64::encode_config( + &try!(mechanism.response( + username, + password, + Some(&decoded_challenge), + )).as_bytes(), + base64::STANDARD, + )) + ); if !response.has_code(334) { return Ok(response); @@ -317,15 +324,19 @@ mod test { fn test_remove_crlf() { assert_eq!(remove_crlf("\r\n"), ""); assert_eq!(remove_crlf("EHLO my_name\r\n"), "EHLO my_name"); - assert_eq!(remove_crlf("EHLO my_name\r\nSIZE 42\r\n"), - "EHLO my_nameSIZE 42"); + assert_eq!( + remove_crlf("EHLO my_name\r\nSIZE 42\r\n"), + "EHLO my_nameSIZE 42" + ); } #[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 873075b..2c47ffd 100644 --- a/lettre/src/smtp/client/net.rs +++ b/lettre/src/smtp/client/net.rs @@ -26,7 +26,9 @@ impl NetworkStream { NetworkStream::Tcp(ref s) => s.peer_addr(), NetworkStream::Ssl(ref s) => s.get_ref().peer_addr(), NetworkStream::Mock(_) => { - Ok(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80))) + Ok(SocketAddr::V4( + SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80), + )) } } } @@ -87,9 +89,11 @@ impl Connector for NetworkStream { Some(context) => { match Ssl::new(context) { Ok(ssl) => { - ssl.connect(tcp_stream) - .map(NetworkStream::Ssl) - .map_err(|e| io::Error::new(ErrorKind::Other, e)) + ssl.connect(tcp_stream).map(NetworkStream::Ssl).map_err( + |e| { + io::Error::new(ErrorKind::Other, e) + }, + ) } Err(e) => Err(io::Error::new(ErrorKind::Other, e)), } diff --git a/lettre/src/smtp/error.rs b/lettre/src/smtp/error.rs index 9654742..58c2b56 100644 --- a/lettre/src/smtp/error.rs +++ b/lettre/src/smtp/error.rs @@ -43,14 +43,26 @@ impl Display for Error { impl StdError for Error { fn description(&self) -> &str { match *self { - Transient(_) => "a transient error occured during the SMTP transaction", - Permanent(_) => "a permanent error occured during the SMTP transaction", - ResponseParsing(_) => "an error occured while parsing an SMTP response", - ChallengeParsing(_) => "an error occured while parsing an SMTP AUTH challenge", - Utf8Parsing(_) => "an error occured while parsing an SMTP response as UTF8", + // Try to display the first line of the server's response that usually + // contains a short humanly readable error message + Transient(ref e) => { + match e.first_line() { + Some(line) => line, + None => "undetailed transient error during SMTP transaction", + } + } + Permanent(ref e) => { + match e.first_line() { + Some(line) => line, + None => "undetailed permanent error during SMTP transaction", + } + } + ResponseParsing(ref e) => e, + ChallengeParsing(ref e) => e.description(), + Utf8Parsing(ref e) => e.description(), Resolution => "could not resolve hostname", - Client(_) => "an unknown error occured", - Io(_) => "an I/O error occured", + Client(ref e) => e, + Io(ref e) => e.description(), } } diff --git a/lettre/src/smtp/extension.rs b/lettre/src/smtp/extension.rs index 3291666..f797187 100644 --- a/lettre/src/smtp/extension.rs +++ b/lettre/src/smtp/extension.rs @@ -53,14 +53,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) + } + ) } } @@ -105,9 +107,9 @@ impl ServerInfo { } Ok(ServerInfo { - name: name, - features: features, - }) + name: name, + features: features, + }) } /// Checks if the server supports an ESMTP feature @@ -117,8 +119,9 @@ 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), + ) } } @@ -132,10 +135,14 @@ mod test { #[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] @@ -143,40 +150,55 @@ 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, 1), - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]); + let response = Response::new( + Code::new(Severity::PositiveCompletion, Category::Unspecified4, 1), + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ); let mut features = HashSet::new(); assert!(features.insert(Extension::EightBitMime)); @@ -192,17 +214,24 @@ mod test { assert!(!server_info.supports_feature(&Extension::StartTls)); assert!(!server_info.supports_auth_mechanism(Mechanism::CramMd5)); - let response2 = - Response::new(Code::new(Severity::PositiveCompletion, Category::Unspecified4, 1), - 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, 1), + 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)); - assert!(features2.insert(Extension::Authentication(Mechanism::Plain))); - assert!(features2.insert(Extension::Authentication(Mechanism::CramMd5))); + assert!(features2.insert( + Extension::Authentication(Mechanism::Plain), + )); + assert!(features2.insert( + Extension::Authentication(Mechanism::CramMd5), + )); let server_info2 = ServerInfo { name: "me".to_string(), diff --git a/lettre/src/smtp/mod.rs b/lettre/src/smtp/mod.rs index 9a1717b..e0b5d8e 100644 --- a/lettre/src/smtp/mod.rs +++ b/lettre/src/smtp/mod.rs @@ -203,17 +203,17 @@ impl SmtpTransportBuilder { match addresses.next() { Some(addr) => { Ok(SmtpTransportBuilder { - server_addr: addr, - ssl_context: SslContext::builder(SslMethod::tls()).unwrap().build(), - security_level: SecurityLevel::AlwaysEncrypt, - smtp_utf8: false, - credentials: None, - connection_reuse_count_limit: 100, - connection_reuse: false, - hello_name: "localhost".to_string(), - authentication_mechanism: None, - timeout: Some(Duration::new(60, 0)), - }) + server_addr: addr, + ssl_context: SslContext::builder(SslMethod::tls()).unwrap().build(), + security_level: SecurityLevel::AlwaysEncrypt, + smtp_utf8: false, + credentials: None, + connection_reuse_count_limit: 100, + connection_reuse: false, + hello_name: "localhost".to_string(), + authentication_mechanism: None, + timeout: Some(Duration::new(60, 0)), + }) } None => Err(From::from("Could not resolve hostname")), } @@ -277,10 +277,11 @@ impl SmtpTransportBuilder { } /// Set the client credentials - pub fn credentials>(mut self, - username: S, - password: S) - -> SmtpTransportBuilder { + pub fn credentials>( + mut self, + username: S, + password: S, + ) -> SmtpTransportBuilder { self.credentials = Some((username.into(), password.into())); self } @@ -416,11 +417,12 @@ impl EmailTransport for SmtpTransport { try!(self.get_ehlo()); - match (&self.client_info.security_level, - self.server_info - .as_ref() - .unwrap() - .supports_feature(&Extension::StartTls)) { + match ( + &self.client_info.security_level, + self.server_info.as_ref().unwrap().supports_feature( + &Extension::StartTls, + ), + ) { (&SecurityLevel::AlwaysEncrypt, false) => { return Err(From::from("Could not encrypt connection, aborting")) } @@ -429,9 +431,12 @@ impl EmailTransport for SmtpTransport { (&SecurityLevel::EncryptedWrapper, _) => (), (_, true) => { try_smtp!(self.client.starttls(), self); - try_smtp!(self.client - .upgrade_tls_stream(&self.client_info.ssl_context), - self); + try_smtp!( + self.client.upgrade_tls_stream( + &self.client_info.ssl_context, + ), + self + ); debug!("connection encrypted"); @@ -462,10 +467,10 @@ impl EmailTransport 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!(self.client.auth(mechanism, &username, &password), self); break; @@ -479,14 +484,14 @@ impl EmailTransport for SmtpTransport { } // Mail - let mail_options = match (self.server_info - .as_ref() - .unwrap() - .supports_feature(&Extension::EightBitMime), - self.server_info - .as_ref() - .unwrap() - .supports_feature(&Extension::SmtpUtfEight)) { + let mail_options = match ( + self.server_info.as_ref().unwrap().supports_feature( + &Extension::EightBitMime, + ), + self.server_info.as_ref().unwrap().supports_feature( + &Extension::SmtpUtfEight, + ), + ) { (true, true) => Some("BODY=8BITMIME SMTPUTF8"), (true, false) => Some("BODY=8BITMIME"), (false, _) => None, @@ -516,23 +521,26 @@ impl EmailTransport for SmtpTransport { self.state.connection_reuse_count += 1; // Log the message - info!("{}: conn_use={}, size={}, status=sent ({})", - message_id, - self.state.connection_reuse_count, - message.len(), - result - .as_ref() - .ok() - .unwrap() - .message() - .iter() - .next() - .unwrap_or(&"no response".to_string())); + info!( + "{}: conn_use={}, size={}, status=sent ({})", + message_id, + self.state.connection_reuse_count, + message.len(), + result + .as_ref() + .ok() + .unwrap() + .message() + .iter() + .next() + .unwrap_or(&"no response".to_string()) + ); } // Test if we can reuse the existing connection if (!self.client_info.connection_reuse) || - (self.state.connection_reuse_count >= self.client_info.connection_reuse_count_limit) { + (self.state.connection_reuse_count >= self.client_info.connection_reuse_count_limit) + { self.reset(); } diff --git a/lettre/src/smtp/response.rs b/lettre/src/smtp/response.rs index 918f8a1..6059ff0 100644 --- a/lettre/src/smtp/response.rs +++ b/lettre/src/smtp/response.rs @@ -29,19 +29,25 @@ impl FromStr for Severity { "3" => Ok(PositiveIntermediate), "4" => Ok(TransientNegativeCompletion), "5" => Ok(PermanentNegativeCompletion), - _ => Err(Error::ResponseParsing("First digit must be between 2 and 5")), + _ => Err(Error::ResponseParsing( + "First digit must be between 2 and 5", + )), } } } impl Display for Severity { fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "{}", match *self { - PositiveCompletion => 2, - PositiveIntermediate => 3, - TransientNegativeCompletion => 4, - PermanentNegativeCompletion => 5, - }) + write!( + f, + "{}", + match *self { + PositiveCompletion => 2, + PositiveIntermediate => 3, + TransientNegativeCompletion => 4, + PermanentNegativeCompletion => 5, + } + ) } } @@ -72,26 +78,32 @@ impl FromStr for Category { "3" => Ok(Unspecified3), "4" => Ok(Unspecified4), "5" => Ok(MailSystem), - _ => Err(Error::ResponseParsing("Second digit must be between 0 and 5")), + _ => Err(Error::ResponseParsing( + "Second digit must be between 0 and 5", + )), } } } impl Display for Category { fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "{}", match *self { - Syntax => 0, - Information => 1, - Connections => 2, - Unspecified3 => 3, - Unspecified4 => 4, - MailSystem => 5, - }) + write!( + f, + "{}", + match *self { + Syntax => 0, + Information => 1, + Connections => 2, + Unspecified3 => 3, + Unspecified4 => 4, + MailSystem => 5, + } + ) } } /// Represents a 3 digit SMTP response code -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct Code { /// First digit of the response code severity: Severity, @@ -101,26 +113,36 @@ pub struct Code { detail: u8, } +impl Display for Code { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{}{}{}", self.severity, self.category, self.detail) + } +} + impl FromStr for Code { type Err = Error; #[inline] fn from_str(s: &str) -> result::Result { if s.len() == 3 { - match (s[0..1].parse::(), - s[1..2].parse::(), - s[2..3].parse::()) { + match ( + s[0..1].parse::(), + s[1..2].parse::(), + s[2..3].parse::(), + ) { (Ok(severity), Ok(category), Ok(detail)) => { Ok(Code { - severity: severity, - category: category, - detail: detail, - }) + severity: severity, + category: category, + detail: detail, + }) } _ => Err(Error::ResponseParsing("Could not parse response code")), } } else { - Err(Error::ResponseParsing("Wrong code length (should be 3 digit)")) + Err(Error::ResponseParsing( + "Wrong code length (should be 3 digit)", + )) } } } @@ -134,11 +156,6 @@ impl Code { detail: detail, } } - - /// Returns the reply code - pub fn code(&self) -> String { - format!("{}{}{}", self.severity, self.category, self.detail) - } } /// Parses an SMTP response @@ -156,14 +173,18 @@ impl ResponseParser { pub fn read_line(&mut self, line: &str) -> result::Result { if line.len() < 3 { - return Err(Error::ResponseParsing("Wrong code length (should be 3 digit)")); + return Err(Error::ResponseParsing( + "Wrong code length (should be 3 digit)", + )); } match self.code { Some(ref code) => { - if code.code() != line[0..3] { - return Err(Error::ResponseParsing("Response code has changed during a \ - reponse")); + if code.to_string() != line[0..3] { + return Err(Error::ResponseParsing( + "Response code has changed during a \ + reponse", + )); } } None => self.code = Some(try!(line[0..3].parse::())), @@ -182,8 +203,10 @@ impl ResponseParser { match self.code { Some(code) => Ok(Response::new(code, self.message)), None => { - Err(Error::ResponseParsing("Incomplete response, could not read response \ - code")) + Err(Error::ResponseParsing( + "Incomplete response, could not read response \ + code", + )) } } } @@ -223,6 +246,11 @@ impl Response { self.message.clone() } + /// Returns the response code + pub fn code(&self) -> Code { + self.code + } + /// Returns the severity (i.e. 1st digit) pub fn severity(&self) -> Severity { self.code.severity @@ -238,14 +266,9 @@ impl Response { self.code.detail } - /// Returns the reply code - fn code(&self) -> String { - self.code.code() - } - /// Tests code equality pub fn has_code(&self, code: u16) -> bool { - self.code() == format!("{}", code) + self.code.to_string() == format!("{}", code) } /// Returns only the first word of the message if possible @@ -258,7 +281,15 @@ impl Response { None => None, } } + } + /// Returns only the line word of the message if possible + pub fn first_line(&self) -> Option<&str> { + if self.message.is_empty() { + None + } else { + Some(&self.message[0]) + } } } @@ -268,10 +299,14 @@ mod test { #[test] fn test_severity_from_str() { - assert_eq!("2".parse::().unwrap(), - Severity::PositiveCompletion); - assert_eq!("4".parse::().unwrap(), - Severity::TransientNegativeCompletion); + assert_eq!( + "2".parse::().unwrap(), + Severity::PositiveCompletion + ); + assert_eq!( + "4".parse::().unwrap(), + Severity::TransientNegativeCompletion + ); assert!("1".parse::().is_err()); } @@ -294,71 +329,89 @@ mod test { #[test] fn test_code_new() { - assert_eq!(Code::new(Severity::TransientNegativeCompletion, - Category::Connections, - 0), - Code { - severity: Severity::TransientNegativeCompletion, - category: Category::Connections, - detail: 0, - }); + assert_eq!( + Code::new( + Severity::TransientNegativeCompletion, + Category::Connections, + 0, + ), + Code { + severity: Severity::TransientNegativeCompletion, + category: Category::Connections, + detail: 0, + } + ); } #[test] fn test_code_from_str() { - assert_eq!("421".parse::().unwrap(), - Code { - severity: Severity::TransientNegativeCompletion, - category: Category::Connections, - detail: 1, - }); + assert_eq!( + "421".parse::().unwrap(), + Code { + severity: Severity::TransientNegativeCompletion, + category: Category::Connections, + detail: 1, + } + ); } #[test] - fn test_code_code() { + fn test_code_display() { let code = Code { severity: Severity::TransientNegativeCompletion, category: Category::Connections, detail: 1, }; - assert_eq!(code.code(), "421"); + assert_eq!(code.to_string(), "421"); } #[test] fn test_response_new() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]), - Response { - code: Code { - severity: Severity::PositiveCompletion, - category: Category::Unspecified4, - detail: 1, - }, - message: vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()], - }); - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec![]), - Response { - code: Code { - severity: Severity::PositiveCompletion, - category: Category::Unspecified4, - detail: 1, - }, - message: vec![], - }); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ), + Response { + code: Code { + severity: Severity::PositiveCompletion, + category: Category::Unspecified4, + detail: 1, + }, + message: vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + } + ); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![], + ), + Response { + code: Code { + severity: Severity::PositiveCompletion, + category: Category::Unspecified4, + detail: 1, + }, + message: vec![], + } + ); } #[test] @@ -372,208 +425,286 @@ mod test { let response = parser.response().unwrap(); - assert_eq!(response, - Response { - code: Code { - severity: Severity::PositiveCompletion, - category: Category::MailSystem, - detail: 0, - }, - message: vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string(), - "AUTH PLAIN CRAM-MD5".to_string()], - }); + assert_eq!( + response, + Response { + code: Code { + severity: Severity::PositiveCompletion, + category: Category::MailSystem, + detail: 0, + }, + message: vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + "AUTH PLAIN CRAM-MD5".to_string(), + ], + } + ); } #[test] fn test_response_is_positive() { - assert!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .is_positive()); - assert!(!Response::new(Code { - severity: "5".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .is_positive()); + assert!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).is_positive() + ); + assert!(!Response::new( + Code { + severity: "5".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).is_positive()); } #[test] fn test_response_message() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .message(), - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).message(), + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ] + ); let empty_message: Vec = vec![]; - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec![]) - .message(), - empty_message); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![], + ).message(), + empty_message + ); } #[test] fn test_response_severity() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .severity(), - Severity::PositiveCompletion); - assert_eq!(Response::new(Code { - severity: "5".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .severity(), - Severity::PermanentNegativeCompletion); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).severity(), + Severity::PositiveCompletion + ); + assert_eq!( + Response::new( + Code { + severity: "5".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).severity(), + Severity::PermanentNegativeCompletion + ); } #[test] fn test_response_category() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .category(), - Category::Unspecified4); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).category(), + Category::Unspecified4 + ); } #[test] fn test_response_detail() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .detail(), - 1); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).detail(), + 1 + ); } #[test] fn test_response_code() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .code(), - "241"); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).code() + .to_string(), + "241" + ); } #[test] fn test_response_has_code() { - assert!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .has_code(241)); - assert!(!Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .has_code(251)); + assert!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).has_code(241) + ); + assert!(!Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).has_code(251)); } #[test] fn test_response_first_word() { - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .first_word(), - Some("me".to_string())); - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["me mo".to_string(), - "8BITMIME".to_string(), - "SIZE 42".to_string()]) - .first_word(), - Some("me".to_string())); - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec![]) - .first_word(), - None); - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec![" ".to_string()]) - .first_word(), - None); - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec![" ".to_string()]) - .first_word(), - None); - assert_eq!(Response::new(Code { - severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, - }, - vec!["".to_string()]) - .first_word(), - None); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).first_word(), + Some("me".to_string()) + ); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![ + "me mo".to_string(), + "8BITMIME".to_string(), + "SIZE 42".to_string(), + ], + ).first_word(), + Some("me".to_string()) + ); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![], + ).first_word(), + None + ); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![" ".to_string()], + ).first_word(), + None + ); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec![" ".to_string()], + ).first_word(), + None + ); + assert_eq!( + Response::new( + Code { + severity: "2".parse::().unwrap(), + category: "4".parse::().unwrap(), + detail: 1, + }, + vec!["".to_string()], + ).first_word(), + None + ); } } diff --git a/lettre/src/stub/mod.rs b/lettre/src/stub/mod.rs index e5d793b..97ee062 100644 --- a/lettre/src/stub/mod.rs +++ b/lettre/src/stub/mod.rs @@ -38,10 +38,12 @@ pub type StubResult = Result<(), error::Error>; impl EmailTransport for StubEmailTransport { fn send(&mut self, email: T) -> StubResult { - info!("{}: from=<{}> to=<{:?}>", - email.message_id(), - email.from(), - email.to()); + info!( + "{}: from=<{}> to=<{:?}>", + email.message_id(), + email.from(), + email.to() + ); Ok(()) } diff --git a/lettre/tests/transport_file.rs b/lettre/tests/transport_file.rs index 48992fc..4ff9c76 100644 --- a/lettre/tests/transport_file.rs +++ b/lettre/tests/transport_file.rs @@ -11,10 +11,12 @@ use std::io::Read; #[test] fn file_transport() { let mut sender = FileEmailTransport::new(temp_dir()); - let email = SimpleSendableEmail::new("user@localhost", - vec!["root@localhost"], - "file_id", - "Hello file"); + let email = SimpleSendableEmail::new( + "user@localhost", + vec!["root@localhost"], + "file_id", + "Hello file", + ); let result = sender.send(email.clone()); assert!(result.is_ok()); @@ -24,10 +26,14 @@ fn file_transport() { let mut buffer = String::new(); let _ = f.read_to_string(&mut buffer); - assert_eq!(buffer, - format!("{}: from= to=\n{}", - message_id, - email.message())); + assert_eq!( + buffer, + format!( + "{}: from= to=\n{}", + message_id, + email.message() + ) + ); remove_file(file).unwrap(); } diff --git a/lettre/tests/transport_sendmail.rs b/lettre/tests/transport_sendmail.rs index db46558..ed6f5eb 100644 --- a/lettre/tests/transport_sendmail.rs +++ b/lettre/tests/transport_sendmail.rs @@ -6,10 +6,12 @@ use lettre::sendmail::SendmailTransport; #[test] fn sendmail_transport_simple() { let mut sender = SendmailTransport::new(); - let email = SimpleSendableEmail::new("user@localhost", - vec!["root@localhost"], - "sendmail_id", - "Hello sendmail"); + let email = SimpleSendableEmail::new( + "user@localhost", + vec!["root@localhost"], + "sendmail_id", + "Hello sendmail", + ); let result = sender.send(email); println!("{:?}", result); diff --git a/lettre/tests/transport_smtp.rs b/lettre/tests/transport_smtp.rs index 4b87e3e..8d08b1b 100644 --- a/lettre/tests/transport_smtp.rs +++ b/lettre/tests/transport_smtp.rs @@ -10,10 +10,12 @@ fn smtp_transport_simple() { .unwrap() .security_level(SecurityLevel::Opportunistic) .build(); - let email = SimpleSendableEmail::new("user@localhost", - vec!["root@localhost"], - "smtp_id", - "Hello smtp"); + let email = SimpleSendableEmail::new( + "user@localhost", + vec!["root@localhost"], + "smtp_id", + "Hello smtp", + ); let result = sender.send(email); assert!(result.is_ok()); diff --git a/lettre/tests/transport_stub.rs b/lettre/tests/transport_stub.rs index d1b8f2d..9751ccb 100644 --- a/lettre/tests/transport_stub.rs +++ b/lettre/tests/transport_stub.rs @@ -6,10 +6,12 @@ use lettre::stub::StubEmailTransport; #[test] fn stub_transport() { let mut sender = StubEmailTransport; - let email = SimpleSendableEmail::new("user@localhost", - vec!["root@localhost"], - "stub_id", - "Hello stub"); + let email = SimpleSendableEmail::new( + "user@localhost", + vec!["root@localhost"], + "stub_id", + "Hello stub", + ); let result = sender.send(email); assert!(result.is_ok());