chore(transport): Use nom 5.0 with functions (#360)
This commit is contained in:
@@ -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 }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user