EhloOkResp Parser + Type
* ehlo_ok_resp returns `EhloOkResp` now * Converted some &[u8] parser to &str * Added unit test * Changed ehlo_line to accept "AUTH=LOGIN PLAIN" (note the "=")
This commit is contained in:
parent
f14357d5b9
commit
d41e8118f3
|
@ -10,7 +10,7 @@ use abnf_core::streaming::{is_ALPHA, is_DIGIT, CRLF, DQUOTE, SP};
|
|||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
|
||||
combinator::{opt, recognize},
|
||||
combinator::{map_res, opt, recognize},
|
||||
multi::many0,
|
||||
sequence::{delimited, preceded, tuple},
|
||||
IResult,
|
||||
|
@ -348,10 +348,10 @@ pub fn Argument(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
}
|
||||
|
||||
/// Domain = sub-domain *("." sub-domain)
|
||||
pub fn Domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
pub fn Domain(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let parser = tuple((sub_domain, many0(tuple((tag(b"."), sub_domain)))));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
let (remaining, parsed) = map_res(recognize(parser), std::str::from_utf8)(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
@ -389,18 +389,21 @@ pub fn Ldh_str(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// General-address-literal
|
||||
/// ) "]"
|
||||
/// ; See Section 4.1.3
|
||||
pub fn address_literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
pub fn address_literal(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
let parser = delimited(
|
||||
tag(b"["),
|
||||
map_res(
|
||||
alt((
|
||||
IPv4_address_literal,
|
||||
IPv6_address_literal,
|
||||
General_address_literal,
|
||||
)),
|
||||
std::str::from_utf8,
|
||||
),
|
||||
tag(b"]"),
|
||||
);
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
let (remaining, parsed) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
@ -493,3 +496,43 @@ pub fn String(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{ehlo, helo, mail, sub_domain};
|
||||
use crate::types::Command;
|
||||
|
||||
#[test]
|
||||
fn test_subdomain() {
|
||||
let (rem, parsed) = sub_domain(b"example???").unwrap();
|
||||
assert_eq!(parsed, b"example");
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ehlo() {
|
||||
let (rem, parsed) = ehlo(b"EHLO [123.123.123.123]\r\n???").unwrap();
|
||||
assert_eq!(parsed, Command::Ehlo(b"123.123.123.123".to_vec()));
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_helo() {
|
||||
let (rem, parsed) = helo(b"HELO example.com\r\n???").unwrap();
|
||||
assert_eq!(parsed, Command::Helo(b"example.com".to_vec()));
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mail() {
|
||||
let (rem, parsed) = mail(b"MAIL FROM:<userx@y.foo.org>\r\n???").unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
Command::Mail {
|
||||
data: b"<userx@y.foo.org>".to_vec(),
|
||||
params: None
|
||||
}
|
||||
);
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crate::parse::command::Domain;
|
||||
use crate::{parse::command::Domain, types::EhloOkResp};
|
||||
use abnf_core::streaming::{is_ALPHA, is_DIGIT, CRLF, SP};
|
||||
use nom::multi::separated_list;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag, take_while, take_while1, take_while_m_n},
|
||||
combinator::{opt, recognize},
|
||||
combinator::{map, map_res, opt, recognize},
|
||||
multi::many0,
|
||||
sequence::tuple,
|
||||
sequence::{delimited, preceded, tuple},
|
||||
IResult,
|
||||
};
|
||||
|
||||
|
@ -13,29 +14,56 @@ use nom::{
|
|||
/// ( "250-" Domain [ SP ehlo-greet ] CRLF
|
||||
/// *( "250-" ehlo-line CRLF )
|
||||
/// "250" SP ehlo-line CRLF )
|
||||
pub fn ehlo_ok_rsp(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
pub fn ehlo_ok_rsp(input: &[u8]) -> IResult<&[u8], EhloOkResp> {
|
||||
let parser = alt((
|
||||
recognize(tuple((
|
||||
tag(b"250"),
|
||||
SP,
|
||||
Domain,
|
||||
opt(tuple((SP, ehlo_greet))),
|
||||
CRLF,
|
||||
))),
|
||||
recognize(tuple((
|
||||
map(
|
||||
tuple((tag(b"250"), SP, Domain, opt(preceded(SP, ehlo_greet)), CRLF)),
|
||||
|(_, _, domain, maybe_ehlo, _)| EhloOkResp {
|
||||
domain: domain.to_owned(),
|
||||
greet: maybe_ehlo.map(|ehlo| ehlo.to_owned()),
|
||||
lines: Vec::new(),
|
||||
},
|
||||
),
|
||||
map(
|
||||
tuple((
|
||||
tag(b"250-"),
|
||||
Domain,
|
||||
opt(tuple((SP, ehlo_greet))),
|
||||
opt(preceded(SP, ehlo_greet)),
|
||||
CRLF,
|
||||
many0(tuple((tag(b"250-"), ehlo_line, CRLF))),
|
||||
many0(delimited(tag(b"250-"), ehlo_line, CRLF)),
|
||||
tag(b"250"),
|
||||
SP,
|
||||
ehlo_line,
|
||||
CRLF,
|
||||
))),
|
||||
)),
|
||||
|(_, domain, maybe_ehlo, _, lines, _, _, (keyword, params), _)| EhloOkResp {
|
||||
domain: domain.to_owned(),
|
||||
greet: maybe_ehlo.map(|ehlo| ehlo.to_owned()),
|
||||
lines: {
|
||||
let mut lines = lines
|
||||
.iter()
|
||||
.map(|(keyword, params)| {
|
||||
let params = params
|
||||
.iter()
|
||||
.map(|param| param.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
(keyword.to_string(), params)
|
||||
})
|
||||
.collect::<Vec<(String, Vec<String>)>>();
|
||||
lines.push((
|
||||
keyword.to_string(),
|
||||
params
|
||||
.iter()
|
||||
.map(|param| param.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
));
|
||||
lines
|
||||
},
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
let (remaining, parsed) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
}
|
||||
|
@ -43,7 +71,7 @@ pub fn ehlo_ok_rsp(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// String of any characters other than CR or LF.
|
||||
///
|
||||
/// ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
|
||||
pub fn ehlo_greet(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
pub fn ehlo_greet(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
fn is_valid_character(byte: u8) -> bool {
|
||||
match byte {
|
||||
0..=9 | 11..=12 | 14..=127 => true,
|
||||
|
@ -51,16 +79,24 @@ pub fn ehlo_greet(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
}
|
||||
}
|
||||
|
||||
take_while1(is_valid_character)(input)
|
||||
map_res(take_while1(is_valid_character), std::str::from_utf8)(input)
|
||||
}
|
||||
|
||||
/// ehlo-line = ehlo-keyword *( SP ehlo-param )
|
||||
pub fn ehlo_line(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = tuple((ehlo_keyword, many0(tuple((SP, ehlo_param)))));
|
||||
///
|
||||
/// TODO: SMTP servers often respond with "AUTH=LOGIN PLAIN". Why?
|
||||
pub fn ehlo_line(input: &[u8]) -> IResult<&[u8], (&str, Vec<&str>)> {
|
||||
let parser = tuple((
|
||||
map_res(ehlo_keyword, std::str::from_utf8),
|
||||
opt(preceded(
|
||||
alt((SP, tag("="))), // TODO: For Outlook?
|
||||
separated_list(SP, ehlo_param),
|
||||
)),
|
||||
));
|
||||
|
||||
let (remaining, parsed) = recognize(parser)(input)?;
|
||||
let (remaining, (ehlo_keyword, ehlo_params)) = parser(input)?;
|
||||
|
||||
Ok((remaining, parsed))
|
||||
Ok((remaining, (ehlo_keyword, ehlo_params.unwrap_or(vec![]))))
|
||||
}
|
||||
|
||||
/// Additional syntax of ehlo-params depends on ehlo-keyword
|
||||
|
@ -81,7 +117,7 @@ pub fn ehlo_keyword(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// (US-ASCII 0-31 and 127 inclusive)
|
||||
///
|
||||
/// ehlo-param = 1*(%d33-126)
|
||||
pub fn ehlo_param(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
pub fn ehlo_param(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
fn is_valid_character(byte: u8) -> bool {
|
||||
match byte {
|
||||
33..=126 => true,
|
||||
|
@ -89,5 +125,52 @@ pub fn ehlo_param(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
}
|
||||
}
|
||||
|
||||
take_while1(is_valid_character)(input)
|
||||
map_res(take_while1(is_valid_character), std::str::from_utf8)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ehlo_ok_rsp() {
|
||||
let (rem, out) = ehlo_ok_rsp(
|
||||
b"250-example.org hello\r\n\
|
||||
250-AUTH LOGIN CRAM-MD5 PLAIN\r\n\
|
||||
250-AUTH=LOGIN CRAM-MD5 PLAIN\r\n\
|
||||
250-STARTTLS\r\n\
|
||||
250-SIZE 12345\r\n\
|
||||
250 8BITMIME\r\n",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(rem, b"");
|
||||
assert_eq!(
|
||||
out,
|
||||
EhloOkResp {
|
||||
domain: "example.org".into(),
|
||||
greet: Some("hello".into()),
|
||||
lines: vec![
|
||||
(
|
||||
"AUTH".into(),
|
||||
vec!["LOGIN".into(), "CRAM-MD5".into(), "PLAIN".into()]
|
||||
),
|
||||
(
|
||||
"AUTH".into(),
|
||||
vec!["LOGIN".into(), "CRAM-MD5".into(), "PLAIN".into()]
|
||||
),
|
||||
("STARTTLS".into(), vec![]),
|
||||
("SIZE".into(), vec!["12345".into()]),
|
||||
("8BITMIME".into(), vec![]),
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ehlo_line() {
|
||||
let (rem, (keyword, params)) = ehlo_line(b"SIZE 123456\r\n").unwrap();
|
||||
assert_eq!(rem, b"\r\n");
|
||||
assert_eq!(keyword, "SIZE");
|
||||
assert_eq!(params, &["123456"]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ pub fn By_domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// ( address-literal FWS "(" TCP-info ")" )
|
||||
pub fn Extended_Domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((
|
||||
Domain,
|
||||
recognize(Domain),
|
||||
recognize(tuple((Domain, FWS, tag(b"("), TCP_info, tag(b")")))),
|
||||
recognize(tuple((
|
||||
address_literal,
|
||||
|
@ -100,7 +100,7 @@ pub fn Extended_Domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
/// TCP-info = address-literal / ( Domain FWS address-literal )
|
||||
pub fn TCP_info(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let parser = alt((
|
||||
address_literal,
|
||||
recognize(address_literal),
|
||||
recognize(tuple((Domain, FWS, address_literal))),
|
||||
));
|
||||
|
||||
|
|
46
src/types.rs
46
src/types.rs
|
@ -90,43 +90,11 @@ impl std::fmt::Debug for Command {
|
|||
}
|
||||
}
|
||||
|
||||
pub type EhloLine = (String, Option<String>);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{parse::command::*, types::*};
|
||||
|
||||
#[test]
|
||||
fn test_subdomain() {
|
||||
let (rem, parsed) = sub_domain(b"example???").unwrap();
|
||||
assert_eq!(parsed, b"example");
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ehlo() {
|
||||
let (rem, parsed) = ehlo(b"EHLO [123.123.123.123]\r\n???").unwrap();
|
||||
assert_eq!(parsed, Command::Ehlo(b"[123.123.123.123]".to_vec()));
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_helo() {
|
||||
let (rem, parsed) = helo(b"HELO example.com\r\n???").unwrap();
|
||||
assert_eq!(parsed, Command::Helo(b"example.com".to_vec()));
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mail() {
|
||||
let (rem, parsed) = mail(b"MAIL FROM:<userx@y.foo.org>\r\n???").unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
Command::Mail {
|
||||
data: b"<userx@y.foo.org>".to_vec(),
|
||||
params: None
|
||||
}
|
||||
);
|
||||
assert_eq!(rem, b"???");
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EhloOkResp {
|
||||
pub domain: String,
|
||||
pub greet: Option<String>,
|
||||
pub lines: Vec<EhloLine>,
|
||||
}
|
||||
|
||||
pub type EhloLine = (String, Vec<String>);
|
||||
|
|
Loading…
Reference in New Issue