diff --git a/examples/client.rs b/examples/client.rs index 69241e9..5b36e7c 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -12,7 +12,7 @@ use smtp::email::EmailBuilder; fn main() { env_logger::init().unwrap(); - let sender = Arc::new(Mutex::new(SenderBuilder::localhost().hello_name("localhost") + let sender = Arc::new(Mutex::new(SenderBuilder::localhost().unwrap().hello_name("localhost") .enable_connection_reuse(true).build())); let mut threads = Vec::new(); diff --git a/src/client/mod.rs b/src/client/mod.rs index c229d94..9f2d784 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -46,7 +46,6 @@ pub struct Client { stream: Option>, /// Socket we are connecting to server_addr: SocketAddr, - } macro_rules! return_err ( @@ -59,11 +58,16 @@ impl Client { /// Creates a new SMTP client /// /// It does not connects to the server, but only creates the `Client` - pub fn new(addr: A) -> Client { - Client{ - stream: None, - server_addr: addr.to_socket_addrs().ok().expect("could not parse server address").next().unwrap(), - } + pub fn new(addr: A) -> Result, Error> { + let mut addresses = try!(addr.to_socket_addrs()); + + match addresses.next() { + Some(addr) => Ok(Client { + stream: None, + server_addr: addr, + }), + None => Err(From::from("Could nor resolve hostname")), + } } } diff --git a/src/email.rs b/src/email.rs index fc4b075..d170f01 100644 --- a/src/email.rs +++ b/src/email.rs @@ -185,13 +185,13 @@ impl EmailBuilder { /// Email sendable by an SMTP client pub trait SendableEmail { /// From address - fn from_address(&self) -> String; + fn from_address(&self) -> Option; /// To addresses - fn to_addresses(&self) -> Vec; + fn to_addresses(&self) -> Option>; /// Message content - fn message(&self) -> String; + fn message(&self) -> Option; /// Message ID - fn message_id(&self) -> String; + fn message_id(&self) -> Option; } /// Minimal email structure @@ -216,46 +216,47 @@ impl SimpleSendableEmail { } impl SendableEmail for SimpleSendableEmail { - fn from_address(&self) -> String { - self.from.clone() + fn from_address(&self) -> Option { + Some(self.from.clone()) } - fn to_addresses(&self) -> Vec { - self.to.clone() + fn to_addresses(&self) -> Option> { + Some(self.to.clone()) } - fn message(&self) -> String { - self.message.clone() + fn message(&self) -> Option { + Some(self.message.clone()) } - fn message_id(&self) -> String { - format!("<{}@rust-smtp>", Uuid::new_v4()) + fn message_id(&self) -> Option { + Some(format!("<{}@rust-smtp>", Uuid::new_v4())) } } impl SendableEmail for Email { /// Return the to addresses, and fails if it is not set - fn to_addresses(&self) -> Vec { + fn to_addresses(&self) -> Option> { if self.to.is_empty() { - panic!("The To field is empty") + None + } else { + Some(self.to.clone()) } - self.to.clone() } /// Return the from address, and fails if it is not set - fn from_address(&self) -> String { + fn from_address(&self) -> Option { match self.from { - Some(ref from_address) => from_address.clone(), - None => panic!("The From field is empty"), + Some(ref from_address) => Some(from_address.clone()), + None => None, } } - fn message(&self) -> String { - format!("{}", self) + fn message(&self) -> Option { + Some(format!("{}", self)) } - fn message_id(&self) -> String { - format!("{}", self.message_id) + fn message_id(&self) -> Option { + Some(format!("{}", self.message_id)) } } @@ -295,7 +296,7 @@ mod test { format!("{}", email), format!("Message-ID: <{}@rust-smtp>\r\nTo: to@example.com\r\n\r\nbody\r\n", current_message) ); - assert_eq!(current_message.to_string(), email.message_id()); + assert_eq!(current_message.to_string(), email.message_id().unwrap()); } #[test] @@ -317,7 +318,7 @@ mod test { assert_eq!( format!("{}", email), format!("Message-ID: <{}@rust-smtp>\r\nTo: \r\nFrom: \r\nCc: \"Alias\" \r\nReply-To: \r\nSender: \r\nDate: {}\r\nSubject: Hello\r\nX-test: value\r\n\r\nHello World!\r\n", - email.message_id(), date_now.rfc822()) + email.message_id().unwrap(), date_now.rfc822()) ); } @@ -338,15 +339,15 @@ mod test { .build(); assert_eq!( - email.from_address(), + email.from_address().unwrap(), "sender@localhost".to_string() ); assert_eq!( - email.to_addresses(), + email.to_addresses().unwrap(), vec!["user@localhost".to_string(), "cc@localhost".to_string()] ); assert_eq!( - email.message(), + email.message().unwrap(), format!("{}", email) ); } diff --git a/src/extension.rs b/src/extension.rs index 3a793c4..3c67644 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -77,11 +77,11 @@ impl ServerInfo { for line in response.message() { let splitted : Vec<&str> = line.split_whitespace().collect(); - let _ = match (splitted[0], splitted.len()) { - ("8BITMIME", 1) => {features.insert(Extension::EightBitMime);}, - ("SMTPUTF8", 1) => {features.insert(Extension::SmtpUtfEight);}, - ("STARTTLS", 1) => {features.insert(Extension::StartTls);}, - ("AUTH", _) => { + let _ = match splitted[0] { + "8BITMIME" => {features.insert(Extension::EightBitMime);}, + "SMTPUTF8" => {features.insert(Extension::SmtpUtfEight);}, + "STARTTLS" => {features.insert(Extension::StartTls);}, + "AUTH" => { for &mecanism in &splitted[1..] { match mecanism { "PLAIN" => {features.insert(Extension::Authentication(Mecanism::Plain));}, @@ -90,7 +90,7 @@ impl ServerInfo { } } }, - (_, _) => (), + _ => (), }; } diff --git a/src/lib.rs b/src/lib.rs index f696f2e..c74e074 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,14 @@ //! # Rust SMTP client //! //! This client should tend to follow [RFC 5321](https://tools.ietf.org/html/rfc5321), but is still -//! a work in progress. It is designed to efficiently send emails from a rust application to a -//! relay email server. +//! a work in progress. It is designed to efficiently send emails from an application to a +//! relay email server, as it relies as much as possible on the relay server for sanity and RFC compliance +//! checks. //! //! It implements the following extensions: //! //! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152)) -//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) +//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN and CRAM-MD5 mecanisms //! //! It will eventually implement the following extensions: //! @@ -16,7 +17,7 @@ //! //! ## Architecture //! -//! This client is divided into three parts: +//! This client is divided into three main parts: //! //! * client: a low level SMTP client providing all SMTP commands //! * sender: a high level SMTP client providing an easy method to send emails @@ -43,7 +44,7 @@ //! .build(); //! //! // Open a local connection on port 25 -//! let mut sender = SenderBuilder::localhost().build(); +//! let mut sender = SenderBuilder::localhost().unwrap().build(); //! // Send the email //! let result = sender.send(email); //! @@ -70,7 +71,7 @@ //! let email = builder.build(); //! //! // Connect to a remote server on a custom port -//! let mut sender = SenderBuilder::new(("server.tld", 10025)) +//! let mut sender = SenderBuilder::new(("server.tld", 10025)).unwrap() //! // Set the name sent during EHLO/HELO, default is `localhost` //! .hello_name("my.hostname.tld") //! // Add credentials for authentication @@ -104,7 +105,7 @@ //! "Hello world !" //! ); //! -//! let mut sender = SenderBuilder::localhost().build(); +//! let mut sender = SenderBuilder::localhost().unwrap().build(); //! let result = sender.send(email); //! assert!(result.is_ok()); //! ``` @@ -119,7 +120,7 @@ //! use smtp::SMTP_PORT; //! use std::net::TcpStream; //! -//! let mut email_client: Client = Client::new(("localhost", SMTP_PORT)); +//! let mut email_client: Client = Client::new(("localhost", SMTP_PORT)).unwrap(); //! let _ = email_client.connect(); //! let _ = email_client.ehlo("my_hostname"); //! let _ = email_client.mail("user@example.com", None); diff --git a/src/sender.rs b/src/sender.rs index 6e44283..dacc208 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -30,18 +30,24 @@ pub struct SenderBuilder { /// Builder for the SMTP Sender impl SenderBuilder { /// Creates a new local SMTP client - pub fn new(addr: A) -> SenderBuilder { - SenderBuilder { - server_addr: addr.to_socket_addrs().ok().expect("could not parse server address").next().unwrap(), - credentials: None, - connection_reuse_count_limit: 100, - enable_connection_reuse: false, - hello_name: "localhost".to_string(), - } + pub fn new(addr: A) -> Result { + let mut addresses = try!(addr.to_socket_addrs()); + + + match addresses.next() { + Some(addr) => Ok(SenderBuilder { + server_addr: addr, + credentials: None, + connection_reuse_count_limit: 100, + enable_connection_reuse: false, + hello_name: "localhost".to_string(), + }), + None => Err(From::from("Could nor resolve hostname")), + } } /// Creates a new local SMTP client to port 25 - pub fn localhost() -> SenderBuilder { + pub fn localhost() -> Result { SenderBuilder::new(("localhost", SMTP_PORT)) } @@ -119,7 +125,7 @@ impl Sender { /// /// It does not connects to the server, but only creates the `Sender` pub fn new(builder: SenderBuilder) -> Sender { - let client: Client = Client::new(builder.server_addr); + let client: Client = Client::new(builder.server_addr).unwrap(); Sender{ client: client, server_info: None, @@ -201,10 +207,10 @@ impl Sender { } } - let current_message = email.message_id(); - let from_address = email.from_address(); - let to_addresses = email.to_addresses(); - let message = email.message(); + let current_message = try!(email.message_id().ok_or("Missing Message-ID")); + let from_address = try!(email.from_address().ok_or("Missing Message-ID")); + let to_addresses = try!(email.to_addresses().ok_or("Missing Message-ID")); + let message = try!(email.message().ok_or("Missing Message-ID")); // Mail let mail_options = match self.server_info.as_ref().unwrap().supports_feature(&Extension::EightBitMime) {