Add typed support for `SIZE` parameter (RFC 1870)

This also makes `Parameter` type an enum so future type support for
other parameters can be more easily added. Note that the enum is
non-exhaustive so that future variant addtions would not cause API
breaks.
This commit is contained in:
Zeeshan Ali 2022-10-06 21:37:59 +02:00 committed by Damian Poddebniak
parent 1065b44eef
commit 6c6fa0f2ff
2 changed files with 35 additions and 22 deletions

View File

@ -88,10 +88,18 @@ pub fn Mail_parameters(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> {
/// esmtp-param = esmtp-keyword ["=" esmtp-value] /// esmtp-param = esmtp-keyword ["=" esmtp-value]
pub fn esmtp_param(input: &[u8]) -> IResult<&[u8], Parameter> { pub fn esmtp_param(input: &[u8]) -> IResult<&[u8], Parameter> {
map( alt((
tuple((esmtp_keyword, opt(preceded(tag(b"="), esmtp_value)))), map_res(tuple((tag_no_case(b"SIZE="), esmtp_value)), |(_, value)| {
|(keyword, value)| Parameter::new(keyword, value), value.parse().map(Parameter::Size)
)(input) }),
map(
tuple((esmtp_keyword, opt(preceded(tag(b"="), esmtp_value)))),
|(keyword, value)| Parameter::Other {
keyword: keyword.to_owned(),
value: value.map(String::from),
},
),
))(input)
} }
/// esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") /// esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
@ -353,7 +361,7 @@ pub fn Dot_string(input: &[u8]) -> IResult<&[u8], &str> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::{ehlo, helo, mail}; use super::{ehlo, helo, mail, Parameter};
use crate::types::{Command, DomainOrAddress}; use crate::types::{Command, DomainOrAddress};
#[test] #[test]
@ -382,12 +390,12 @@ mod test {
#[test] #[test]
fn test_mail() { fn test_mail() {
let (rem, parsed) = mail(b"MAIL FROM:<userx@y.foo.org>\r\n???").unwrap(); let (rem, parsed) = mail(b"MAIL FROM:<userx@y.foo.org> size=12345\r\n???").unwrap();
assert_eq!( assert_eq!(
parsed, parsed,
Command::Mail { Command::Mail {
reverse_path: "userx@y.foo.org".into(), reverse_path: "userx@y.foo.org".into(),
parameters: Vec::default(), parameters: vec![Parameter::Size(12345)],
} }
); );
assert_eq!(rem, b"???"); assert_eq!(rem, b"???");

View File

@ -110,9 +110,14 @@ impl DomainOrAddress {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Parameter { #[non_exhaustive]
keyword: String, pub enum Parameter {
value: Option<String>, /// Message size declaration [RFC1870]
Size(u32),
Other {
keyword: String,
value: Option<String>,
},
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -244,20 +249,20 @@ impl Command {
} }
impl Parameter { impl Parameter {
pub fn new<K: Into<String>, V: Into<String>>(keyword: K, value: Option<V>) -> Parameter {
Parameter {
keyword: keyword.into(),
value: value.map(Into::into),
}
}
pub fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> { pub fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> {
writer.write_all(self.keyword.as_bytes())?; match self {
Parameter::Size(size) => {
write!(writer, "SIZE={}", size)?;
}
Parameter::Other { keyword, value } => {
writer.write_all(keyword.as_bytes())?;
if let Some(ref value) = self.value { if let Some(ref value) = value {
writer.write_all(b"=")?; writer.write_all(b"=")?;
writer.write_all(value.as_bytes())?; writer.write_all(value.as_bytes())?;
} }
}
};
Ok(()) Ok(())
} }