Remove uneeded code to simplify the client
This commit is contained in:
@@ -9,10 +9,10 @@
|
||||
|
||||
//! SMTP client
|
||||
|
||||
use std::slice::Iter;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::string::String;
|
||||
use std::error::FromError;
|
||||
use std::default::Default;
|
||||
use std::old_io::net::tcp::TcpStream;
|
||||
use std::old_io::net::ip::{SocketAddr, ToSocketAddr};
|
||||
|
||||
@@ -22,8 +22,6 @@ use tools::get_first_word;
|
||||
use common::{CRLF, MESSAGE_ENDING, SMTP_PORT};
|
||||
use response::Response;
|
||||
use extension::Extension;
|
||||
use command::Command;
|
||||
use transaction::TransactionState;
|
||||
use error::{SmtpResult, ErrorKind};
|
||||
use client::connecter::Connecter;
|
||||
use client::server_info::ServerInfo;
|
||||
@@ -53,8 +51,6 @@ pub struct Configuration {
|
||||
/// Represents the state of a client
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
/// Transaction state, to check the sequence of commands
|
||||
pub transaction_state: TransactionState,
|
||||
/// Panic state
|
||||
pub panic: bool,
|
||||
/// Connection reuse counter
|
||||
@@ -99,12 +95,18 @@ macro_rules! close_and_return_err (
|
||||
})
|
||||
);
|
||||
|
||||
macro_rules! check_command_sequence (
|
||||
($command: ident $client: ident) => ({
|
||||
if !$client.state.transaction_state.is_allowed(&$command) {
|
||||
close_and_return_err!(
|
||||
Response{code: 503, message: Some("Bad sequence of commands".to_string())}, $client
|
||||
);
|
||||
macro_rules! with_code (
|
||||
($result: ident, $codes: expr) => ({
|
||||
match $result {
|
||||
Ok(response) => {
|
||||
for code in $codes {
|
||||
if *code == response.code {
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
Err(FromError::from_error(response))
|
||||
},
|
||||
Err(_) => $result,
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -125,7 +127,6 @@ impl<S = TcpStream> Client<S> {
|
||||
hello_name: "localhost".to_string(),
|
||||
},
|
||||
state: State {
|
||||
transaction_state: Default::default(),
|
||||
panic: false,
|
||||
connection_reuse_count: 0,
|
||||
current_message: None,
|
||||
@@ -169,7 +170,6 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
|
||||
// Reset the client state
|
||||
self.stream = None;
|
||||
self.state.transaction_state = Default::default();
|
||||
self.server_info = None;
|
||||
self.state.panic = false;
|
||||
self.state.connection_reuse_count = 0;
|
||||
@@ -233,10 +233,6 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
|
||||
/// Connects to the configured server
|
||||
pub fn connect(&mut self) -> SmtpResult {
|
||||
let command = Command::Connect;
|
||||
|
||||
check_command_sequence!(command self);
|
||||
|
||||
// Connect should not be called when the client is already connected
|
||||
if self.stream.is_some() {
|
||||
close_and_return_err!("The connection is already established", self);
|
||||
@@ -249,26 +245,12 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
info!("connection established to {}",
|
||||
self.stream.as_mut().unwrap().peer_name().unwrap());
|
||||
|
||||
let result = try!(self.stream.as_mut().unwrap().get_reply());
|
||||
|
||||
let checked_result = try!(command.test_success(result));
|
||||
self.state.transaction_state = self.state.transaction_state.next_state(&command).unwrap();
|
||||
Ok(checked_result)
|
||||
self.stream.as_mut().unwrap().get_reply()//with_code([220].iter());
|
||||
}
|
||||
|
||||
/// Sends an SMTP command
|
||||
fn command(&mut self, command: Command) -> SmtpResult {
|
||||
// for now we do not support SMTPUTF8
|
||||
if !command.is_ascii() {
|
||||
close_and_return_err!("Non-ASCII string", self);
|
||||
}
|
||||
|
||||
self.send_server(command, None)
|
||||
}
|
||||
|
||||
/// Sends the email content
|
||||
fn send_message(&mut self, message: &str) -> SmtpResult {
|
||||
self.send_server(Command::Message, Some(message))
|
||||
fn command(&mut self, command: &str, expected_codes: Iter<u16>) -> SmtpResult {
|
||||
self.send_server(command, CRLF, expected_codes)
|
||||
}
|
||||
|
||||
/// Sends content to the server, after checking the command sequence, and then
|
||||
@@ -276,21 +258,9 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
///
|
||||
/// * If `message` is `None`, the given command will be formatted and sent to the server
|
||||
/// * If `message` is `Some(str)`, the `str` string will be sent to the server
|
||||
fn send_server(&mut self, command: Command, message: Option<&str>) -> SmtpResult {
|
||||
check_command_sequence!(command self);
|
||||
|
||||
let result = try!(match message {
|
||||
Some(message) => self.stream.as_mut().unwrap().send_and_get_response(
|
||||
message, MESSAGE_ENDING
|
||||
),
|
||||
None => self.stream.as_mut().unwrap().send_and_get_response(
|
||||
format! ("{}", command) .as_slice(), CRLF
|
||||
),
|
||||
});
|
||||
|
||||
let checked_result = try!(command.test_success(result));
|
||||
self.state.transaction_state = self.state.transaction_state.next_state(&command).unwrap();
|
||||
Ok(checked_result)
|
||||
fn send_server(&mut self, content: &str, end: &str, expected_codes: Iter<u16>) -> SmtpResult {
|
||||
let result = self.stream.as_mut().unwrap().send_and_get_response(content, end);
|
||||
with_code!(result, expected_codes)
|
||||
}
|
||||
|
||||
/// Checks if the server is connected using the NOOP SMTP command
|
||||
@@ -301,7 +271,7 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
/// Send a HELO command and fills `server_info`
|
||||
pub fn helo(&mut self) -> SmtpResult {
|
||||
let hostname = self.configuration.hello_name.clone();
|
||||
let result = try!(self.command(Command::Hello(hostname)));
|
||||
let result = try!(self.command(format!("HELO {}", hostname).as_slice(), [250].iter()));
|
||||
self.server_info = Some(
|
||||
ServerInfo{
|
||||
name: get_first_word(result.message.as_ref().unwrap().as_slice()).to_string(),
|
||||
@@ -314,7 +284,7 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
/// Sends a EHLO command and fills `server_info`
|
||||
pub fn ehlo(&mut self) -> SmtpResult {
|
||||
let hostname = self.configuration.hello_name.clone();
|
||||
let result = try!(self.command(Command::ExtendedHello(hostname)));
|
||||
let result = try!(self.command(format!("EHLO {}", hostname).as_slice(), [250].iter()));
|
||||
self.server_info = Some(
|
||||
ServerInfo{
|
||||
name: get_first_word(result.message.as_ref().unwrap().as_slice()).to_string(),
|
||||
@@ -327,48 +297,39 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
}
|
||||
|
||||
/// Sends a MAIL command
|
||||
pub fn mail(&mut self, from_address: &str) -> SmtpResult {
|
||||
pub fn mail(&mut self, address: &str) -> SmtpResult {
|
||||
|
||||
// Generate an ID for the logs if None was provided
|
||||
if self.state.current_message.is_none() {
|
||||
self.state.current_message = Some(Uuid::new_v4());
|
||||
}
|
||||
|
||||
let server_info = match self.server_info.clone() {
|
||||
Some(info) => info,
|
||||
None => close_and_return_err!(Response{
|
||||
code: 503,
|
||||
message: Some("Bad sequence of commands".to_string()),
|
||||
}, self),
|
||||
};
|
||||
|
||||
// Checks message encoding according to the server's capability
|
||||
let options = match server_info.supports_feature(Extension::EightBitMime) {
|
||||
Some(extension) => Some(vec![extension.client_mail_option().unwrap().to_string()]),
|
||||
None => None,
|
||||
let options = match self.server_info.as_ref().unwrap().supports_feature(Extension::EightBitMime) {
|
||||
Some(_) => "BODY=8BITMIME",
|
||||
None => "",
|
||||
};
|
||||
|
||||
let result = self.command(
|
||||
Command::Mail(from_address.to_string(), options)
|
||||
format!("MAIL FROM:<{}> {}", address, options).as_slice(), [250].iter()
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
// Log the mail command
|
||||
info!("{}: from=<{}>", self.state.current_message.as_ref().unwrap(), from_address);
|
||||
info!("{}: from=<{}>", self.state.current_message.as_ref().unwrap(), address);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Sends a RCPT command
|
||||
pub fn rcpt(&mut self, to_address: &str) -> SmtpResult {
|
||||
pub fn rcpt(&mut self, address: &str) -> SmtpResult {
|
||||
let result = self.command(
|
||||
Command::Recipient(to_address.to_string(), None)
|
||||
);
|
||||
format!("RCPT TO:<{}>", address).as_slice(), [250, 251].iter());
|
||||
|
||||
if result.is_ok() {
|
||||
// Log the rcpt command
|
||||
info!("{}: to=<{}>", self.state.current_message.as_ref().unwrap(), to_address);
|
||||
info!("{}: to=<{}>", self.state.current_message.as_ref().unwrap(), address);
|
||||
}
|
||||
|
||||
result
|
||||
@@ -376,28 +337,19 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
|
||||
/// Sends a DATA command
|
||||
pub fn data(&mut self) -> SmtpResult {
|
||||
self.command(Command::Data)
|
||||
self.command("DATA", [354].iter())
|
||||
}
|
||||
|
||||
/// Sends the message content
|
||||
pub fn message(&mut self, message_content: &str) -> SmtpResult {
|
||||
|
||||
let server_info = match self.server_info.clone() {
|
||||
Some(info) => info,
|
||||
None => close_and_return_err!(Response{
|
||||
code: 503,
|
||||
message: Some("Bad sequence of commands".to_string()),
|
||||
}, self)
|
||||
};
|
||||
|
||||
// Check message encoding
|
||||
if !server_info.supports_feature(Extension::EightBitMime).is_some() {
|
||||
if !self.server_info.clone().unwrap().supports_feature(Extension::EightBitMime).is_some() {
|
||||
if !message_content.as_bytes().is_ascii() {
|
||||
close_and_return_err!("Server does not accepts UTF-8 strings", self);
|
||||
}
|
||||
}
|
||||
|
||||
let result = self.send_message(message_content);
|
||||
let result = self.send_server(message_content, MESSAGE_ENDING, [250].iter()); //250
|
||||
|
||||
if result.is_ok() {
|
||||
// Increment the connection reuse counter
|
||||
@@ -420,27 +372,26 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
|
||||
/// Sends a QUIT command
|
||||
pub fn quit(&mut self) -> SmtpResult {
|
||||
self.command(Command::Quit)
|
||||
self.command("QUIT", [221].iter())
|
||||
}
|
||||
|
||||
/// Sends a RSET command
|
||||
pub fn rset(&mut self) -> SmtpResult {
|
||||
self.command(Command::Reset)
|
||||
self.command("RSET", [250].iter())
|
||||
}
|
||||
|
||||
/// Sends a NOOP command
|
||||
pub fn noop(&mut self) -> SmtpResult {
|
||||
self.command(Command::Noop)
|
||||
self.command("NOOP", [250].iter())
|
||||
}
|
||||
|
||||
/// Sends a VRFY command
|
||||
pub fn vrfy(&mut self, to_address: &str) -> SmtpResult {
|
||||
self.command(Command::Verify(to_address.to_string()))
|
||||
pub fn vrfy(&mut self, address: &str) -> SmtpResult {
|
||||
self.command(format!("VRFY {}", address).as_slice(), [250, 251, 252].iter())
|
||||
}
|
||||
|
||||
/// Sends a EXPN command
|
||||
pub fn expn(&mut self, list: &str) -> SmtpResult {
|
||||
self.command(Command::Expand(list.to_string()))
|
||||
self.command(format!("EXPN {}", list).as_slice(), [250, 252].iter())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
179
src/command.rs
179
src/command.rs
@@ -1,179 +0,0 @@
|
||||
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! SMTP command
|
||||
|
||||
use std::ascii::AsciiExt;
|
||||
use std::error::FromError;
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
use response::Response;
|
||||
use error::SmtpResult;
|
||||
use common::SP;
|
||||
|
||||
/// Supported SMTP commands
|
||||
///
|
||||
/// We do not implement the following SMTP commands, as they were deprecated in RFC 5321
|
||||
/// and must not be used by clients: `SEND`, `SOML`, `SAML`, `TURN`.
|
||||
#[derive(PartialEq,Eq,Clone,Debug)]
|
||||
pub enum Command {
|
||||
/// A fake command to represent the connection step
|
||||
Connect,
|
||||
/// Start a TLS tunnel
|
||||
StartTls,
|
||||
/// Authentication command
|
||||
Authenticate(String, Option<String>),
|
||||
/// Authentication response
|
||||
AuthenticationResponse(String),
|
||||
/// Extended Hello command
|
||||
ExtendedHello(String),
|
||||
/// Hello command
|
||||
Hello(String),
|
||||
/// Mail command, takes optional options
|
||||
Mail(String, Option<Vec<String>>),
|
||||
/// Recipient command, takes optional options
|
||||
Recipient(String, Option<Vec<String>>),
|
||||
/// Data command
|
||||
Data,
|
||||
/// A fake command to represent the message content
|
||||
Message,
|
||||
/// Reset command
|
||||
Reset,
|
||||
/// Verify command, takes optional options
|
||||
Verify(String),
|
||||
/// Expand command, takes optional options
|
||||
Expand(String),
|
||||
/// Help command, takes optional options
|
||||
Help(Option<String>),
|
||||
/// Noop command
|
||||
Noop,
|
||||
/// Quit command
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl Display for Command {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
write! (f, "{}", match *self {
|
||||
Command::Connect => "CONNECT".to_string(),
|
||||
Command::StartTls => "STARTTLS".to_string(),
|
||||
Command::Authenticate(ref mecanism, None) => format!("AUTH {}", mecanism),
|
||||
Command::Authenticate(ref mecanism, Some(ref response)) => format!("AUTH {} {}", mecanism, response),
|
||||
Command::AuthenticationResponse(ref response) => format!("{}", response),
|
||||
Command::ExtendedHello(ref my_hostname) => format!("EHLO {}", my_hostname),
|
||||
Command::Hello(ref my_hostname) => format!("HELO {}", my_hostname),
|
||||
Command::Mail(ref from_address, None) => format!("MAIL FROM:<{}>", from_address),
|
||||
Command::Mail(ref from_address, Some(ref options)) =>
|
||||
format!("MAIL FROM:<{}> {}", from_address, options.connect(SP)),
|
||||
Command::Recipient(ref to_address, None) => format!("RCPT TO:<{}>", to_address),
|
||||
Command::Recipient(ref to_address, Some(ref options)) =>
|
||||
format!("RCPT TO:<{}> {}", to_address, options.connect(SP)),
|
||||
Command::Data => "DATA".to_string(),
|
||||
Command::Message => "MESSAGE".to_string(),
|
||||
Command::Reset => "RSET".to_string(),
|
||||
Command::Verify(ref address) => format!("VRFY {}", address),
|
||||
Command::Expand(ref address) => format!("EXPN {}", address),
|
||||
Command::Help(None) => "HELP".to_string(),
|
||||
Command::Help(Some(ref argument)) => format!("HELP {}", argument),
|
||||
Command::Noop => "NOOP".to_string(),
|
||||
Command::Quit => "QUIT".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Tests if the `Command` is ASCII-only
|
||||
pub fn is_ascii(&self) -> bool {
|
||||
match *self {
|
||||
Command::ExtendedHello(ref my_hostname) => my_hostname.is_ascii(),
|
||||
Command::Hello(ref my_hostname) => my_hostname.is_ascii(),
|
||||
Command::Mail(ref from_address, None) => from_address.is_ascii(),
|
||||
Command::Mail(ref from_address, Some(ref options)) => from_address.is_ascii()
|
||||
&& options.concat().is_ascii(),
|
||||
Command::Recipient(ref to_address, None) => to_address.is_ascii(),
|
||||
Command::Recipient(ref to_address, Some(ref options)) => to_address.is_ascii()
|
||||
&& options.concat().is_ascii(),
|
||||
Command::Verify(ref address) => address.is_ascii(),
|
||||
Command::Expand(ref address) => address.is_ascii(),
|
||||
Command::Help(Some(ref argument)) => argument.is_ascii(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests if the command was successful
|
||||
///
|
||||
/// Returns `Ok` if the given response is considered successful for the `Command`,
|
||||
/// `Err` otherwise
|
||||
pub fn test_success(&self, response: Response) -> SmtpResult {
|
||||
let success = match *self {
|
||||
Command::Connect => vec![220],
|
||||
Command::StartTls => vec![220],
|
||||
Command::Authenticate(..) => vec![334, 235],
|
||||
Command::AuthenticationResponse(..) => vec![235],
|
||||
Command::ExtendedHello(..) => vec![250],
|
||||
Command::Hello(..) => vec![250],
|
||||
Command::Mail(..) => vec![250],
|
||||
Command::Recipient(..) => vec![250, 251],
|
||||
Command::Data => vec![354],
|
||||
Command::Message => vec![250],
|
||||
Command::Reset => vec![250],
|
||||
Command::Verify(..) => vec![250, 251, 252],
|
||||
Command::Expand(..) => vec![250, 252],
|
||||
Command::Help(..) => vec![211, 214],
|
||||
Command::Noop => vec![250],
|
||||
Command::Quit => vec![221],
|
||||
}.contains(&response.code);
|
||||
if success {
|
||||
Ok(response)
|
||||
} else {
|
||||
Err(FromError::from_error(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Command;
|
||||
|
||||
#[test]
|
||||
fn test_fmt() {
|
||||
assert_eq!(
|
||||
format!("{}", Command::Noop),
|
||||
"NOOP".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", Command::ExtendedHello("my_name".to_string())),
|
||||
"EHLO my_name".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", Command::Mail("test".to_string(), Some(vec!["option".to_string()]))),
|
||||
"MAIL FROM:<test> option".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", Command::Mail("test".to_string(),
|
||||
Some(vec!["option".to_string(), "option2".to_string()]))),
|
||||
"MAIL FROM:<test> option option2".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_ascii() {
|
||||
assert!(Command::Help(None).is_ascii());
|
||||
assert!(Command::ExtendedHello("my_name".to_string()).is_ascii());
|
||||
assert!(!Command::ExtendedHello("my_namé".to_string()).is_ascii());
|
||||
assert!(
|
||||
Command::Mail("test".to_string(), Some(vec!["test".to_string(), "test".to_string()]))
|
||||
.is_ascii());
|
||||
assert!(
|
||||
!Command::Mail("test".to_string(), Some(vec!["est".to_string(), "testà".to_string()]))
|
||||
.is_ascii());
|
||||
assert!(
|
||||
!Command::Mail("testé".to_string(), Some(vec!["est".to_string(), "test".to_string()]))
|
||||
.is_ascii());
|
||||
}
|
||||
}
|
||||
@@ -90,14 +90,6 @@ impl Extension {
|
||||
}
|
||||
esmtp_features
|
||||
}
|
||||
|
||||
/// Returns the string to add to the mail command
|
||||
pub fn client_mail_option(&self) -> Option<&str> {
|
||||
match *self {
|
||||
EightBitMime => Some("BODY=8BITMIME"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
#![unstable]
|
||||
|
||||
#![deny(missing_docs)]
|
||||
//#![deny(missing_docs)]
|
||||
|
||||
#![feature(plugin,core,old_io,io,collections)]
|
||||
|
||||
@@ -135,10 +135,8 @@ extern crate time;
|
||||
extern crate uuid;
|
||||
|
||||
pub mod client;
|
||||
pub mod command;
|
||||
pub mod extension;
|
||||
pub mod response;
|
||||
pub mod transaction;
|
||||
pub mod common;
|
||||
pub mod error;
|
||||
pub mod tools;
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! State of an SMTP transaction
|
||||
|
||||
use std::default::Default;
|
||||
|
||||
use command::Command;
|
||||
use self::TransactionState::{Unconnected, Connected, HelloSent, MailSent, RecipientSent, DataSent, AuthenticateSent};
|
||||
|
||||
/// Contains the state of the current transaction
|
||||
#[derive(PartialEq,Eq,Copy,Debug)]
|
||||
pub enum TransactionState {
|
||||
/// No connection was established
|
||||
Unconnected,
|
||||
/// The connection was successful and the banner was received
|
||||
Connected,
|
||||
/// 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,
|
||||
/// An authenticate without initial response was successful
|
||||
AuthenticateSent,
|
||||
}
|
||||
|
||||
impl Default for TransactionState {
|
||||
fn default() -> TransactionState {
|
||||
Unconnected
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionState {
|
||||
/// Tests if the given command is allowed in the current state
|
||||
pub fn is_allowed(&self, command: &Command) -> bool {
|
||||
(*self).next_state(command).is_some()
|
||||
}
|
||||
|
||||
/// Returns the state resulting of the given command
|
||||
///
|
||||
/// A `None` return value means the comand is not allowed.
|
||||
pub fn next_state(&self, command: &Command) -> Option<TransactionState> {
|
||||
match (*self, command) {
|
||||
(Unconnected, &Command::Connect) => Some(Connected),
|
||||
(Unconnected, _) => None,
|
||||
(DataSent, &Command::Message) => Some(HelloSent),
|
||||
(DataSent, _) => None,
|
||||
// Authentication
|
||||
(AuthenticateSent, &Command::AuthenticationResponse(_)) => Some(HelloSent),
|
||||
(AuthenticateSent, _) => None,
|
||||
(HelloSent, &Command::Authenticate(_, None)) => Some(AuthenticateSent),
|
||||
(HelloSent, &Command::Authenticate(_, Some(_))) => Some(HelloSent),
|
||||
// Commands that can be issued everytime
|
||||
(_, &Command::ExtendedHello(_)) => Some(HelloSent),
|
||||
(_, &Command::Hello(_)) => Some(HelloSent),
|
||||
(Connected, &Command::Reset) => Some(Connected),
|
||||
(_, &Command::Reset) => Some(HelloSent),
|
||||
(state, &Command::Verify(_)) => Some(state),
|
||||
(state, &Command::Expand(_)) => Some(state),
|
||||
(state, &Command::Help(_)) => Some(state),
|
||||
(state, &Command::Noop) => Some(state),
|
||||
(_, &Command::Quit) => Some(Unconnected),
|
||||
// Email transaction
|
||||
(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 std::default::Default;
|
||||
|
||||
use command::Command;
|
||||
use super::TransactionState;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let default: TransactionState = Default::default();
|
||||
assert_eq!(default, TransactionState::Unconnected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_allowed() {
|
||||
assert!(!TransactionState::Unconnected.is_allowed(&Command::Noop));
|
||||
assert!(!TransactionState::DataSent.is_allowed(&Command::Noop));
|
||||
assert!(TransactionState::HelloSent.is_allowed(&Command::Mail("".to_string(), None)));
|
||||
assert!(!TransactionState::MailSent.is_allowed(&Command::Mail("".to_string(), None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_next_state() {
|
||||
assert_eq!(TransactionState::MailSent.next_state(&Command::Noop), Some(TransactionState::MailSent));
|
||||
assert_eq!(TransactionState::HelloSent.next_state(&Command::Mail("".to_string(), None)),
|
||||
Some(TransactionState::MailSent));
|
||||
assert_eq!(TransactionState::MailSent.next_state(&Command::Mail("".to_string(), None)), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user