Renaming structures of the smtp library

This commit is contained in:
Alexis Mousset
2014-05-14 19:16:57 +02:00
parent aa67048bb4
commit 24acc2d34c
2 changed files with 172 additions and 69 deletions

View File

@@ -19,11 +19,11 @@ use std::io::net::ip::Port;
use std::io::net::tcp::TcpStream;
use common::{resolve_host, get_first_word, unquote_email_address};
use smtp::smtp_response::SmtpResponse;
use smtp::esmtp_parameter;
use smtp::esmtp_parameter::EsmtpParameter;
use smtp::smtp_command;
use smtp::smtp_command::SmtpCommand;
use smtp::response::SmtpResponse;
use smtp::extension;
use smtp::extension::SmtpExtension;
use smtp::command;
use smtp::command::SmtpCommand;
use smtp::{SMTP_PORT, CRLF};
/// Information about an SMTP server
@@ -32,7 +32,7 @@ struct SmtpServerInfo<T> {
/// Server name
name: T,
/// ESMTP features supported by the server
esmtp_features: Option<Vec<EsmtpParameter>>
esmtp_features: Option<Vec<SmtpExtension>>
}
impl<T: Show> Show for SmtpServerInfo<T>{
@@ -53,12 +53,12 @@ impl<T: Str> SmtpServerInfo<T> {
/// Parses supported ESMTP features
///
/// TODO: Improve parsing
fn parse_esmtp_response(message: T) -> Option<Vec<EsmtpParameter>> {
fn parse_esmtp_response(message: T) -> Option<Vec<SmtpExtension>> {
let mut esmtp_features = Vec::new();
for line in message.as_slice().split_str(CRLF) {
match from_str::<SmtpResponse<StrBuf>>(line) {
Some(SmtpResponse{code: 250, message: message}) => {
match from_str::<EsmtpParameter>(message.unwrap().into_owned()) {
match from_str::<SmtpExtension>(message.unwrap().into_owned()) {
Some(keyword) => esmtp_features.push(keyword),
None => ()
}
@@ -73,11 +73,11 @@ impl<T: Str> SmtpServerInfo<T> {
}
/// Checks if the server supports an ESMTP feature
fn supports_feature(&self, keyword: EsmtpParameter) -> Result<EsmtpParameter, ()> {
fn supports_feature(&self, keyword: SmtpExtension) -> Result<SmtpExtension, ()> {
match self.esmtp_features.clone() {
Some(esmtp_features) => {
for feature in esmtp_features.iter() {
if keyword.same_keyword_as(*feature) {
if keyword.same_extension_as(*feature) {
return Ok(*feature);
}
}
@@ -224,7 +224,7 @@ impl SmtpClient<StrBuf, TcpStream> {
// Checks message encoding according to the server's capability
// TODO : Add an encoding check.
if ! self.server_info.clone().unwrap().supports_feature(esmtp_parameter::EightBitMime).is_ok() {
if ! self.server_info.clone().unwrap().supports_feature(extension::EightBitMime).is_ok() {
if ! message.clone().into_owned().is_ascii() {
self.smtp_fail("Server does not accepts UTF-8 strings");
}
@@ -327,7 +327,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
pub fn helo(&mut self, my_hostname: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!(vec!(Connected));
match self.send_command(smtp_command::Hello(my_hostname.clone())).with_code(vec!(250)) {
match self.send_command(command::Hello(my_hostname.clone())).with_code(vec!(250)) {
Ok(response) => {
self.server_info = Some(
SmtpServerInfo{
@@ -346,7 +346,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
pub fn ehlo(&mut self, my_hostname: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!(vec!(Unconnected));
match self.send_command(smtp_command::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) {
match self.send_command(command::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) {
Ok(response) => {
self.server_info = Some(
SmtpServerInfo{
@@ -365,7 +365,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
pub fn mail(&mut self, from_address: StrBuf, options: Option<Vec<StrBuf>>) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!(vec!(HeloSent));
match self.send_command(smtp_command::Mail(StrBuf::from_str(unquote_email_address(from_address.to_owned())), options)).with_code(vec!(250)) {
match self.send_command(command::Mail(StrBuf::from_str(unquote_email_address(from_address.to_owned())), options)).with_code(vec!(250)) {
Ok(response) => {
self.state = MailSent;
Ok(response)
@@ -380,7 +380,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
pub fn rcpt(&mut self, to_address: StrBuf, options: Option<Vec<StrBuf>>) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!(vec!(MailSent, RcptSent));
match self.send_command(smtp_command::Recipient(StrBuf::from_str(unquote_email_address(to_address.to_owned())), options)).with_code(vec!(250)) {
match self.send_command(command::Recipient(StrBuf::from_str(unquote_email_address(to_address.to_owned())), options)).with_code(vec!(250)) {
Ok(response) => {
self.state = RcptSent;
Ok(response)
@@ -395,7 +395,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
pub fn data(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!(vec!(RcptSent));
match self.send_command(smtp_command::Data).with_code(vec!(354)) {
match self.send_command(command::Data).with_code(vec!(354)) {
Ok(response) => {
self.state = DataSent;
Ok(response)
@@ -424,7 +424,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a QUIT command
pub fn quit(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!(vec!(Unconnected));
match self.send_command(smtp_command::Quit).with_code(vec!(221)) {
match self.send_command(command::Quit).with_code(vec!(221)) {
Ok(response) => {
Ok(response)
},
@@ -437,7 +437,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a RSET command
pub fn rset(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!(vec!(Unconnected));
match self.send_command(smtp_command::Reset).with_code(vec!(250)) {
match self.send_command(command::Reset).with_code(vec!(250)) {
Ok(response) => {
if vec!(MailSent, RcptSent, DataSent).contains(&self.state) {
self.state = HeloSent;
@@ -453,13 +453,13 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a NOOP commands
pub fn noop(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!(vec!(Unconnected));
self.send_command(smtp_command::Noop).with_code(vec!(250))
self.send_command(command::Noop).with_code(vec!(250))
}
/// Sends a VRFY command
pub fn vrfy(&mut self, to_address: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!(vec!(Unconnected));
self.send_command(smtp_command::Verify(to_address)).with_code(vec!(250))
self.send_command(command::Verify(to_address)).with_code(vec!(250))
}
}
@@ -498,17 +498,17 @@ impl<T, S: Writer + Clone> Writer for SmtpClient<T, S> {
#[cfg(test)]
mod test {
use super::SmtpServerInfo;
use smtp::esmtp_parameter;
use smtp::extension;
#[test]
fn test_smtp_server_info_fmt() {
assert_eq!(format!("{}", SmtpServerInfo{
name: "name",
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime))
esmtp_features: Some(vec!(extension::EightBitMime))
}), "name with [8BITMIME]".to_owned());
assert_eq!(format!("{}", SmtpServerInfo{
name: "name",
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime, esmtp_parameter::Size(42)))
esmtp_features: Some(vec!(extension::EightBitMime, extension::Size(42)))
}), "name with [8BITMIME, SIZE=42]".to_owned());
assert_eq!(format!("{}", SmtpServerInfo{
name: "name",
@@ -519,13 +519,13 @@ mod test {
#[test]
fn test_smtp_server_info_parse_esmtp_response() {
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 SIZE 42"),
Some(vec!(esmtp_parameter::EightBitMime, esmtp_parameter::Size(42))));
Some(vec!(extension::EightBitMime, extension::Size(42))));
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 UNKNON 42"),
Some(vec!(esmtp_parameter::EightBitMime)));
Some(vec!(extension::EightBitMime)));
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-9BITMIME\r\n250 SIZE a"),
None);
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-SIZE 42\r\n250 SIZE 43"),
Some(vec!(esmtp_parameter::Size(42), esmtp_parameter::Size(43))));
Some(vec!(extension::Size(42), extension::Size(43))));
assert_eq!(SmtpServerInfo::parse_esmtp_response(""),
None);
}
@@ -534,19 +534,19 @@ mod test {
fn test_smtp_server_info_supports_feature() {
assert_eq!(SmtpServerInfo{
name: "name",
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime))
}.supports_feature(esmtp_parameter::EightBitMime), Ok(esmtp_parameter::EightBitMime));
esmtp_features: Some(vec!(extension::EightBitMime))
}.supports_feature(extension::EightBitMime), Ok(extension::EightBitMime));
assert_eq!(SmtpServerInfo{
name: "name",
esmtp_features: Some(vec!(esmtp_parameter::Size(42), esmtp_parameter::EightBitMime))
}.supports_feature(esmtp_parameter::EightBitMime), Ok(esmtp_parameter::EightBitMime));
esmtp_features: Some(vec!(extension::Size(42), extension::EightBitMime))
}.supports_feature(extension::EightBitMime), Ok(extension::EightBitMime));
assert_eq!(SmtpServerInfo{
name: "name",
esmtp_features: Some(vec!(esmtp_parameter::Size(42), esmtp_parameter::EightBitMime))
}.supports_feature(esmtp_parameter::Size(0)), Ok(esmtp_parameter::Size(42)));
esmtp_features: Some(vec!(extension::Size(42), extension::EightBitMime))
}.supports_feature(extension::Size(0)), Ok(extension::Size(42)));
assert!(SmtpServerInfo{
name: "name",
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime))
}.supports_feature(esmtp_parameter::Size(42)).is_err());
esmtp_features: Some(vec!(extension::EightBitMime))
}.supports_feature(extension::Size(42)).is_err());
}
}

View File

@@ -23,7 +23,7 @@ pub static SP: &'static str = " ";
pub static CRLF: &'static str = "\r\n";
/// A module
pub mod smtp_command {
pub mod command {
use std::fmt::{Show, Formatter, Result};
/// Supported SMTP commands
@@ -55,7 +55,6 @@ pub mod smtp_command {
Noop,
/// Quit command
Quit,
}
impl<T: Show + Str> Show for SmtpCommand<T> {
@@ -90,13 +89,13 @@ pub mod smtp_command {
}
/// This is a module
pub mod esmtp_parameter {
pub mod extension {
use std::from_str::FromStr;
use std::fmt::{Show, Formatter, Result};
/// Supported ESMTP keywords
#[deriving(Eq,Clone)]
pub enum EsmtpParameter {
pub enum SmtpExtension {
/// 8BITMIME keyword
///
/// RFC 6152 : https://tools.ietf.org/html/rfc6152
@@ -107,7 +106,7 @@ pub mod esmtp_parameter {
Size(uint)
}
impl Show for EsmtpParameter {
impl Show for SmtpExtension {
fn fmt(&self, f: &mut Formatter) -> Result {
f.buf.write(
match self {
@@ -118,8 +117,8 @@ pub mod esmtp_parameter {
}
}
impl FromStr for EsmtpParameter {
fn from_str(s: &str) -> Option<EsmtpParameter> {
impl FromStr for SmtpExtension {
fn from_str(s: &str) -> Option<SmtpExtension> {
let splitted : Vec<&str> = s.splitn(' ', 1).collect();
match splitted.len() {
1 => match *splitted.get(0) {
@@ -135,9 +134,9 @@ pub mod esmtp_parameter {
}
}
impl EsmtpParameter {
impl SmtpExtension {
/// Checks if the ESMTP keyword is the same
pub fn same_keyword_as(&self, other: EsmtpParameter) -> bool {
pub fn same_extension_as(&self, other: SmtpExtension) -> bool {
if *self == other {
return true;
}
@@ -149,7 +148,7 @@ pub mod esmtp_parameter {
}
}
/// This is a module
pub mod smtp_response {
pub mod response {
use std::from_str::FromStr;
use std::fmt::{Show, Formatter, Result};
use common::remove_trailing_crlf;
@@ -224,55 +223,142 @@ pub mod smtp_response {
}
}
/// a module
pub mod transaction_state {
use std::fmt;
use std::fmt::{Show, Formatter};
use super::command;
use super::command::SmtpCommand;
/// Contains the state of the current transaction
#[deriving(Eq,Clone)]
pub enum TransactionState {
/// The connection was successful and the banner was received
OutOfTransaction,
/// An HELO or EHLO was successful
HelloSent,
/// A MAIL command was successful send
MailSent,
/// At least one RCPT command was sucessful
RecipientSent,
/// A DATA command was successful
DataSent
}
impl Show for TransactionState {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.buf.write(
match *self {
OutOfTransaction => "OutOfTransaction",
HelloSent => "HelloSent",
MailSent => "MailSent",
RecipientSent => "RecipientSent",
DataSent => "DataSent"
}.as_bytes()
)
}
}
impl TransactionState {
/// bla bla
pub fn is_command_possible<T>(&self, command: SmtpCommand<T>) -> bool {
match (*self, command) {
// Only the message content can be sent in this state
(DataSent, _) => false,
// Commands that can be issued everytime
(_, command::ExtendedHello(_)) => true,
(_, command::Hello(_)) => true,
(_, command::Reset) => true,
(_, command::Verify(_)) => true,
(_, command::Expand(_)) => true,
(_, command::Help(_)) => true,
(_, command::Noop) => true,
(_, command::Quit) => true,
// Commands that require a particular state
(HelloSent, command::Mail(_, _)) => true,
(MailSent, command::Recipient(_, _)) => true,
(RecipientSent, command::Recipient(_, _)) => true,
(RecipientSent, command::Data) => true,
// Everything else
(_, _) => false
}
}
/// a method
pub fn next_state<T>(&mut self, command: SmtpCommand<T>) -> Option<TransactionState> {
match (*self, command) {
(DataSent, _) => None,
// Commands that can be issued everytime
(_, command::ExtendedHello(_)) => Some(HelloSent),
(_, command::Hello(_)) => Some(HelloSent),
(_, command::Reset) => Some(OutOfTransaction),
(state, command::Verify(_)) => Some(state),
(state, command::Expand(_)) => Some(state),
(state, command::Help(_)) => Some(state),
(state, command::Noop) => Some(state),
(_, command::Quit) => Some(OutOfTransaction),
// Commands that require a particular state
(HelloSent, command::Mail(_, _)) => Some(MailSent),
(MailSent, command::Recipient(_, _)) => Some(RecipientSent),
(RecipientSent, command::Recipient(_, _)) => Some(RecipientSent),
(RecipientSent, command::Data) => Some(DataSent),
// Everything else
(_, _) => None
}
}
}
}
#[cfg(test)]
mod test {
use super::smtp_response::SmtpResponse;
use super::esmtp_parameter;
use super::esmtp_parameter::EsmtpParameter;
use super::smtp_command;
use super::smtp_command::SmtpCommand;
use super::response::SmtpResponse;
use super::extension;
use super::extension::SmtpExtension;
use super::command;
use super::command::SmtpCommand;
use super::transaction_state;
#[test]
fn test_command_fmt() {
let noop: SmtpCommand<StrBuf> = smtp_command::Noop;
let noop: SmtpCommand<StrBuf> = command::Noop;
assert_eq!(format!("{}", noop), "NOOP".to_owned());
assert_eq!(format!("{}", smtp_command::ExtendedHello("me")), "EHLO me".to_owned());
assert_eq!(format!("{}", command::ExtendedHello("me")), "EHLO me".to_owned());
assert_eq!(format!("{}",
smtp_command::Mail("test", Some(vec!("option")))), "MAIL FROM:<test> option".to_owned()
command::Mail("test", Some(vec!("option")))), "MAIL FROM:<test> option".to_owned()
);
}
#[test]
fn test_esmtp_parameter_same_keyword_as() {
assert_eq!(esmtp_parameter::EightBitMime.same_keyword_as(esmtp_parameter::EightBitMime), true);
assert_eq!(esmtp_parameter::Size(42).same_keyword_as(esmtp_parameter::Size(42)), true);
assert_eq!(esmtp_parameter::Size(42).same_keyword_as(esmtp_parameter::Size(43)), true);
assert_eq!(esmtp_parameter::Size(42).same_keyword_as(esmtp_parameter::EightBitMime), false);
fn test_extension_same_extension_as() {
assert_eq!(extension::EightBitMime.same_extension_as(extension::EightBitMime), true);
assert_eq!(extension::Size(42).same_extension_as(extension::Size(42)), true);
assert_eq!(extension::Size(42).same_extension_as(extension::Size(43)), true);
assert_eq!(extension::Size(42).same_extension_as(extension::EightBitMime), false);
}
#[test]
fn test_esmtp_parameter_fmt() {
assert_eq!(format!("{}", esmtp_parameter::EightBitMime), "8BITMIME".to_owned());
assert_eq!(format!("{}", esmtp_parameter::Size(42)), "SIZE=42".to_owned());
fn test_extension_fmt() {
assert_eq!(format!("{}", extension::EightBitMime), "8BITMIME".to_owned());
assert_eq!(format!("{}", extension::Size(42)), "SIZE=42".to_owned());
}
#[test]
fn test_esmtp_parameter_from_str() {
assert_eq!(from_str::<EsmtpParameter>("8BITMIME"), Some(esmtp_parameter::EightBitMime));
assert_eq!(from_str::<EsmtpParameter>("SIZE 42"), Some(esmtp_parameter::Size(42)));
assert_eq!(from_str::<EsmtpParameter>("SIZ 42"), None);
assert_eq!(from_str::<EsmtpParameter>("SIZE 4a2"), None);
fn test_extension_from_str() {
assert_eq!(from_str::<SmtpExtension>("8BITMIME"), Some(extension::EightBitMime));
assert_eq!(from_str::<SmtpExtension>("SIZE 42"), Some(extension::Size(42)));
assert_eq!(from_str::<SmtpExtension>("SIZ 42"), None);
assert_eq!(from_str::<SmtpExtension>("SIZE 4a2"), None);
// TODO: accept trailing spaces ?
assert_eq!(from_str::<EsmtpParameter>("SIZE 42 "), None);
assert_eq!(from_str::<SmtpExtension>("SIZE 42 "), None);
}
#[test]
fn test_smtp_response_fmt() {
fn test_response_fmt() {
assert_eq!(format!("{}", SmtpResponse{code: 200, message: Some("message")}), "200 message".to_owned());
}
#[test]
fn test_smtp_response_from_str() {
fn test_response_from_str() {
assert_eq!(from_str::<SmtpResponse<StrBuf>>("200 response message"),
Some(SmtpResponse{
code: 200,
@@ -312,7 +398,7 @@ mod test {
}
#[test]
fn test_smtp_response_with_code() {
fn test_response_with_code() {
assert_eq!(SmtpResponse{code: 200, message: Some("message")}.with_code(vec!(200)),
Ok(SmtpResponse{code: 200, message: Some("message")}));
assert_eq!(SmtpResponse{code: 400, message: Some("message")}.with_code(vec!(200)),
@@ -320,4 +406,21 @@ mod test {
assert_eq!(SmtpResponse{code: 200, message: Some("message")}.with_code(vec!(200, 300)),
Ok(SmtpResponse{code: 200, message: Some("message")}));
}
#[test]
fn test_transaction_state_is_command_possible() {
let noop: SmtpCommand<StrBuf> = command::Noop;
assert!(transaction_state::OutOfTransaction.is_command_possible(noop.clone()));
assert!(! transaction_state::DataSent.is_command_possible(noop));
assert!(transaction_state::HelloSent.is_command_possible(command::Mail("", None)));
assert!(! transaction_state::MailSent.is_command_possible(command::Mail("", None)));
}
#[test]
fn test_transaction_state_next_state() {
let noop: SmtpCommand<StrBuf> = command::Noop;
assert_eq!(transaction_state::MailSent.next_state(noop), Some(transaction_state::MailSent));
assert_eq!(transaction_state::HelloSent.next_state(command::Mail("", None)), Some(transaction_state::MailSent));
assert_eq!(transaction_state::MailSent.next_state(command::Mail("", None)), None);
}
}