From 22b2dad57f3bf442924b3dbbb13dc02bbd2f0ce9 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Tue, 4 Nov 2014 17:50:44 +0100 Subject: [PATCH] Add transparency for . --- src/client/mod.rs | 6 +++--- src/client/stream.rs | 14 +++++++++----- src/command.rs | 17 +++++++---------- src/common.rs | 36 ++++++++++++++++++++++++++++++------ 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 0ce8b8a..3675b2a 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -19,7 +19,7 @@ use response::Response; use extension; use command; use command::Command; -use common::{SMTP_PORT, CRLF}; +use common::SMTP_PORT; use transaction; use transaction::TransactionState; use client::connecter::Connecter; @@ -175,12 +175,12 @@ impl Client { if !self.state.is_command_possible(command.clone()) { panic!("Bad command sequence"); } - self.stream.clone().unwrap().send_and_get_response(format!("{}", command).as_slice()) + self.stream.clone().unwrap().send_and_get_response(format!("{}", command).as_slice(), false) } /// Sends the email content fn send_message(&mut self, message: String) -> Response { - self.stream.clone().unwrap().send_and_get_response(format!("{}{}.{}", message, CRLF, CRLF).as_slice()) + self.stream.clone().unwrap().send_and_get_response(format!("{}", message).as_slice(), true) } /// Connects to the configured server diff --git a/src/client/stream.rs b/src/client/stream.rs index a6dc084..ce1ff93 100644 --- a/src/client/stream.rs +++ b/src/client/stream.rs @@ -6,14 +6,14 @@ use std::str::from_utf8; use std::vec::Vec; use response::Response; -use common::escape_crlf; +use common::{escape_crlf, escape_dot, CRLF}; static BUFFER_SIZE: uint = 1024; /// TODO pub trait ClientStream { /// TODO - fn send_and_get_response(&mut self, string: &str) -> Response; + fn send_and_get_response(&mut self, string: &str, is_message: bool) -> Response; /// TODO fn get_reply(&mut self) -> Option; /// TODO @@ -22,9 +22,13 @@ pub trait ClientStream { impl ClientStream for TcpStream { /// Sends a complete message or a command to the server and get the response - fn send_and_get_response(&mut self, string: &str) -> Response { - match self.write_str(format!("{}", string).as_slice()) { - Ok(..) => debug!("Wrote: {}", escape_crlf(string.to_string())), + fn send_and_get_response(&mut self, string: &str, is_message: bool) -> Response { + let end = match is_message { + true => format!("{}.{}", CRLF, CRLF), + false => format!("{}", CRLF) + }; + match self.write_str(format!("{}{}", escape_dot(string.to_string()), end).as_slice()) { + Ok(..) => debug!("Wrote: {}", escape_crlf(escape_dot(string.to_string()))), Err(..) => panic!("Could not write to stream") } diff --git a/src/command.rs b/src/command.rs index 5f5c011..a45dd69 100644 --- a/src/command.rs +++ b/src/command.rs @@ -12,7 +12,7 @@ //! Represents a valid complete SMTP command, ready to be sent to a server use std::fmt::{Show, Formatter, Result}; -use common::{SP, CRLF}; +use common::SP; /// Supported SMTP commands /// @@ -49,7 +49,7 @@ pub enum Command { impl Show for Command { fn fmt(&self, f: &mut Formatter) -> Result { - let mut line = match *self { + f.write( match *self { Connect => "CONNECT".to_string(), ExtendedHello(ref my_hostname) => format!("EHLO {}", my_hostname.clone()), @@ -78,9 +78,7 @@ impl Show for Command { format!("HELP {}", argument.clone()), Noop => "NOOP".to_string(), Quit => "QUIT".to_string(), - }; - line.push_str(CRLF); - f.write(line.as_bytes()) + }.as_bytes()) } } @@ -107,25 +105,24 @@ impl Command { #[cfg(test)] mod test { use command; - use common::CRLF; #[test] fn test_fmt() { assert_eq!( format!("{}", command::Noop), - format!("NOOP{}", CRLF) + format!("NOOP") ); assert_eq!( format!("{}", command::ExtendedHello("my_name".to_string())), - format!("EHLO my_name{}", CRLF) + format!("EHLO my_name") ); assert_eq!( format!("{}", command::Mail("test".to_string(), Some(vec!("option".to_string())))), - format!("MAIL FROM: option{}", CRLF) + format!("MAIL FROM: option") ); assert_eq!( format!("{}", command::Mail("test".to_string(), Some(vec!("option".to_string(), "option2".to_string())))), - format!("MAIL FROM: option option2{}", CRLF) + format!("MAIL FROM: option option2") ); } diff --git a/src/common.rs b/src/common.rs index 7873bfa..e487f66 100644 --- a/src/common.rs +++ b/src/common.rs @@ -25,6 +25,8 @@ pub static SP: &'static str = " "; /// The line ending for SMTP transactions pub static CRLF: &'static str = "\r\n"; +pub static CR: &'static str = "\r"; +pub static LF: &'static str = "\n"; /// Adds quotes to emails if needed pub fn quote_email_address(address: String) -> String { @@ -66,14 +68,27 @@ pub fn get_first_word(string: String) -> String { string.as_slice().split_str(CRLF).next().unwrap().splitn(1, ' ').next().unwrap().to_string() } -/// TODO +/// Returns the string replacing all the CRLF with "" +#[inline] pub fn escape_crlf(string: String) -> String { - string.replace(CRLF, "") + string.replace(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: String) -> String { + if string.len() > 0 && string.chars().next().unwrap() == '.' { + format!(".{}", string) + } else { + 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::CRLF; + use super::{CRLF, CR, LF}; #[test] fn test_quote_email_address() { @@ -122,8 +137,17 @@ mod test { #[test] fn test_escape_crlf() { - assert_eq!(super::escape_crlf(format!("{}", CRLF)), "".to_string()); - assert_eq!(super::escape_crlf(format!("EHLO my_name{}", CRLF)), "EHLO my_name".to_string()); - assert_eq!(super::escape_crlf(format!("EHLO my_name{}SIZE 42{}", CRLF, CRLF)), "EHLO my_nameSIZE 42".to_string()); + assert_eq!(super::escape_crlf(format!("{}", CRLF)), "".to_string()); + assert_eq!(super::escape_crlf(format!("EHLO my_name{}", CRLF)), "EHLO my_name".to_string()); + assert_eq!(super::escape_crlf(format!("EHLO my_name{}SIZE 42{}", CRLF, CRLF)), "EHLO my_nameSIZE 42".to_string()); + } + + #[test] + fn test_escape_dot() { + assert_eq!(super::escape_dot(".test".to_string()), "..test".to_string()); + assert_eq!(super::escape_dot(format!("{}.{}.{}", CR, LF, CRLF)), format!("{}..{}..{}", CR, LF, CRLF)); + assert_eq!(super::escape_dot(format!("{}.{}.{}", CRLF, CRLF, CRLF)), format!("{}..{}..{}", CRLF, CRLF, CRLF)); + assert_eq!(super::escape_dot(format!("test{}.test{}", CRLF, CRLF)), format!("test{}..test{}", CRLF, CRLF)); + assert_eq!(super::escape_dot(format!("test{}.{}test", CRLF, CRLF)), format!("test{}..{}test", CRLF, CRLF)); } }