From 658e7937d7a2b5ecc7b76edfdd2d960952c33316 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sat, 14 Mar 2015 15:38:23 +0100 Subject: [PATCH] Add tests for Extension and Response --- Cargo.toml | 2 +- src/client/authentication.rs | 2 +- src/client/mod.rs | 45 ++++++++++++- src/error.rs | 5 ++ src/extension.rs | 29 +++++---- src/lib.rs | 18 +++++- src/mailer/address.rs | 2 +- src/mailer/header.rs | 2 +- src/mailer/mod.rs | 2 +- src/response.rs | 119 +++++++++++++++++++++++++++++++++++ src/tools.rs | 102 ------------------------------ 11 files changed, 205 insertions(+), 123 deletions(-) delete mode 100644 src/tools.rs diff --git a/Cargo.toml b/Cargo.toml index 06e43ca..a2f6c02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "smtp" -version = "0.0.7" +version = "0.0.8" description = "Simple SMTP client" readme = "README.md" documentation = "http://amousset.github.io/rust-smtp/smtp/" diff --git a/src/client/authentication.rs b/src/client/authentication.rs index ce96e5e..9a070ce 100644 --- a/src/client/authentication.rs +++ b/src/client/authentication.rs @@ -15,7 +15,7 @@ use crypto::hmac::Hmac; use crypto::md5::Md5; use crypto::mac::Mac; -use tools::NUL; +use NUL; /// Returns a PLAIN mecanism response pub fn plain(username: &str, password: &str) -> String { diff --git a/src/client/mod.rs b/src/client/mod.rs index 1fa4ac3..89381fe 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -15,16 +15,34 @@ use std::net::TcpStream; use std::net::{SocketAddr, ToSocketAddrs}; use std::io::{BufRead, BufStream, Read, Write}; -use tools::{CRLF, MESSAGE_ENDING}; -use tools::{escape_dot, escape_crlf}; use response::{Response, Severity, Category}; use error::SmtpResult; use client::connecter::Connecter; use client::authentication::{plain, cram_md5}; +use {CRLF, MESSAGE_ENDING}; pub mod connecter; mod authentication; +/// 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] +fn escape_dot(string: &str) -> String { + if string.starts_with(".") { + format!(".{}", string) + } else { + string.to_string() + }.replace("\r.", "\r..") + .replace("\n.", "\n..") +} + +/// Returns the string replacing all the CRLF with "\" +#[inline] +fn escape_crlf(string: &str) -> String { + string.replace(CRLF, "") +} + /// Structure that implements the SMTP client pub struct Client { /// TCP stream between client and server @@ -210,3 +228,26 @@ impl Client { } } } + +#[cfg(test)] +mod test { + use super::{escape_dot, escape_crlf}; + + #[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"); + } + + #[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" + ); + } +} diff --git a/src/error.rs b/src/error.rs index 5009c87..ff935d8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -83,3 +83,8 @@ impl FromError<&'static str> for SmtpError { /// SMTP result type pub type SmtpResult = Result; + +#[cfg(test)] +mod test { + // TODO +} diff --git a/src/extension.rs b/src/extension.rs index 8f80498..84db303 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -79,6 +79,7 @@ impl Extension { #[cfg(test)] mod test { use super::Extension; + use response::{Severity, Category, Response}; #[test] fn test_from_str() { @@ -89,17 +90,19 @@ mod test { assert_eq!(Extension::from_str("AUTH DIGEST-MD5 PLAIN CRAM-MD5"), Ok(vec![Extension::PlainAuthentication, Extension::CramMd5Authentication])); } - // #[test] - // fn test_parse_esmtp_response() { - // assert_eq!(Extension::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 SIZE 42"), - // vec![Extension::EightBitMime]); - // assert_eq!(Extension::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 AUTH PLAIN CRAM-MD5\r\n250 UNKNON 42"), - // vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]); - // assert_eq!(Extension::parse_esmtp_response("me\r\n250-9BITMIME\r\n250 SIZE a"), - // vec![]); - // assert_eq!(Extension::parse_esmtp_response("me\r\n250-SIZE 42\r\n250 SIZE 43"), - // vec![]); - // assert_eq!(Extension::parse_esmtp_response(""), - // vec![]); - // } + #[test] + fn test_parse_esmtp_response() { + assert_eq!(Extension::parse_esmtp_response(&Response::new( + "2".parse::().unwrap(), + "2".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + )), vec![Extension::EightBitMime]); + assert_eq!(Extension::parse_esmtp_response(&Response::new( + "4".parse::().unwrap(), + "3".parse::().unwrap(), + 3, + vec!["me".to_string(), "8BITMIME".to_string(), "AUTH PLAIN CRAM-MD5".to_string()] + )), vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]); + } } diff --git a/src/lib.rs b/src/lib.rs index fdf1780..5cb48ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,6 @@ extern crate crypto; extern crate time; extern crate uuid; -mod tools; mod extension; pub mod client; pub mod sender; @@ -169,3 +168,20 @@ pub static SMTPS_PORT: u16 = 465; /// Default submission port pub static SUBMISSION_PORT: u16 = 587; + +// Useful strings and characters + +/// The word separator for SMTP transactions +pub static SP: &'static str = " "; + +/// The line ending for SMTP transactions (carriage return + line feed) +pub static CRLF: &'static str = "\r\n"; + +/// Colon +pub static COLON: &'static str = ":"; + +/// The ending of message content +pub static MESSAGE_ENDING: &'static str = "\r\n.\r\n"; + +/// NUL unicode character +pub static NUL: &'static str = "\0"; diff --git a/src/mailer/address.rs b/src/mailer/address.rs index 43f59a7..d5b8311 100644 --- a/src/mailer/address.rs +++ b/src/mailer/address.rs @@ -11,7 +11,7 @@ use std::fmt::{Display, Formatter, Result}; -use tools::SP; +use SP; /// Converts an adress or an address with an alias to an `Address` pub trait ToAddress { diff --git a/src/mailer/header.rs b/src/mailer/header.rs index ec91ccc..1246a4b 100644 --- a/src/mailer/header.rs +++ b/src/mailer/header.rs @@ -13,8 +13,8 @@ use time::Tm; use std::fmt::{Display, Formatter, Result}; -use tools::{SP, COLON}; use mailer::address::Address; +use {COLON, SP}; /// Converts to an `Header` pub trait ToHeader { diff --git a/src/mailer/mod.rs b/src/mailer/mod.rs index f6d0b73..299f883 100644 --- a/src/mailer/mod.rs +++ b/src/mailer/mod.rs @@ -15,8 +15,8 @@ use time::{now, Tm}; use mailer::header::{ToHeader, Header}; use mailer::address::ToAddress; -use tools::CRLF; use sendable_email::SendableEmail; +use CRLF; pub mod header; pub mod address; diff --git a/src/response.rs b/src/response.rs index 4ba679d..d25883a 100644 --- a/src/response.rs +++ b/src/response.rs @@ -222,4 +222,123 @@ mod test { fn test_category_fmt() { assert_eq!(format!("{}", Category::Unspecified4).as_slice(), "4"); } + + #[test] + fn test_response_new() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ), Response { + severity: Severity::PositiveCompletion, + category: Category::Unspecified4, + detail: 1, + message: vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()], + }); + } + + #[test] + fn test_response_is_positive() { + assert!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).is_positive()); + assert!(! Response::new( + "4".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).is_positive()); + } + + #[test] + fn test_response_message() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).message(), vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]); + } + + #[test] + fn test_response_severity() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).severity(), Severity::PositiveCompletion); + } + + #[test] + fn test_response_category() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).category(), Category::Unspecified4); + } + + #[test] + fn test_response_detail() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).detail(), 1); + } + + #[test] + fn test_response_code() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).code(), "241"); + } + + #[test] + fn test_response_has_code() { + assert!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).has_code(241)); + assert!(! Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).has_code(251)); + } + + #[test] + fn test_response_first_word() { + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).first_word(), Some("me".to_string())); + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec!["me mo".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()] + ).first_word(), Some("me".to_string())); + assert_eq!(Response::new( + "2".parse::().unwrap(), + "4".parse::().unwrap(), + 1, + vec![] + ).first_word(), None); + } } diff --git a/src/tools.rs b/src/tools.rs deleted file mode 100644 index f5b8a57..0000000 --- a/src/tools.rs +++ /dev/null @@ -1,102 +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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Tools for common string manipulations - -use std::string::String; - -/// The word separator for SMTP transactions -pub static SP: &'static str = " "; - -/// The line ending for SMTP transactions (carriage return + line feed) -pub static CRLF: &'static str = "\r\n"; - -/// Carriage return -pub static CR: &'static str = "\r"; - -/// Line feed -pub static LF: &'static str = "\n"; - -/// Colon -pub static COLON: &'static str = ":"; - -/// The ending of message content -pub static MESSAGE_ENDING: &'static str = "\r\n.\r\n"; - -/// NUL unicode character -pub static NUL: &'static str = "\0"; - -/// Returns the first word of a string, or the string if it contains no space -#[inline] -pub fn get_first_word(string: &str) -> &str { - match string.lines_any().next() { - Some(line) => match line.words().next() { - Some(word) => word, - None => "", - }, - None => "", - } -} - -/// Returns the string replacing all the CRLF with "\" -#[inline] -pub fn escape_crlf(string: &str) -> String { - 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: &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::*; - - #[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"); - } -}