feat(transport-smtp): Add support for LOGIN auth mechanism
This commit is contained in:
@@ -552,8 +552,8 @@ impl EmailBuilder {
|
||||
/// Sets the email body to plain text content
|
||||
pub fn set_text<S: Into<String>>(&mut self, body: S) {
|
||||
self.message.set_body(body);
|
||||
self.message
|
||||
.add_header(("Content-Type", format!("{}", mime!(Text/Plain; Charset=Utf8)).as_ref()));
|
||||
self.message.add_header(("Content-Type",
|
||||
format!("{}", mime!(Text/Plain; Charset=Utf8)).as_ref()));
|
||||
}
|
||||
|
||||
/// Sets the email body to HTML content
|
||||
@@ -565,8 +565,8 @@ impl EmailBuilder {
|
||||
/// Sets the email body to HTML content
|
||||
pub fn set_html<S: Into<String>>(&mut self, body: S) {
|
||||
self.message.set_body(body);
|
||||
self.message
|
||||
.add_header(("Content-Type", format!("{}", mime!(Text/Html; Charset=Utf8)).as_ref()));
|
||||
self.message.add_header(("Content-Type",
|
||||
format!("{}", mime!(Text/Html; Charset=Utf8)).as_ref()));
|
||||
}
|
||||
|
||||
/// Sets the email content
|
||||
@@ -646,9 +646,9 @@ impl EmailBuilder {
|
||||
let mut e = Envelope::new();
|
||||
// add all receivers in to_header and cc_header
|
||||
for receiver in self.to_header
|
||||
.iter()
|
||||
.chain(self.cc_header.iter())
|
||||
.chain(self.bcc_header.iter()) {
|
||||
.iter()
|
||||
.chain(self.cc_header.iter())
|
||||
.chain(self.bcc_header.iter()) {
|
||||
match *receiver {
|
||||
Address::Mailbox(ref m) => e.add_to(m.address.clone()),
|
||||
Address::Group(_, ref ms) => {
|
||||
@@ -691,8 +691,8 @@ impl EmailBuilder {
|
||||
self.message.add_header(Header::new_with_value("To".into(), self.to_header).unwrap());
|
||||
}
|
||||
if !self.from_header.is_empty() {
|
||||
self.message
|
||||
.add_header(Header::new_with_value("From".into(), self.from_header).unwrap());
|
||||
self.message.add_header(Header::new_with_value("From".into(), self.from_header)
|
||||
.unwrap());
|
||||
} else {
|
||||
return Err(Error::MissingFrom);
|
||||
}
|
||||
@@ -700,9 +700,9 @@ impl EmailBuilder {
|
||||
self.message.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap());
|
||||
}
|
||||
if !self.reply_to_header.is_empty() {
|
||||
self.message
|
||||
.add_header(Header::new_with_value("Reply-To".into(), self.reply_to_header)
|
||||
.unwrap());
|
||||
self.message.add_header(Header::new_with_value("Reply-To".into(),
|
||||
self.reply_to_header)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
if !self.date_issued {
|
||||
@@ -719,10 +719,10 @@ impl EmailBuilder {
|
||||
}
|
||||
|
||||
Ok(Email {
|
||||
message: self.message.build(),
|
||||
envelope: envelope,
|
||||
message_id: message_id,
|
||||
})
|
||||
message: self.message.build(),
|
||||
envelope: envelope,
|
||||
message_id: message_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,9 +816,9 @@ pub trait ExtractableEmail {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use email_format::{Header, MimeMessage};
|
||||
|
||||
use super::{Email, EmailBuilder, Envelope, IntoEmail, SendableEmail, SimpleEmail};
|
||||
use email_format::{Header, MimeMessage};
|
||||
use time::now;
|
||||
|
||||
use uuid::Uuid;
|
||||
@@ -865,12 +865,11 @@ mod test {
|
||||
email.message.headers.insert(Header::new_with_value("Message-ID".to_string(),
|
||||
format!("<{}@rust-smtp>",
|
||||
current_message))
|
||||
.unwrap());
|
||||
.unwrap());
|
||||
|
||||
email.message
|
||||
.headers
|
||||
.insert(Header::new_with_value("To".to_string(), "to@example.com".to_string())
|
||||
.unwrap());
|
||||
email.message.headers.insert(Header::new_with_value("To".to_string(),
|
||||
"to@example.com".to_string())
|
||||
.unwrap());
|
||||
|
||||
email.message.body = "body".to_string();
|
||||
|
||||
|
||||
@@ -31,12 +31,18 @@ impl EmailTransport<SendmailResult> for SendmailTransport {
|
||||
fn send<T: SendableEmail>(&mut self, email: T) -> SendmailResult {
|
||||
// Spawn the sendmail command
|
||||
let mut process = try!(Command::new(&self.command)
|
||||
.args(&["-i", "-f", &email.envelope().from, &email.envelope().to.join(" ")])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn());
|
||||
.args(&["-i",
|
||||
"-f",
|
||||
&email.envelope().from,
|
||||
&email.envelope().to.join(" ")])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn());
|
||||
|
||||
match process.stdin.as_mut().unwrap().write_all(email.message().as_bytes()) {
|
||||
match process.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(email.message().as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(error) => return Err(From::from(error)),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::mac::Mac;
|
||||
use crypto::md5::Md5;
|
||||
use rustc_serialize::base64::{self, FromBase64, ToBase64};
|
||||
use rustc_serialize::hex::ToHex;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -16,6 +15,10 @@ pub enum Mechanism {
|
||||
/// PLAIN authentication mechanism
|
||||
/// RFC 4616: https://tools.ietf.org/html/rfc4616
|
||||
Plain,
|
||||
/// LOGIN authentication mechanism
|
||||
/// Obsolete but needed for some providers (like office365)
|
||||
/// https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt
|
||||
Login,
|
||||
/// CRAM-MD5 authentication mechanism
|
||||
/// RFC 2195: https://tools.ietf.org/html/rfc2195
|
||||
CramMd5,
|
||||
@@ -27,6 +30,7 @@ impl Display for Mechanism {
|
||||
"{}",
|
||||
match *self {
|
||||
Mechanism::Plain => "PLAIN",
|
||||
Mechanism::Login => "LOGIN",
|
||||
Mechanism::CramMd5 => "CRAM-MD5",
|
||||
})
|
||||
}
|
||||
@@ -37,6 +41,7 @@ impl Mechanism {
|
||||
pub fn supports_initial_response(&self) -> bool {
|
||||
match *self {
|
||||
Mechanism::Plain => true,
|
||||
Mechanism::Login |
|
||||
Mechanism::CramMd5 => false,
|
||||
}
|
||||
}
|
||||
@@ -52,30 +57,35 @@ impl Mechanism {
|
||||
Mechanism::Plain => {
|
||||
match challenge {
|
||||
Some(_) => Err(Error::Client("This mechanism does not expect a challenge")),
|
||||
None => {
|
||||
Ok(format!("{}{}{}{}", NUL, username, NUL, password)
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD))
|
||||
}
|
||||
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password)),
|
||||
}
|
||||
}
|
||||
Mechanism::CramMd5 => {
|
||||
let encoded_challenge = match challenge {
|
||||
Mechanism::Login => {
|
||||
let decoded_challenge = match challenge {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::Client("This mechanism does expect a challenge")),
|
||||
};
|
||||
|
||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||
Ok(challenge) => challenge,
|
||||
Err(error) => return Err(Error::ChallengeParsing(error)),
|
||||
if vec!["User Name", "Username:", "Username"].contains(&decoded_challenge) {
|
||||
return Ok(username.to_string());
|
||||
}
|
||||
|
||||
if vec!["Password", "Password:"].contains(&decoded_challenge) {
|
||||
return Ok(password.to_string());
|
||||
}
|
||||
|
||||
Err(Error::Client("Unrecognized challenge"))
|
||||
}
|
||||
Mechanism::CramMd5 => {
|
||||
let decoded_challenge = match challenge {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::Client("This mechanism does expect a challenge")),
|
||||
};
|
||||
|
||||
let mut hmac = Hmac::new(Md5::new(), password.as_bytes());
|
||||
hmac.input(&decoded_challenge);
|
||||
hmac.input(decoded_challenge.as_bytes());
|
||||
|
||||
Ok(format!("{} {}", username, hmac.result().code().to_hex())
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD))
|
||||
Ok(format!("{} {}", username, hmac.result().code().to_hex()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,20 +100,30 @@ mod test {
|
||||
let mechanism = Mechanism::Plain;
|
||||
|
||||
assert_eq!(mechanism.response("username", "password", None).unwrap(),
|
||||
"AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||
"\u{0}username\u{0}password");
|
||||
assert!(mechanism.response("username", "password", Some("test")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login() {
|
||||
let mechanism = Mechanism::Login;
|
||||
|
||||
assert_eq!(mechanism.response("alice", "wonderland", Some("Username")).unwrap(),
|
||||
"alice");
|
||||
assert_eq!(mechanism.response("alice", "wonderland", Some("Password")).unwrap(),
|
||||
"wonderland");
|
||||
assert!(mechanism.response("username", "password", None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cram_md5() {
|
||||
let mechanism = Mechanism::CramMd5;
|
||||
|
||||
assert_eq!(mechanism.response("alice",
|
||||
"wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
|
||||
"wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
|
||||
.unwrap(),
|
||||
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
||||
assert!(mechanism.response("alice", "wonderland", Some("tést")).is_err());
|
||||
"alice a540ebe4ef2304070bbc3c456c1f64c0");
|
||||
assert!(mechanism.response("alice", "wonderland", None).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use bufstream::BufStream;
|
||||
use openssl::ssl::SslContext;
|
||||
|
||||
use rustc_serialize::base64::{self, FromBase64, ToBase64};
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::io::{BufRead, Read, Write};
|
||||
@@ -202,20 +204,47 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
if mechanism.supports_initial_response() {
|
||||
self.command(&format!("AUTH {} {}",
|
||||
mechanism,
|
||||
try!(mechanism.response(username, password, None))))
|
||||
try!(mechanism.response(username, password, None))
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD)))
|
||||
} else {
|
||||
let encoded_challenge = match try!(self.command("AUTH CRAM-MD5")).first_word() {
|
||||
let encoded_challenge = match try!(self.command(&format!("AUTH {}", mechanism)))
|
||||
.first_word() {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::ResponseParsing("Could not read CRAM challenge")),
|
||||
None => return Err(Error::ResponseParsing("Could not read auth challenge")),
|
||||
};
|
||||
|
||||
debug!("CRAM challenge: {}", encoded_challenge);
|
||||
debug!("auth encoded challenge: {}", encoded_challenge);
|
||||
|
||||
let cram_response = try!(mechanism.response(username,
|
||||
password,
|
||||
Some(&encoded_challenge)));
|
||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||
Ok(challenge) => {
|
||||
match String::from_utf8(challenge) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(Error::Utf8Parsing(error)),
|
||||
}
|
||||
}
|
||||
Err(error) => return Err(Error::ChallengeParsing(error)),
|
||||
};
|
||||
|
||||
self.command(&cram_response.clone())
|
||||
debug!("auth decoded challenge: {}", decoded_challenge);
|
||||
|
||||
let mut challenge_expected = 3;
|
||||
|
||||
while challenge_expected > 0 {
|
||||
let response = try!(self.command(&try!(mechanism.response(username,
|
||||
password,
|
||||
Some(&decoded_challenge)))
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD)));
|
||||
|
||||
if !response.has_code(334) {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
challenge_expected -= 1;
|
||||
}
|
||||
|
||||
Err(Error::ResponseParsing("Unexpected number of challenges"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +265,10 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
}
|
||||
|
||||
try!(write!(self.stream.as_mut().unwrap(), "{}{}", string, end));
|
||||
try!(self.stream.as_mut().unwrap().flush());
|
||||
try!(self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.flush());
|
||||
|
||||
debug!("Wrote: {}", escape_crlf(string));
|
||||
|
||||
@@ -249,13 +281,19 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
let mut parser = ResponseParser::default();
|
||||
|
||||
let mut line = String::new();
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
try!(self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read_line(&mut line));
|
||||
|
||||
debug!("Read: {}", escape_crlf(line.as_ref()));
|
||||
|
||||
while try!(parser.read_line(remove_crlf(line.as_ref()).as_ref())) {
|
||||
line.clear();
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
try!(self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read_line(&mut line));
|
||||
}
|
||||
|
||||
let response = try!(parser.response());
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//! Error and result type for SMTP clients
|
||||
|
||||
use rustc_serialize::base64::FromBase64Error;
|
||||
use self::Error::*;
|
||||
use rustc_serialize::base64::FromBase64Error;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::string::FromUtf8Error;
|
||||
use transport::smtp::response::{Response, Severity};
|
||||
|
||||
/// An enum of all error kinds.
|
||||
@@ -23,6 +24,8 @@ pub enum Error {
|
||||
ResponseParsing(&'static str),
|
||||
/// Error parsing a base64 string in response
|
||||
ChallengeParsing(FromBase64Error),
|
||||
/// Error parsing UTF8in response
|
||||
Utf8Parsing(FromUtf8Error),
|
||||
/// Internal client error
|
||||
Client(&'static str),
|
||||
/// DNS resolution error
|
||||
@@ -43,7 +46,8 @@ impl StdError for Error {
|
||||
Transient(_) => "a transient error occured during the SMTP transaction",
|
||||
Permanent(_) => "a permanent error occured during the SMTP transaction",
|
||||
ResponseParsing(_) => "an error occured while parsing an SMTP response",
|
||||
ChallengeParsing(_) => "an error occured while parsing a CRAM-MD5 challenge",
|
||||
ChallengeParsing(_) => "an error occured while parsing an SMTP AUTH challenge",
|
||||
Utf8Parsing(_) => "an error occured while parsing an SMTP response as UTF8",
|
||||
Resolution => "could not resolve hostname",
|
||||
Client(_) => "an unknown error occured",
|
||||
Io(_) => "an I/O error occured",
|
||||
|
||||
@@ -104,9 +104,9 @@ impl ServerInfo {
|
||||
}
|
||||
|
||||
Ok(ServerInfo {
|
||||
name: name,
|
||||
features: features,
|
||||
})
|
||||
name: name,
|
||||
features: features,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the server supports an ESMTP feature
|
||||
@@ -122,9 +122,9 @@ impl ServerInfo {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::{Extension, ServerInfo};
|
||||
use std::collections::HashSet;
|
||||
use transport::smtp::authentication::Mechanism;
|
||||
use transport::smtp::response::{Category, Code, Response, Severity};
|
||||
|
||||
|
||||
@@ -100,17 +100,17 @@ impl SmtpTransportBuilder {
|
||||
match addresses.next() {
|
||||
Some(addr) => {
|
||||
Ok(SmtpTransportBuilder {
|
||||
server_addr: addr,
|
||||
ssl_context: SslContext::builder(SslMethod::tls()).unwrap().build(),
|
||||
security_level: SecurityLevel::Opportunistic,
|
||||
smtp_utf8: false,
|
||||
credentials: None,
|
||||
connection_reuse_count_limit: 100,
|
||||
connection_reuse: false,
|
||||
hello_name: "localhost".to_string(),
|
||||
authentication_mechanism: None,
|
||||
timeout: Some(Duration::new(60, 0)),
|
||||
})
|
||||
server_addr: addr,
|
||||
ssl_context: SslContext::builder(SslMethod::tls()).unwrap().build(),
|
||||
security_level: SecurityLevel::Opportunistic,
|
||||
smtp_utf8: false,
|
||||
credentials: None,
|
||||
connection_reuse_count_limit: 100,
|
||||
connection_reuse: false,
|
||||
hello_name: "localhost".to_string(),
|
||||
authentication_mechanism: None,
|
||||
timeout: Some(Duration::new(60, 0)),
|
||||
})
|
||||
}
|
||||
None => Err(From::from("Could nor resolve hostname")),
|
||||
}
|
||||
@@ -313,10 +313,11 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
try!(self.get_ehlo());
|
||||
|
||||
match (&self.client_info.security_level,
|
||||
self.server_info.as_ref().unwrap().supports_feature(&Extension::StartTls)) {
|
||||
(&SecurityLevel::AlwaysEncrypt, false) => {
|
||||
return Err(From::from("Could not encrypt connection, aborting"))
|
||||
}
|
||||
self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(&Extension::StartTls)) {
|
||||
(&SecurityLevel::AlwaysEncrypt, false) => return Err(From::from("Could not encrypt connection, aborting")),
|
||||
(&SecurityLevel::Opportunistic, false) => (),
|
||||
(&SecurityLevel::NeverEncrypt, _) => (),
|
||||
(&SecurityLevel::EncryptedWrapper, _) => (),
|
||||
@@ -333,7 +334,10 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
}
|
||||
|
||||
if self.client_info.credentials.is_some() {
|
||||
let (username, password) = self.client_info.credentials.clone().unwrap();
|
||||
let (username, password) = self.client_info
|
||||
.credentials
|
||||
.clone()
|
||||
.unwrap();
|
||||
|
||||
let mut found = false;
|
||||
|
||||
@@ -344,7 +348,8 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
if self.client.is_encrypted() {
|
||||
// If encrypted, allow all mechanisms, with a preference for the
|
||||
// simplest
|
||||
vec![Mechanism::Plain, Mechanism::CramMd5]
|
||||
// Login is obsolete so try it last
|
||||
vec![Mechanism::Plain, Mechanism::CramMd5, Mechanism::Login]
|
||||
} else {
|
||||
// If not encrypted, do not allow clear-text passwords
|
||||
vec![Mechanism::CramMd5]
|
||||
@@ -353,7 +358,10 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
};
|
||||
|
||||
for mechanism in accepted_mechanisms {
|
||||
if self.server_info.as_ref().unwrap().supports_auth_mechanism(mechanism) {
|
||||
if self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_auth_mechanism(mechanism) {
|
||||
found = true;
|
||||
try_smtp!(self.client.auth(mechanism, &username, &password), self);
|
||||
break;
|
||||
@@ -368,13 +376,13 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
|
||||
// Mail
|
||||
let mail_options = match (self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(&Extension::EightBitMime),
|
||||
self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(&Extension::SmtpUtfEight)) {
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(&Extension::EightBitMime),
|
||||
self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(&Extension::SmtpUtfEight)) {
|
||||
(true, true) => Some("BODY=8BITMIME SMTPUTF8"),
|
||||
(true, false) => Some("BODY=8BITMIME"),
|
||||
(false, _) => None,
|
||||
@@ -409,12 +417,12 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
self.state.connection_reuse_count,
|
||||
message.len(),
|
||||
result.as_ref()
|
||||
.ok()
|
||||
.unwrap()
|
||||
.message()
|
||||
.iter()
|
||||
.next()
|
||||
.unwrap_or(&"no response".to_string()));
|
||||
.ok()
|
||||
.unwrap()
|
||||
.message()
|
||||
.iter()
|
||||
.next()
|
||||
.unwrap_or(&"no response".to_string()));
|
||||
}
|
||||
|
||||
// Test if we can reuse the existing connection
|
||||
|
||||
@@ -116,10 +116,10 @@ impl FromStr for Code {
|
||||
s[2..3].parse::<u8>()) {
|
||||
(Ok(severity), Ok(category), Ok(detail)) => {
|
||||
Ok(Code {
|
||||
severity: severity,
|
||||
category: category,
|
||||
detail: detail,
|
||||
})
|
||||
severity: severity,
|
||||
category: category,
|
||||
detail: detail,
|
||||
})
|
||||
}
|
||||
_ => Err(Error::ResponseParsing("Could not parse response code")),
|
||||
}
|
||||
@@ -217,8 +217,7 @@ impl Response {
|
||||
/// Tells if the response is positive
|
||||
pub fn is_positive(&self) -> bool {
|
||||
match self.code.severity {
|
||||
PositiveCompletion => true,
|
||||
PositiveIntermediate => true,
|
||||
PositiveCompletion | PositiveIntermediate => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -401,7 +400,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.is_positive());
|
||||
.is_positive());
|
||||
assert!(!Response::new(Code {
|
||||
severity: "5".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
@@ -410,7 +409,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.is_positive());
|
||||
.is_positive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -423,7 +422,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.message(),
|
||||
.message(),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]);
|
||||
let empty_message: Vec<String> = vec![];
|
||||
assert_eq!(Response::new(Code {
|
||||
@@ -432,7 +431,7 @@ mod test {
|
||||
detail: 1,
|
||||
},
|
||||
vec![])
|
||||
.message(),
|
||||
.message(),
|
||||
empty_message);
|
||||
}
|
||||
|
||||
@@ -446,7 +445,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.severity(),
|
||||
.severity(),
|
||||
Severity::PositiveCompletion);
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "5".parse::<Severity>().unwrap(),
|
||||
@@ -456,7 +455,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.severity(),
|
||||
.severity(),
|
||||
Severity::PermanentNegativeCompletion);
|
||||
}
|
||||
|
||||
@@ -470,7 +469,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.category(),
|
||||
.category(),
|
||||
Category::Unspecified4);
|
||||
}
|
||||
|
||||
@@ -484,7 +483,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.detail(),
|
||||
.detail(),
|
||||
1);
|
||||
}
|
||||
|
||||
@@ -498,7 +497,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.code(),
|
||||
.code(),
|
||||
"241");
|
||||
}
|
||||
|
||||
@@ -512,7 +511,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.has_code(241));
|
||||
.has_code(241));
|
||||
assert!(!Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
@@ -521,7 +520,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.has_code(251));
|
||||
.has_code(251));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -534,7 +533,7 @@ mod test {
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.first_word(),
|
||||
.first_word(),
|
||||
Some("me".to_string()));
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
@@ -544,7 +543,7 @@ mod test {
|
||||
vec!["me mo".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.first_word(),
|
||||
.first_word(),
|
||||
Some("me".to_string()));
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
@@ -552,7 +551,7 @@ mod test {
|
||||
detail: 1,
|
||||
},
|
||||
vec![])
|
||||
.first_word(),
|
||||
.first_word(),
|
||||
None);
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
@@ -560,7 +559,7 @@ mod test {
|
||||
detail: 1,
|
||||
},
|
||||
vec![" ".to_string()])
|
||||
.first_word(),
|
||||
.first_word(),
|
||||
None);
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
@@ -568,7 +567,7 @@ mod test {
|
||||
detail: 1,
|
||||
},
|
||||
vec![" ".to_string()])
|
||||
.first_word(),
|
||||
.first_word(),
|
||||
None);
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
@@ -576,7 +575,7 @@ mod test {
|
||||
detail: 1,
|
||||
},
|
||||
vec!["".to_string()])
|
||||
.first_word(),
|
||||
.first_word(),
|
||||
None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user