chore(transport): Use nom 5.0 with functions (#360)

This commit is contained in:
Alexis Mousset
2019-09-18 23:12:36 +00:00
committed by GitHub
parent bf2adcabed
commit 5bc1cba2eb
5 changed files with 98 additions and 99 deletions

View File

@@ -21,7 +21,7 @@ is-it-maintained-open-issues = { repository = "lettre/lettre" }
[dependencies] [dependencies]
log = "^0.4" log = "^0.4"
nom = { version = "^4.0", optional = true } nom = { version = "^5.0", optional = true }
bufstream = { version = "^0.1", optional = true } bufstream = { version = "^0.1", optional = true }
native-tls = { version = "^0.2", optional = true } native-tls = { version = "^0.2", optional = true }
base64 = { version = "^0.10", optional = true } base64 = { version = "^0.10", optional = true }

View File

@@ -7,7 +7,6 @@ use crate::smtp::error::{Error, SmtpResult};
use crate::smtp::response::Response; use crate::smtp::response::Response;
use bufstream::BufStream; use bufstream::BufStream;
use log::debug; use log::debug;
use nom::ErrorKind as NomErrorKind;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::io::{self, BufRead, BufReader, Read, Write}; use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
@@ -250,7 +249,9 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
let mut response = raw_response.parse::<Response>(); let mut response = raw_response.parse::<Response>();
while response.is_err() { while response.is_err() {
if response.as_ref().err().unwrap() != &NomErrorKind::Complete { if let Error::Parsing(nom::error::ErrorKind::Complete) =
response.as_ref().err().unwrap()
{
break; break;
} }
// TODO read more than one line // TODO read more than one line

View File

@@ -37,7 +37,7 @@ pub enum Error {
/// TLS error /// TLS error
Tls(native_tls::Error), Tls(native_tls::Error),
/// Parsing error /// Parsing error
Parsing(nom::ErrorKind), Parsing(nom::error::ErrorKind),
} }
impl Display for Error { impl Display for Error {
@@ -94,9 +94,13 @@ impl From<native_tls::Error> for Error {
} }
} }
impl From<nom::ErrorKind> for Error { impl From<nom::Err<(&str, nom::error::ErrorKind)>> for Error {
fn from(err: nom::ErrorKind) -> Error { fn from(err: nom::Err<(&str, nom::error::ErrorKind)>) -> Error {
Parsing(err) Parsing(match err {
nom::Err::Incomplete(_) => nom::error::ErrorKind::Complete,
nom::Err::Failure((_, k)) => k,
nom::Err::Error((_, k)) => k,
})
} }
} }

View File

@@ -1,11 +1,18 @@
//! SMTP response, containing a mandatory return code and an optional text //! SMTP response, containing a mandatory return code and an optional text
//! message //! message
use nom::*; use crate::smtp::Error;
use nom::{crlf, ErrorKind as NomErrorKind}; use nom::{
branch::alt,
bytes::complete::{tag, take_until},
combinator::{complete, map},
multi::many0,
sequence::{preceded, tuple},
IResult,
};
use std::fmt::{Display, Formatter, Result}; use std::fmt::{Display, Formatter, Result};
use std::result; use std::result;
use std::str::{from_utf8, FromStr}; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
/// First digit indicates severity /// First digit indicates severity
@@ -142,13 +149,10 @@ pub struct Response {
} }
impl FromStr for Response { impl FromStr for Response {
type Err = NomErrorKind; type Err = Error;
fn from_str(s: &str) -> result::Result<Response, NomErrorKind> { fn from_str(s: &str) -> result::Result<Response, Error> {
match parse_response(s.as_bytes()) { parse_response(s).map(|(_, r)| r).map_err(|e| e.into())
Ok((_, res)) => Ok(res),
Err(e) => Err(e.into_error_kind()),
}
} }
} }
@@ -186,97 +190,88 @@ impl Response {
// Parsers (originally from tokio-smtp) // Parsers (originally from tokio-smtp)
named!( fn parse_code(i: &str) -> IResult<&str, Code> {
parse_code<Code>, let (i, severity) = parse_severity(i)?;
map!( let (i, category) = parse_category(i)?;
tuple!(parse_severity, parse_category, parse_detail), let (i, detail) = parse_detail(i)?;
|(severity, category, detail)| Code { Ok((
i,
Code {
severity, severity,
category, category,
detail, detail,
} },
) ))
); }
named!( fn parse_severity(i: &str) -> IResult<&str, Severity> {
parse_severity<Severity>, alt((
alt!( map(tag("2"), |_| Severity::PositiveCompletion),
tag!("2") => { |_| Severity::PositiveCompletion } | map(tag("3"), |_| Severity::PositiveIntermediate),
tag!("3") => { |_| Severity::PositiveIntermediate } | map(tag("4"), |_| Severity::TransientNegativeCompletion),
tag!("4") => { |_| Severity::TransientNegativeCompletion } | map(tag("5"), |_| Severity::PermanentNegativeCompletion),
tag!("5") => { |_| Severity::PermanentNegativeCompletion } ))(i)
) }
);
named!( fn parse_category(i: &str) -> IResult<&str, Category> {
parse_category<Category>, alt((
alt!( map(tag("0"), |_| Category::Syntax),
tag!("0") => { |_| Category::Syntax } | map(tag("1"), |_| Category::Information),
tag!("1") => { |_| Category::Information } | map(tag("2"), |_| Category::Connections),
tag!("2") => { |_| Category::Connections } | map(tag("3"), |_| Category::Unspecified3),
tag!("3") => { |_| Category::Unspecified3 } | map(tag("4"), |_| Category::Unspecified4),
tag!("4") => { |_| Category::Unspecified4 } | map(tag("5"), |_| Category::MailSystem),
tag!("5") => { |_| Category::MailSystem } ))(i)
) }
);
named!( fn parse_detail(i: &str) -> IResult<&str, Detail> {
parse_detail<Detail>, alt((
alt!( map(tag("0"), |_| Detail::Zero),
tag!("0") => { |_| Detail::Zero } | map(tag("1"), |_| Detail::One),
tag!("1") => { |_| Detail::One } | map(tag("2"), |_| Detail::Two),
tag!("2") => { |_| Detail::Two } | map(tag("3"), |_| Detail::Three),
tag!("3") => { |_| Detail::Three } | map(tag("4"), |_| Detail::Four),
tag!("4") => { |_| Detail::Four} | map(tag("5"), |_| Detail::Five),
tag!("5") => { |_| Detail::Five } | map(tag("6"), |_| Detail::Six),
tag!("6") => { |_| Detail::Six} | map(tag("7"), |_| Detail::Seven),
tag!("7") => { |_| Detail::Seven } | map(tag("8"), |_| Detail::Eight),
tag!("8") => { |_| Detail::Eight } | map(tag("9"), |_| Detail::Nine),
tag!("9") => { |_| Detail::Nine } ))(i)
) }
);
named!( fn parse_response(i: &str) -> IResult<&str, Response> {
parse_response<Response>, let (i, lines) = many0(tuple((
map_res!( parse_code,
tuple!( preceded(tag("-"), take_until("\r\n")),
// Parse any number of continuation lines. tag("\r\n"),
many0!(tuple!( )))(i)?;
parse_code, let (i, (last_code, last_line)) =
preceded!(char!('-'), take_until_and_consume!(b"\r\n".as_ref())) tuple((parse_code, preceded(tag(" "), take_until("\r\n"))))(i)?;
)), let (i, _) = complete(tag("\r\n"))(i)?;
// Parse the final line.
tuple!(
parse_code,
terminated!(
opt!(preceded!(char!(' '), take_until!(b"\r\n".as_ref()))),
crlf
)
)
),
|(lines, (last_code, last_line)): (Vec<_>, _)| {
// Check that all codes are equal.
if !lines.iter().all(|&(ref code, _)| *code == last_code) {
return Err(());
}
// Extract text from lines, and append last line. // Check that all codes are equal.
let mut lines = lines.into_iter().map(|(_, text)| text).collect::<Vec<_>>(); if !lines.iter().all(|&(ref code, _, _)| *code == last_code) {
if let Some(text) = last_line { return Err(nom::Err::Failure(("", nom::error::ErrorKind::Not)));
lines.push(text); }
}
Ok(Response { // Extract text from lines, and append last line.
code: last_code, let mut lines: Vec<&str> = lines
message: lines .into_iter()
.into_iter() .map(|(_, text, _)| text)
.map(|line| from_utf8(line).map(ToString::to_string)) .collect::<Vec<_>>();
.collect::<result::Result<Vec<_>, _>>() lines.push(last_line);
.map_err(|_| ())?,
}) Ok((
} i,
) Response {
); code: last_code,
message: lines
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>(),
},
))
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@@ -610,7 +610,6 @@ mod test {
assert!(string_res.unwrap().starts_with("Subject: A Subject")); assert!(string_res.unwrap().starts_with("Subject: A Subject"));
} }
#[test] #[test]
fn test_email_sendable() { fn test_email_sendable() {
let email_builder = EmailBuilder::new(); let email_builder = EmailBuilder::new();