feat(transport-smtp): Use a streaming parser for response

This commit is contained in:
Alexis Mousset
2020-05-09 15:12:29 +02:00
parent ce08d9e8aa
commit 0d063873fc
2 changed files with 34 additions and 30 deletions

View File

@@ -7,7 +7,7 @@ use crate::{
commands::*,
error::Error,
extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
response::Response,
response::{parse_response, Response},
},
Envelope,
};
@@ -312,36 +312,30 @@ impl SmtpConnection {
/// Gets the SMTP response
pub fn read_response(&mut self) -> Result<Response, Error> {
let mut raw_response = String::new();
let mut response = raw_response.parse::<Response>();
let mut buffer = String::with_capacity(100);
while response.is_err() {
if let Error::Parsing(nom::error::ErrorKind::Complete) =
response.as_ref().err().unwrap()
{
break;
while self.stream.read_line(&mut buffer)? > 0 {
#[cfg(feature = "log")]
debug!("<< {}", escape_crlf(&buffer));
match parse_response(&buffer) {
Ok((_remaining, response)) => {
if response.is_positive() {
return Ok(response);
}
return Err(response.into());
}
Err(nom::Err::Failure(e)) => {
return Err(Error::Parsing(e.1));
}
Err(nom::Err::Incomplete(_)) => { /* read more */ }
Err(nom::Err::Error(e)) => {
return Err(Error::Parsing(e.1));
}
}
// TODO read more than one line
let read_count = self.stream.read_line(&mut raw_response)?;
// EOF is reached
if read_count == 0 {
break;
}
response = raw_response.parse::<Response>();
}
#[cfg(feature = "log")]
debug!("Read: {}", escape_crlf(raw_response.as_ref()));
let final_response = response?;
if final_response.is_positive() {
Ok(final_response)
} else {
Err(From::from(final_response))
}
Err(io::Error::new(io::ErrorKind::Other, "incomplete").into())
}
}

View File

@@ -4,7 +4,7 @@
use crate::transport::smtp::Error;
use nom::{
branch::alt,
bytes::complete::{tag, take_until},
bytes::streaming::{tag, take_until},
combinator::{complete, map},
multi::many0,
sequence::{preceded, tuple},
@@ -226,7 +226,7 @@ fn parse_detail(i: &str) -> IResult<&str, Detail> {
))(i)
}
fn parse_response(i: &str) -> IResult<&str, Response> {
pub(crate) fn parse_response(i: &str) -> IResult<&str, Response> {
let (i, lines) = many0(tuple((
parse_code,
preceded(tag("-"), take_until("\r\n")),
@@ -262,7 +262,7 @@ fn parse_response(i: &str) -> IResult<&str, Response> {
#[cfg(test)]
mod test {
use super::{Category, Code, Detail, Response, Severity};
use super::*;
#[test]
fn test_severity_fmt() {
@@ -472,6 +472,16 @@ mod test {
);
}
#[test]
fn test_response_incomplete() {
let raw_response = "250-smtp.example.org\r\n";
let res = parse_response(raw_response);
match res {
Err(nom::Err::Incomplete(_)) => {}
_ => panic!("Expected incomplete response, got {:?}", res),
}
}
#[test]
fn test_response_first_line() {
assert_eq!(