destination address fields

This commit is contained in:
Quentin 2023-06-13 14:44:41 +02:00
parent 6ae9a6a9aa
commit de6926bb92
Signed by: quentin
GPG key ID: E9602264D639FF68
4 changed files with 105 additions and 14 deletions

View file

@ -29,11 +29,11 @@ pub fn address(input: &str) -> IResult<&str, AddressRef> {
/// ``` /// ```
pub fn group(input: &str) -> IResult<&str, GroupRef> { pub fn group(input: &str) -> IResult<&str, GroupRef> {
let (input, (grp_name, _, grp_list, _, _)) = let (input, (grp_name, _, grp_list, _, _)) =
tuple((phrase, tag(":"), group_list, tag(";"), opt(cfws)))(input)?; tuple((phrase, tag(":"), opt(group_list), tag(";"), opt(cfws)))(input)?;
Ok((input, GroupRef { Ok((input, GroupRef {
name: grp_name, name: grp_name,
participants: grp_list, participants: grp_list.unwrap_or(vec![]),
})) }))
} }
@ -43,10 +43,10 @@ pub fn group(input: &str) -> IResult<&str, GroupRef> {
/// group-list = mailbox-list / CFWS / obs-group-list /// group-list = mailbox-list / CFWS / obs-group-list
/// ``` /// ```
pub fn group_list(input: &str) -> IResult<&str, Vec<MailboxRef>> { pub fn group_list(input: &str) -> IResult<&str, Vec<MailboxRef>> {
alt((mailbox_list, mx_cfws))(input) alt((mailbox_list, mailbox_cfws))(input)
} }
fn mx_cfws(input: &str) -> IResult<&str, Vec<MailboxRef>> { fn mailbox_cfws(input: &str) -> IResult<&str, Vec<MailboxRef>> {
let (input, _) = cfws(input)?; let (input, _) = cfws(input)?;
Ok((input, vec![])) Ok((input, vec![]))
} }
@ -69,6 +69,11 @@ pub fn address_list(input: &str) -> IResult<&str, Vec<AddressRef>> {
separated_list1(tag(","), address)(input) separated_list1(tag(","), address)(input)
} }
pub fn address_list_cfws(input: &str) -> IResult<&str, Vec<AddressRef>> {
let (input, _) = cfws(input)?;
Ok((input, vec![]))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -1,6 +1,7 @@
use chrono::DateTime; use chrono::DateTime;
use nom::{ use nom::{
IResult, IResult,
branch::alt,
bytes::complete::take_while1, bytes::complete::take_while1,
bytes::complete::tag, bytes::complete::tag,
character::complete::space0, character::complete::space0,
@ -15,7 +16,7 @@ use crate::words::vchar_seq;
use crate::misc_token::unstructured; use crate::misc_token::unstructured;
use crate::model::{PermissiveHeaderSection, HeaderDate, MailboxRef, AddressRef}; use crate::model::{PermissiveHeaderSection, HeaderDate, MailboxRef, AddressRef};
use crate::mailbox::mailbox; use crate::mailbox::mailbox;
use crate::address::{mailbox_list, address_list}; use crate::address::{mailbox_list, address_list, address_list_cfws};
/// HEADERS /// HEADERS
@ -28,9 +29,11 @@ pub fn header_section(input: &str) -> IResult<&str, PermissiveHeaderSection> {
PermissiveHeaderSection::default, PermissiveHeaderSection::default,
|mut section, head| { |mut section, head| {
match head { match head {
//@FIXME min and max limits are not enforced,
// it may result in missing data or silently overriden data.
// 3.6.1. The Origination Date Field // 3.6.1. The Origination Date Field
HeaderField::Date(d) => { HeaderField::Date(d) => {
//@FIXME only one date is allowed, what are we doing if multiple dates are
//encountered? Currently, we override... //encountered? Currently, we override...
// | orig-date | 1 | 1 | | // | orig-date | 1 | 1 | |
section.date = d; section.date = d;
@ -38,7 +41,6 @@ pub fn header_section(input: &str) -> IResult<&str, PermissiveHeaderSection> {
// 3.6.2. Originator Fields // 3.6.2. Originator Fields
HeaderField::From(v) => { HeaderField::From(v) => {
//@FIXME override the from field if declared multiple times.
// | from | 1 | 1 | See sender and 3.6.2 | // | from | 1 | 1 | See sender and 3.6.2 |
section.from = v; section.from = v;
} }
@ -52,6 +54,18 @@ pub fn header_section(input: &str) -> IResult<&str, PermissiveHeaderSection> {
} }
// 3.6.3. Destination Address Fields // 3.6.3. Destination Address Fields
HeaderField::To(addr_list) => {
// | to | 0 | 1 | |
section.to = addr_list;
}
HeaderField::Cc(addr_list) => {
// | cc | 0 | 1 | |
section.cc = addr_list;
}
HeaderField::Bcc(addr_list) => {
// | bcc | 0 | 1 | |
section.bcc = addr_list;
}
HeaderField::Subject(title) => { HeaderField::Subject(title) => {
@ -81,9 +95,9 @@ enum HeaderField<'a> {
ReplyTo(Vec<AddressRef>), ReplyTo(Vec<AddressRef>),
// 3.6.3. Destination Address Fields // 3.6.3. Destination Address Fields
To, To(Vec<AddressRef>),
Cc, Cc(Vec<AddressRef>),
Bcc, Bcc(Vec<AddressRef>),
// 3.6.4. Identification Fields // 3.6.4. Identification Fields
MessageID, MessageID,
@ -130,8 +144,10 @@ fn header_field(input: &str) -> IResult<&str, HeaderField> {
// Extract field body // Extract field body
let (input, hfield) = match field_name { let (input, hfield) = match field_name {
// 3.6.1. The Origination Date Field
"Date" => datetime(input)?, "Date" => datetime(input)?,
// 3.6.2. Originator Fields
"From" => { "From" => {
let (input, body) = mailbox_list(input)?; let (input, body) = mailbox_list(input)?;
(input, HeaderField::From(body)) (input, HeaderField::From(body))
@ -145,6 +161,20 @@ fn header_field(input: &str) -> IResult<&str, HeaderField> {
(input, HeaderField::ReplyTo(body)) (input, HeaderField::ReplyTo(body))
} }
// 3.6.3. Destination Address Fields
"To" => {
let (input, body) = address_list(input)?;
(input, HeaderField::To(body))
},
"Cc" => {
let (input, body) = address_list(input)?;
(input, HeaderField::Cc(body))
},
"Bcc" => {
let (input, body) = opt(alt((address_list, address_list_cfws)))(input)?;
(input, HeaderField::Bcc(body.unwrap_or(vec![])))
},
"Subject" => { "Subject" => {
let (input, body) = unstructured(input)?; let (input, body) = unstructured(input)?;
(input, HeaderField::Subject(body)) (input, HeaderField::Subject(body))
@ -174,7 +204,7 @@ fn datetime(input: &str) -> IResult<&str, HeaderField> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::model::AddrSpec; use crate::model::{GroupRef, AddrSpec};
// 3.6.1. The Origination Date Field // 3.6.1. The Origination Date Field
#[test] #[test]
@ -229,6 +259,51 @@ mod tests {
); );
} }
// 3.6.3. Destination Address Fields
#[test]
fn test_to() {
assert_eq!(
header_field("To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;\r\n"),
Ok(("", HeaderField::To(vec![AddressRef::Many(GroupRef {
name: "A Group".into(),
participants: vec![
MailboxRef {
name: Some("Ed Jones".into()),
addrspec: AddrSpec { local_part: "c".into(), domain: "a.test".into() },
},
MailboxRef {
name: None,
addrspec: AddrSpec { local_part: "joe".into(), domain: "where.test".into() },
},
MailboxRef {
name: Some("John".into()),
addrspec: AddrSpec { local_part: "jdoe".into(), domain: "one.test".into() },
},
]
})])))
);
}
#[test]
fn test_cc() {
assert_eq!(
header_field("Cc: Undisclosed recipients:;\r\n"),
Ok(("", HeaderField::Cc(vec![AddressRef::Many(GroupRef {
name: "Undisclosed recipients".into(),
participants: vec![],
})])))
);
}
#[test]
fn test_bcc() {
assert_eq!(
header_field("Bcc: (empty)\r\n"),
Ok(("", HeaderField::Bcc(vec![])))
);
assert_eq!(
header_field("Bcc: \r\n"),
Ok(("", HeaderField::Bcc(vec![])))
);
}
} }

View file

@ -65,11 +65,20 @@ impl From<GroupRef> for AddressRef {
/// still extract some data. /// still extract some data.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct PermissiveHeaderSection<'a> { pub struct PermissiveHeaderSection<'a> {
pub subject: Option<String>, // 3.6.1. The Origination Date Field
pub date: HeaderDate,
// 3.6.2. Originator Fields
pub from: Vec<MailboxRef>, pub from: Vec<MailboxRef>,
pub sender: Option<MailboxRef>, pub sender: Option<MailboxRef>,
pub reply_to: Vec<AddressRef>, pub reply_to: Vec<AddressRef>,
pub date: HeaderDate,
// 3.6.3. Destination Address Fields
pub to: Vec<AddressRef>,
pub cc: Vec<AddressRef>,
pub bcc: Vec<AddressRef>,
pub subject: Option<String>,
pub optional: HashMap<&'a str, String>, pub optional: HashMap<&'a str, String>,
} }

View file

@ -7,6 +7,8 @@ From: Mary Smith
Sender: imf@example.com Sender: imf@example.com
Reply-To: "Mary Smith: Personal Account" <smith@home.example> Reply-To: "Mary Smith: Personal Account" <smith@home.example>
To: John Doe <jdoe@machine.example> To: John Doe <jdoe@machine.example>
Cc: imf2@example.com
Bcc: (hidden)
Subject: Re: Saying Hello Subject: Re: Saying Hello
Message-ID: <3456@example.net> Message-ID: <3456@example.net>
In-Reply-To: <1234@local.machine.example> In-Reply-To: <1234@local.machine.example>