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

View File

@ -110,9 +110,14 @@ impl DomainOrAddress {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Parameter {
#[non_exhaustive]
pub enum Parameter {
/// Message size declaration [RFC1870]
Size(u32),
Other {
keyword: String,
value: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -244,20 +249,20 @@ impl Command {
}
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<()> {
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(value.as_bytes())?;
}
}
};
Ok(())
}