diff --git a/src/client/mod.rs b/src/client/mod.rs index 684f7b3..a6b08dd 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,8 +13,8 @@ use std::string::String; use std::io::net::ip::Port; use std::error::FromError; -use common::{get_first_word, unquote_email_address}; -use common::{CRLF, SMTP_PORT, MESSAGE_ENDING}; +use tools::{get_first_word, unquote_email_address}; +use common::{SMTP_PORT, CRLF, MESSAGE_ENDING}; use response::Response; use extension; use extension::Extension; @@ -149,7 +149,7 @@ impl Client { } /// Sends an SMTP command - fn send_command(&mut self, command: Command) -> SmtpResult { + pub fn send_command(&mut self, command: Command) -> SmtpResult { // for now we do not support SMTPUTF8 if !command.is_ascii() { fail_with_err!("Non-ASCII string" self); diff --git a/src/client/stream.rs b/src/client/stream.rs index 17dac5f..df4cd1f 100644 --- a/src/client/stream.rs +++ b/src/client/stream.rs @@ -17,7 +17,7 @@ use error::SmtpResult; use std::error::FromError; use response::Response; -use common::{escape_crlf, escape_dot}; +use tools::{escape_crlf, escape_dot}; static BUFFER_SIZE: uint = 1024; diff --git a/src/command.rs b/src/command.rs index 052927f..995ba34 100644 --- a/src/command.rs +++ b/src/command.rs @@ -14,9 +14,9 @@ use std::error::FromError; use std::fmt::{Show, Formatter, Result}; -use common::SP; use response::Response; use error::SmtpResult; +use common::SP; /// Supported SMTP commands /// diff --git a/src/common.rs b/src/common.rs index c4eb4e4..c650019 100644 --- a/src/common.rs +++ b/src/common.rs @@ -7,13 +7,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Contains mixed-up tools for SMTP -//! -//! TODO : Clean up and split this file +//! Contains constants defined in SMTP RFCs use std::io::net::ip::Port; -use std::string::String; -use std::str::replace; /// Default smtp port pub static SMTP_PORT: Port = 25; @@ -34,132 +30,6 @@ pub static SP: &'static str = " "; pub static CRLF: &'static str = "\r\n"; pub static CR: &'static str = "\r"; pub static LF: &'static str = "\n"; + +/// The ending of message content pub static MESSAGE_ENDING: &'static str = "\r\n.\r\n"; - -/// Adds quotes to emails if needed -pub fn quote_email_address(address: &str) -> String { - match address.len() { - 0 ... 1 => format!("<{}>", address), - _ => match (address.slice_to(1), - address.slice_from(address.len() - 1)) { - ("<", ">") => address.to_string(), - _ => format!("<{}>", address) - } - } -} - -/// Removes quotes from emails if needed -pub fn unquote_email_address(address: &str) -> &str { - match address.len() { - 0 ... 1 => address, - _ => match (address.slice_to(1), - address.slice_from(address.len() - 1)) { - ("<", ">") => address.slice(1, address.len() - 1), - _ => address - } - } -} - -/// Removes the trailing line return at the end of a string -pub fn remove_trailing_crlf(string: &str) -> &str { - if string.ends_with(CRLF) { - string.slice_to(string.len() - 2) - } else if string.ends_with(CR) { - string.slice_to(string.len() - 1) - } else { - string - } -} - -/// Returns the first word of a string, or the string if it contains no space -pub fn get_first_word(string: &str) -> &str { - string.split_str(CRLF).next().unwrap().splitn(1, ' ').next().unwrap() -} - -/// Returns the string replacing all the CRLF with "" -#[inline] -pub fn escape_crlf(string: &str) -> String { - replace(string, CRLF, "") -} - -/// Returns the string after adding a dot at the beginning of each line starting with a dot -/// -/// Reference : https://tools.ietf.org/html/rfc5321#page-62 (4.5.2. Transparency) -#[inline] -pub fn escape_dot(string: &str) -> String { - if string.starts_with(".") { - format!(".{}", string) - } else { - string.to_string() - }.replace(format!("{}.", CR).as_slice(), format!("{}..", CR).as_slice()) - .replace(format!("{}.", LF).as_slice(), format!("{}..", LF).as_slice()) -} - -#[cfg(test)] -mod test { - use super::{quote_email_address, unquote_email_address, - remove_trailing_crlf, get_first_word, escape_crlf, escape_dot}; - - #[test] - fn test_quote_email_address() { - assert_eq!(quote_email_address("address").as_slice(), "
"); - assert_eq!(quote_email_address("
").as_slice(), "
"); - assert_eq!(quote_email_address("a").as_slice(), ""); - assert_eq!(quote_email_address("").as_slice(), "<>"); - } - - #[test] - fn test_unquote_email_address() { - assert_eq!(unquote_email_address("
"), "address"); - assert_eq!(unquote_email_address("address"), "address"); - assert_eq!(unquote_email_address(""), ""); - assert_eq!(unquote_email_address("a"), "a"); - assert_eq!(unquote_email_address(""), ""); - } - - #[test] - fn test_remove_trailing_crlf() { - assert_eq!(remove_trailing_crlf("word"), "word"); - assert_eq!(remove_trailing_crlf("word\r\n"), "word"); - assert_eq!(remove_trailing_crlf("word\r\n "), "word\r\n "); - assert_eq!(remove_trailing_crlf("word\r"), "word"); - assert_eq!(remove_trailing_crlf("\r\n"), ""); - assert_eq!(remove_trailing_crlf("\r"), ""); - assert_eq!(remove_trailing_crlf("a"), "a"); - assert_eq!(remove_trailing_crlf(""), ""); - } - - #[test] - fn test_get_first_word() { - assert_eq!(get_first_word("first word"), "first"); - assert_eq!(get_first_word("first word\r\ntest"), "first"); - assert_eq!(get_first_word("first"), "first"); - assert_eq!(get_first_word(""), ""); - assert_eq!(get_first_word("\r\n"), ""); - assert_eq!(get_first_word("a\r\n"), "a"); - // Manage cases of empty line, spaces at the beginning - //assert_eq!(get_first_word(" a"), "a"); - //assert_eq!(get_first_word("\r\n a"), "a"); - assert_eq!(get_first_word(" \r\n"), ""); - assert_eq!(get_first_word("\r\n "), ""); - } - - #[test] - fn test_escape_crlf() { - assert_eq!(escape_crlf("\r\n").as_slice(), ""); - assert_eq!(escape_crlf("EHLO my_name\r\n").as_slice(), "EHLO my_name"); - assert_eq!( - escape_crlf("EHLO my_name\r\nSIZE 42\r\n").as_slice(), - "EHLO my_nameSIZE 42" - ); - } - - #[test] - fn test_escape_dot() { - assert_eq!(escape_dot(".test").as_slice(), "..test"); - assert_eq!(escape_dot("\r.\n.\r\n").as_slice(), "\r..\n..\r\n"); - assert_eq!(escape_dot("test\r\n.test\r\n").as_slice(), "test\r\n..test\r\n"); - assert_eq!(escape_dot("test\r\n.\r\ntest").as_slice(), "test\r\n..\r\ntest"); - } -} diff --git a/src/extension.rs b/src/extension.rs index 4baac3c..7a62a55 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -14,7 +14,7 @@ use std::from_str::FromStr; use std::fmt::{Show, Formatter, Result}; -use common::{CRLF}; +use common::CRLF; use response::Response; /// Supported ESMTP keywords diff --git a/src/lib.rs b/src/lib.rs index 51ef7cd..7e6bddb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,3 +64,4 @@ pub mod response; pub mod transaction; pub mod common; pub mod error; +pub mod tools; diff --git a/src/response.rs b/src/response.rs index cfabac5..f9b0ef6 100644 --- a/src/response.rs +++ b/src/response.rs @@ -14,7 +14,7 @@ use std::from_str::FromStr; use std::fmt::{Show, Formatter, Result}; -use common::remove_trailing_crlf; +use tools::remove_trailing_crlf; /// Contains an SMTP reply, with separed code and message /// diff --git a/src/tools.rs b/src/tools.rs new file mode 100644 index 0000000..7916f92 --- /dev/null +++ b/src/tools.rs @@ -0,0 +1,147 @@ +// Copyright 2014 Alexis Mousset. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Contains tools for common string manipulations + +use std::string::String; +use std::str::replace; + +use common::{CR, LF, CRLF}; + +/// Adds quotes to emails if needed +#[inline] +pub fn quote_email_address(address: &str) -> String { + match address.len() { + 0 ... 1 => format!("<{}>", address), + _ => match (address.slice_to(1), + address.slice_from(address.len() - 1)) { + ("<", ">") => address.to_string(), + _ => format!("<{}>", address) + } + } +} + +/// Removes quotes from emails if needed +#[inline] +pub fn unquote_email_address(address: &str) -> &str { + match address.len() { + 0 ... 1 => address, + _ => match (address.slice_to(1), + address.slice_from(address.len() - 1)) { + ("<", ">") => address.slice(1, address.len() - 1), + _ => address + } + } +} + +/// Removes the trailing line return at the end of a string +#[inline] +pub fn remove_trailing_crlf(string: &str) -> &str { + if string.ends_with(CRLF) { + string.slice_to(string.len() - 2) + } else if string.ends_with(CR) { + string.slice_to(string.len() - 1) + } else { + string + } +} + +/// Returns the first word of a string, or the string if it contains no space +#[inline] +pub fn get_first_word(string: &str) -> &str { + string.split_str(CRLF).next().unwrap().splitn(1, ' ').next().unwrap() +} + +/// Returns the string replacing all the CRLF with "" +#[inline] +pub fn escape_crlf(string: &str) -> String { + replace(string, CRLF, "") +} + +/// Returns the string after adding a dot at the beginning of each line starting with a dot +/// +/// Reference : https://tools.ietf.org/html/rfc5321#page-62 (4.5.2. Transparency) +#[inline] +pub fn escape_dot(string: &str) -> String { + if string.starts_with(".") { + format!(".{}", string) + } else { + string.to_string() + }.replace(format!("{}.", CR).as_slice(), format!("{}..", CR).as_slice()) + .replace(format!("{}.", LF).as_slice(), format!("{}..", LF).as_slice()) +} + +#[cfg(test)] +mod test { + use super::{quote_email_address, unquote_email_address, + remove_trailing_crlf, get_first_word, escape_crlf, escape_dot}; + + #[test] + fn test_quote_email_address() { + assert_eq!(quote_email_address("address").as_slice(), "
"); + assert_eq!(quote_email_address("
").as_slice(), "
"); + assert_eq!(quote_email_address("a").as_slice(), ""); + assert_eq!(quote_email_address("").as_slice(), "<>"); + } + + #[test] + fn test_unquote_email_address() { + assert_eq!(unquote_email_address("
"), "address"); + assert_eq!(unquote_email_address("address"), "address"); + assert_eq!(unquote_email_address(""), ""); + assert_eq!(unquote_email_address("a"), "a"); + assert_eq!(unquote_email_address(""), ""); + } + + #[test] + fn test_remove_trailing_crlf() { + assert_eq!(remove_trailing_crlf("word"), "word"); + assert_eq!(remove_trailing_crlf("word\r\n"), "word"); + assert_eq!(remove_trailing_crlf("word\r\n "), "word\r\n "); + assert_eq!(remove_trailing_crlf("word\r"), "word"); + assert_eq!(remove_trailing_crlf("\r\n"), ""); + assert_eq!(remove_trailing_crlf("\r"), ""); + assert_eq!(remove_trailing_crlf("a"), "a"); + assert_eq!(remove_trailing_crlf(""), ""); + } + + #[test] + fn test_get_first_word() { + assert_eq!(get_first_word("first word"), "first"); + assert_eq!(get_first_word("first word\r\ntest"), "first"); + assert_eq!(get_first_word("first"), "first"); + assert_eq!(get_first_word(""), ""); + assert_eq!(get_first_word("\r\n"), ""); + assert_eq!(get_first_word("a\r\n"), "a"); + // Manage cases of empty line, spaces at the beginning + //assert_eq!(get_first_word(" a"), "a"); + //assert_eq!(get_first_word("\r\n a"), "a"); + assert_eq!(get_first_word(" \r\n"), ""); + assert_eq!(get_first_word("\r\n "), ""); + } + + #[test] + fn test_escape_crlf() { + assert_eq!(escape_crlf("\r\n").as_slice(), ""); + assert_eq!(escape_crlf("EHLO my_name\r\n").as_slice(), "EHLO my_name"); + assert_eq!( + escape_crlf("EHLO my_name\r\nSIZE 42\r\n").as_slice(), + "EHLO my_nameSIZE 42" + ); + } + + #[test] + fn test_escape_dot() { + assert_eq!(escape_dot(".test").as_slice(), "..test"); + assert_eq!(escape_dot("\r.\n.\r\n").as_slice(), "\r..\n..\r\n"); + assert_eq!(escape_dot("test\r\n.test\r\n").as_slice(), "test\r\n..test\r\n"); + assert_eq!(escape_dot("test\r\n.\r\ntest").as_slice(), "test\r\n..\r\ntest"); + } +}