diff --git a/lettre/src/smtp/client/mod.rs b/lettre/src/smtp/client/mod.rs index 09f9114..b2aa02d 100644 --- a/lettre/src/smtp/client/mod.rs +++ b/lettre/src/smtp/client/mod.rs @@ -215,7 +215,7 @@ impl Client { } else { let encoded_challenge = match try!(self.command(&format!("AUTH {}", mechanism))) .first_word() { - Some(challenge) => challenge, + Some(challenge) => challenge.to_string(), None => return Err(Error::ResponseParsing("Could not read auth challenge")), }; diff --git a/lettre/src/smtp/extension.rs b/lettre/src/smtp/extension.rs index 00d71c6..de8a603 100644 --- a/lettre/src/smtp/extension.rs +++ b/lettre/src/smtp/extension.rs @@ -6,8 +6,37 @@ use smtp::response::Response; use std::collections::HashSet; use std::fmt; use std::fmt::{Display, Formatter}; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::result::Result; +/// Client identifier, the parameter to `EHLO` +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum ClientId { + /// A fully-qualified domain name + Domain(String), + /// An IPv4 address + Ipv4(Ipv4Addr), + /// An IPv6 address + Ipv6(Ipv6Addr), +} + +impl Display for ClientId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + ClientId::Domain(ref value) => f.write_str(value), + ClientId::Ipv4(ref value) => write!(f, "{}", value), + ClientId::Ipv6(ref value) => write!(f, "{}", value), + } + } +} + +impl ClientId { + /// Creates a new `ClientId` from a fully qualified domain name + pub fn new(domain: String) -> ClientId { + ClientId::Domain(domain) + } +} + /// Supported ESMTP keywords #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum Extension { @@ -109,7 +138,7 @@ impl ServerInfo { } Ok(ServerInfo { - name: name, + name: name.to_string(), features: features, }) } @@ -132,7 +161,7 @@ mod test { use super::{Extension, ServerInfo}; use smtp::authentication::Mechanism; - use smtp::response::{Category, Code, Response, Severity}; + use smtp::response::{Category, Code, Detail, Response, Severity}; use std::collections::HashSet; #[test] @@ -194,7 +223,11 @@ mod test { #[test] fn test_serverinfo() { let response = Response::new( - Code::new(Severity::PositiveCompletion, Category::Unspecified4, 1), + Code::new( + Severity::PositiveCompletion, + Category::Unspecified4, + Detail(1), + ), vec![ "me".to_string(), "8BITMIME".to_string(), @@ -217,7 +250,11 @@ mod test { assert!(!server_info.supports_auth_mechanism(Mechanism::CramMd5)); let response2 = Response::new( - Code::new(Severity::PositiveCompletion, Category::Unspecified4, 1), + Code::new( + Severity::PositiveCompletion, + Category::Unspecified4, + Detail(1), + ), vec![ "me".to_string(), "AUTH PLAIN CRAM-MD5 OTHER".to_string(), diff --git a/lettre/src/smtp/mod.rs b/lettre/src/smtp/mod.rs index d927011..320fcda 100644 --- a/lettre/src/smtp/mod.rs +++ b/lettre/src/smtp/mod.rs @@ -46,6 +46,7 @@ //! use lettre::smtp::authentication::Mechanism; //! use lettre::smtp::SUBMISSION_PORT; //! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::smtp::extension::ClientId; //! //! let email = SimpleSendableEmail::new( //! "user@localhost", @@ -58,7 +59,7 @@ //! let mut mailer = SmtpTransportBuilder::new(("server.tld", //! SUBMISSION_PORT)).unwrap() //! // Set the name sent during EHLO/HELO, default is `localhost` -//! .hello_name("my.hostname.tld") +//! .hello_name(ClientId::Domain("my.hostname.tld".to_string())) //! // Add credentials for authentication //! .credentials("username", "password") //! // Specify a TLS security level. You can also specify an SslContext with @@ -109,7 +110,7 @@ use openssl::ssl::{SslContext, SslMethod}; use smtp::authentication::Mechanism; use smtp::client::Client; use smtp::error::{Error, SmtpResult}; -use smtp::extension::{Extension, ServerInfo}; +use smtp::extension::{ClientId, Extension, ServerInfo}; use std::net::{SocketAddr, ToSocketAddrs}; use std::string::String; use std::time::Duration; @@ -176,7 +177,7 @@ pub struct SmtpTransportBuilder { /// Enable connection reuse connection_reuse: bool, /// Name sent during HELO or EHLO - hello_name: String, + hello_name: ClientId, /// Credentials credentials: Option<(String, String)>, /// Socket we are connecting to @@ -210,7 +211,7 @@ impl SmtpTransportBuilder { credentials: None, connection_reuse_count_limit: 100, connection_reuse: false, - hello_name: "localhost".to_string(), + hello_name: ClientId::Domain("localhost".to_string()), authentication_mechanism: None, timeout: Some(Duration::new(60, 0)), }) @@ -259,8 +260,8 @@ impl SmtpTransportBuilder { } /// Set the name used during HELO or EHLO - pub fn hello_name>(mut self, name: S) -> SmtpTransportBuilder { - self.hello_name = name.into(); + pub fn hello_name(mut self, name: ClientId) -> SmtpTransportBuilder { + self.hello_name = name; self } @@ -377,7 +378,10 @@ impl SmtpTransport { /// Gets the EHLO response and updates server information pub fn get_ehlo(&mut self) -> SmtpResult { // Extended Hello - let ehlo_response = try_smtp!(self.client.ehlo(&self.client_info.hello_name), self); + let ehlo_response = try_smtp!( + self.client.ehlo(&self.client_info.hello_name.to_string()), + self + ); self.server_info = Some(try_smtp!(ServerInfo::from_response(&ehlo_response), self)); diff --git a/lettre/src/smtp/response.rs b/lettre/src/smtp/response.rs index 9526637..ea3c5be 100644 --- a/lettre/src/smtp/response.rs +++ b/lettre/src/smtp/response.rs @@ -102,6 +102,28 @@ impl Display for Category { } } +/// The detail digit of a response code (third digit) +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub struct Detail(pub u8); + +impl FromStr for Detail { + type Err = Error; + fn from_str(s: &str) -> result::Result { + match s.parse::() { + Ok(d) if d < 10 => Ok(Detail(d)), + _ => Err(Error::ResponseParsing( + "Third digit must be between 0 and 9", + )), + } + } +} + +impl Display for Detail { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{}", self.0) + } +} + /// Represents a 3 digit SMTP response code #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct Code { @@ -110,7 +132,7 @@ pub struct Code { /// Second digit of the response code category: Category, /// Third digit - detail: u8, + detail: Detail, } impl Display for Code { @@ -128,7 +150,7 @@ impl FromStr for Code { match ( s[0..1].parse::(), s[1..2].parse::(), - s[2..3].parse::(), + s[2..3].parse::(), ) { (Ok(severity), Ok(category), Ok(detail)) => { Ok(Code { @@ -149,8 +171,8 @@ impl FromStr for Code { impl Code { /// Creates a new `Code` structure - pub fn new(severity: Severity, category: Category, detail: u8) -> Code { - if detail > 9 { + pub fn new(severity: Severity, category: Category, detail: Detail) -> Code { + if detail.0 > 9 { panic!("The detail code must be between 0 and 9"); } @@ -266,7 +288,7 @@ impl Response { } /// Returns the detail (i.e. 3rd digit) - pub fn detail(&self) -> u8 { + pub fn detail(&self) -> Detail { self.code.detail } @@ -276,15 +298,10 @@ impl Response { } /// Returns only the first word of the message if possible - pub fn first_word(&self) -> Option { - if self.message.is_empty() { - None - } else { - match self.message[0].split_whitespace().next() { - Some(word) => Some(word.to_string()), - None => None, - } - } + pub fn first_word(&self) -> Option<&str> { + self.message.get(0).and_then( + |line| line.split_whitespace().next(), + ) } /// Returns only the line of the message if possible @@ -295,7 +312,7 @@ impl Response { #[cfg(test)] mod test { - use super::{Category, Code, Response, ResponseParser, Severity}; + use super::{Category, Code, Detail, Response, ResponseParser, Severity}; #[test] fn test_severity_from_str() { @@ -334,12 +351,12 @@ mod test { Code::new( Severity::TransientNegativeCompletion, Category::Connections, - 0, + Detail(0), ), Code { severity: Severity::TransientNegativeCompletion, category: Category::Connections, - detail: 0, + detail: Detail(0), } ); } @@ -350,7 +367,7 @@ mod test { let _ = Code::new( Severity::TransientNegativeCompletion, Category::Connections, - 11, + Detail(11), ); } @@ -361,7 +378,7 @@ mod test { Code { severity: Severity::TransientNegativeCompletion, category: Category::Connections, - detail: 1, + detail: "1".parse::().unwrap(), } ); assert!("2222".parse::().is_err()); @@ -377,7 +394,7 @@ mod test { let code = Code { severity: Severity::TransientNegativeCompletion, category: Category::Connections, - detail: 1, + detail: Detail(1), }; assert_eq!(code.to_string(), "421"); @@ -390,7 +407,7 @@ mod test { Code { severity: "2".parse::().unwrap(), category: "4".parse::().unwrap(), - detail: 1, + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -402,7 +419,7 @@ mod test { code: Code { severity: Severity::PositiveCompletion, category: Category::Unspecified4, - detail: 1, + detail: "1".parse::().unwrap(), }, message: vec![ "me".to_string(), @@ -416,7 +433,7 @@ mod test { Code { severity: "2".parse::().unwrap(), category: "4".parse::().unwrap(), - detail: 1, + detail: "1".parse::().unwrap(), }, vec![], ), @@ -424,7 +441,7 @@ mod test { code: Code { severity: Severity::PositiveCompletion, category: Category::Unspecified4, - detail: 1, + detail: "1".parse::().unwrap(), }, message: vec![], } @@ -448,7 +465,7 @@ mod test { code: Code { severity: Severity::PositiveCompletion, category: Category::MailSystem, - detail: 0, + detail: Detail(0), }, message: vec![ "me".to_string(), @@ -466,8 +483,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -479,8 +496,8 @@ mod test { assert!(!Response::new( Code { severity: "5".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -496,8 +513,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -516,8 +533,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![], ).message(), @@ -531,8 +548,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -546,8 +563,8 @@ mod test { Response::new( Code { severity: "5".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -566,7 +583,7 @@ mod test { Code { severity: "2".parse::().unwrap(), category: "4".parse::().unwrap(), - detail: 1, + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -584,8 +601,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -593,7 +610,7 @@ mod test { "SIZE 42".to_string(), ], ).detail(), - 1 + Detail(1) ); } @@ -604,7 +621,7 @@ mod test { Code { severity: "2".parse::().unwrap(), category: "4".parse::().unwrap(), - detail: 1, + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -624,7 +641,7 @@ mod test { Code { severity: "2".parse::().unwrap(), category: "4".parse::().unwrap(), - detail: 1, + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -636,15 +653,15 @@ mod test { assert!(!Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "5".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string(), ], - ).has_code(251)); + ).has_code(241)); } #[test] @@ -653,8 +670,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -662,14 +679,14 @@ mod test { "SIZE 42".to_string(), ], ).first_word(), - Some("me".to_string()) + Some("me") ); assert_eq!( Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me mo".to_string(), @@ -677,14 +694,14 @@ mod test { "SIZE 42".to_string(), ], ).first_word(), - Some("me".to_string()) + Some("me") ); assert_eq!( Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![], ).first_word(), @@ -694,8 +711,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![" ".to_string()], ).first_word(), @@ -705,8 +722,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![" ".to_string()], ).first_word(), @@ -716,8 +733,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec!["".to_string()], ).first_word(), @@ -731,8 +748,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me".to_string(), @@ -746,8 +763,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![ "me mo".to_string(), @@ -761,8 +778,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![], ).first_line(), @@ -772,8 +789,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![" ".to_string()], ).first_line(), @@ -783,8 +800,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec![" ".to_string()], ).first_line(), @@ -794,8 +811,8 @@ mod test { Response::new( Code { severity: "2".parse::().unwrap(), - category: "4".parse::().unwrap(), - detail: 1, + category: "3".parse::().unwrap(), + detail: "1".parse::().unwrap(), }, vec!["".to_string()], ).first_line(),