Merge `response` and `replies` modules.
This commit is contained in:
parent
e9ddac2ec2
commit
ad3a164d19
|
@ -16,7 +16,6 @@ use std::{borrow::Cow, str::from_utf8};
|
|||
pub mod address;
|
||||
pub mod command;
|
||||
pub mod imf;
|
||||
pub mod replies;
|
||||
pub mod response;
|
||||
pub mod trace;
|
||||
pub mod utils;
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
//! 4.2. SMTP Replies (RFC 5321)
|
||||
|
||||
use crate::{
|
||||
parse::{address::address_literal, Domain},
|
||||
types::Greeting as GreetingType,
|
||||
};
|
||||
use abnf_core::streaming::{CRLF, SP};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, take_while1, take_while_m_n},
|
||||
combinator::{map, map_res, opt, recognize},
|
||||
multi::many0,
|
||||
sequence::{delimited, preceded, tuple},
|
||||
IResult,
|
||||
};
|
||||
|
||||
/// Greeting = ( "220 " (Domain / address-literal) [ SP textstring ] CRLF ) /
|
||||
/// ( "220-" (Domain / address-literal) [ SP textstring ] CRLF
|
||||
/// *( "220-" [ textstring ] CRLF )
|
||||
/// "220" [ SP textstring ] CRLF )
|
||||
pub fn Greeting(input: &[u8]) -> IResult<&[u8], GreetingType> {
|
||||
let mut parser = alt((
|
||||
map(
|
||||
tuple((
|
||||
tag(b"220 "),
|
||||
alt((Domain, address_literal)),
|
||||
opt(preceded(SP, textstring)),
|
||||
CRLF,
|
||||
)),
|
||||
|(_, domain, maybe_text, _)| GreetingType {
|
||||
domain: domain.to_owned(),
|
||||
text: maybe_text
|
||||
.map(|str| str.to_string())
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
},
|
||||
),
|
||||
map(
|
||||
tuple((
|
||||
tag(b"220-"),
|
||||
alt((Domain, address_literal)),
|
||||
opt(preceded(SP, textstring)),
|
||||
CRLF,
|
||||
many0(delimited(tag(b"220-"), opt(textstring), CRLF)),
|
||||
tag(b"220"),
|
||||
opt(preceded(SP, textstring)),
|
||||
CRLF,
|
||||
)),
|
||||
|(_, domain, maybe_text, _, more_text, _, moar_text, _)| GreetingType {
|
||||
domain: domain.to_owned(),
|
||||
text: {
|
||||
let mut res = maybe_text
|
||||
.map(|str| format!("{}\n", str))
|
||||
.unwrap_or_else(|| "\n".to_string());
|
||||
|
||||
for text in more_text {
|
||||
let text = text
|
||||
.map(|str| format!("{}\n", str))
|
||||
.unwrap_or_else(|| "\n".to_string());
|
||||
res.push_str(&text);
|
||||
}
|
||||
|
||||
let text = moar_text
|
||||
.map(|str| str.to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
res.push_str(&text);
|
||||
|
||||
res
|
||||
},
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// HT, SP, Printable US-ASCII
|
||||
///
|
||||
/// textstring = 1*(%d09 / %d32-126)
|
||||
pub fn textstring(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
fn is_value(byte: u8) -> bool {
|
||||
matches!(byte, 9 | 32..=126)
|
||||
}
|
||||
|
||||
let (remaining, parsed) = map_res(take_while1(is_value), std::str::from_utf8)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Reply-line = *( Reply-code "-" [ textstring ] CRLF )
|
||||
/// Reply-code [ SP textstring ] CRLF
|
||||
pub fn Reply_line(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((
|
||||
many0(tuple((Reply_code, tag(b"-"), opt(textstring), CRLF))),
|
||||
Reply_code,
|
||||
opt(tuple((SP, textstring))),
|
||||
CRLF,
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Reply-code = %x32-35 %x30-35 %x30-39
|
||||
///
|
||||
/// 2345
|
||||
/// 012345
|
||||
/// 0123456789
|
||||
pub fn Reply_code(input: &[u8]) -> IResult<&[u8], u16> {
|
||||
// FIXME: do not accept all codes.
|
||||
map_res(
|
||||
map_res(
|
||||
take_while_m_n(3, 3, nom::character::is_digit),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
|s| u16::from_str_radix(s, 10),
|
||||
)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_Greeting() {
|
||||
let greeting = b"220-example.org ESMTP Fake 4.93 #2 Thu, 16 Jul 2020 07:30:16 -0400\r\n\
|
||||
220-We do not authorize the use of this system to transport unsolicited,\r\n\
|
||||
220 and/or bulk e-mail.\r\n";
|
||||
|
||||
let (rem, out) = Greeting(greeting).unwrap();
|
||||
assert_eq!(rem, b"");
|
||||
assert_eq!(
|
||||
out,
|
||||
GreetingType {
|
||||
domain: "example.org".into(),
|
||||
text: "ESMTP Fake 4.93 #2 Thu, 16 Jul 2020 07:30:16 -0400\n\
|
||||
We do not authorize the use of this system to transport unsolicited,\n\
|
||||
and/or bulk e-mail."
|
||||
.into(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
parse::{number, Domain},
|
||||
types::{AuthMechanism, Capability, EhloOkResp},
|
||||
parse::{address::address_literal, number, Domain},
|
||||
types::{AuthMechanism, Capability, EhloOkResp, Greeting as GreetingType},
|
||||
};
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT, CRLF, SP};
|
||||
use nom::{
|
||||
|
@ -12,6 +12,82 @@ use nom::{
|
|||
IResult,
|
||||
};
|
||||
|
||||
/// Greeting = ( "220 " (Domain / address-literal) [ SP textstring ] CRLF ) /
|
||||
/// ( "220-" (Domain / address-literal) [ SP textstring ] CRLF
|
||||
/// *( "220-" [ textstring ] CRLF )
|
||||
/// "220" [ SP textstring ] CRLF )
|
||||
pub fn Greeting(input: &[u8]) -> IResult<&[u8], GreetingType> {
|
||||
let mut parser = alt((
|
||||
map(
|
||||
tuple((
|
||||
tag(b"220 "),
|
||||
alt((Domain, address_literal)),
|
||||
opt(preceded(SP, textstring)),
|
||||
CRLF,
|
||||
)),
|
||||
|(_, domain, maybe_text, _)| GreetingType {
|
||||
domain: domain.to_owned(),
|
||||
text: maybe_text
|
||||
.map(|str| str.to_string())
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
},
|
||||
),
|
||||
map(
|
||||
tuple((
|
||||
tag(b"220-"),
|
||||
alt((Domain, address_literal)),
|
||||
opt(preceded(SP, textstring)),
|
||||
CRLF,
|
||||
many0(delimited(tag(b"220-"), opt(textstring), CRLF)),
|
||||
tag(b"220"),
|
||||
opt(preceded(SP, textstring)),
|
||||
CRLF,
|
||||
)),
|
||||
|(_, domain, maybe_text, _, more_text, _, moar_text, _)| GreetingType {
|
||||
domain: domain.to_owned(),
|
||||
text: {
|
||||
let mut res = maybe_text
|
||||
.map(|str| format!("{}\n", str))
|
||||
.unwrap_or_else(|| "\n".to_string());
|
||||
|
||||
for text in more_text {
|
||||
let text = text
|
||||
.map(|str| format!("{}\n", str))
|
||||
.unwrap_or_else(|| "\n".to_string());
|
||||
res.push_str(&text);
|
||||
}
|
||||
|
||||
let text = moar_text
|
||||
.map(|str| str.to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
res.push_str(&text);
|
||||
|
||||
res
|
||||
},
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// HT, SP, Printable US-ASCII
|
||||
///
|
||||
/// textstring = 1*(%d09 / %d32-126)
|
||||
pub fn textstring(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
fn is_value(byte: u8) -> bool {
|
||||
matches!(byte, 9 | 32..=126)
|
||||
}
|
||||
|
||||
let (remaining, parsed) = map_res(take_while1(is_value), std::str::from_utf8)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// ehlo-ok-rsp = ( "250 " Domain [ SP ehlo-greet ] CRLF ) /
|
||||
/// ( "250-" Domain [ SP ehlo-greet ] CRLF
|
||||
/// *( "250-" ehlo-line CRLF )
|
||||
|
@ -122,20 +198,6 @@ pub fn ehlo_line(input: &[u8]) -> IResult<&[u8], Capability> {
|
|||
))(input)
|
||||
}
|
||||
|
||||
pub fn auth_mechanism(input: &[u8]) -> IResult<&[u8], AuthMechanism> {
|
||||
alt((
|
||||
value(AuthMechanism::Login, tag_no_case("LOGIN")),
|
||||
value(AuthMechanism::Plain, tag_no_case("PLAIN")),
|
||||
value(AuthMechanism::CramMD5, tag_no_case("CRAM-MD5")),
|
||||
value(AuthMechanism::CramSHA1, tag_no_case("CRAM-SHA1")),
|
||||
value(AuthMechanism::DigestMD5, tag_no_case("DIGEST-MD5")),
|
||||
value(AuthMechanism::ScramMD5, tag_no_case("SCRAM-MD5")),
|
||||
value(AuthMechanism::GSSAPI, tag_no_case("GSSAPI")),
|
||||
value(AuthMechanism::NTLM, tag_no_case("NTLM")),
|
||||
map(ehlo_param, |param| AuthMechanism::Other(param.to_string())),
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// Additional syntax of ehlo-params depends on ehlo-keyword
|
||||
///
|
||||
/// ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
|
||||
|
@ -162,11 +224,78 @@ pub fn ehlo_param(input: &[u8]) -> IResult<&[u8], &str> {
|
|||
map_res(take_while1(is_valid_character), std::str::from_utf8)(input)
|
||||
}
|
||||
|
||||
pub fn auth_mechanism(input: &[u8]) -> IResult<&[u8], AuthMechanism> {
|
||||
alt((
|
||||
value(AuthMechanism::Login, tag_no_case("LOGIN")),
|
||||
value(AuthMechanism::Plain, tag_no_case("PLAIN")),
|
||||
value(AuthMechanism::CramMD5, tag_no_case("CRAM-MD5")),
|
||||
value(AuthMechanism::CramSHA1, tag_no_case("CRAM-SHA1")),
|
||||
value(AuthMechanism::DigestMD5, tag_no_case("DIGEST-MD5")),
|
||||
value(AuthMechanism::ScramMD5, tag_no_case("SCRAM-MD5")),
|
||||
value(AuthMechanism::GSSAPI, tag_no_case("GSSAPI")),
|
||||
value(AuthMechanism::NTLM, tag_no_case("NTLM")),
|
||||
map(ehlo_param, |param| AuthMechanism::Other(param.to_string())),
|
||||
))(input)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Reply-line = *( Reply-code "-" [ textstring ] CRLF )
|
||||
/// Reply-code [ SP textstring ] CRLF
|
||||
pub fn Reply_line(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((
|
||||
many0(tuple((Reply_code, tag(b"-"), opt(textstring), CRLF))),
|
||||
Reply_code,
|
||||
opt(tuple((SP, textstring))),
|
||||
CRLF,
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
/// Reply-code = %x32-35 %x30-35 %x30-39
|
||||
///
|
||||
/// 2345
|
||||
/// 012345
|
||||
/// 0123456789
|
||||
pub fn Reply_code(input: &[u8]) -> IResult<&[u8], u16> {
|
||||
// FIXME: do not accept all codes.
|
||||
map_res(
|
||||
map_res(
|
||||
take_while_m_n(3, 3, nom::character::is_digit),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
|s| u16::from_str_radix(s, 10),
|
||||
)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::AuthMechanism;
|
||||
|
||||
#[test]
|
||||
fn test_Greeting() {
|
||||
let greeting = b"220-example.org ESMTP Fake 4.93 #2 Thu, 16 Jul 2020 07:30:16 -0400\r\n\
|
||||
220-We do not authorize the use of this system to transport unsolicited,\r\n\
|
||||
220 and/or bulk e-mail.\r\n";
|
||||
|
||||
let (rem, out) = Greeting(greeting).unwrap();
|
||||
assert_eq!(rem, b"");
|
||||
assert_eq!(
|
||||
out,
|
||||
GreetingType {
|
||||
domain: "example.org".into(),
|
||||
text: "ESMTP Fake 4.93 #2 Thu, 16 Jul 2020 07:30:16 -0400\n\
|
||||
We do not authorize the use of this system to transport unsolicited,\n\
|
||||
and/or bulk e-mail."
|
||||
.into(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ehlo_ok_rsp() {
|
||||
let (rem, out) = ehlo_ok_rsp(
|
||||
|
|
Loading…
Reference in New Issue