working trace test
This commit is contained in:
parent
f22da50c9b
commit
b6c25a4676
4 changed files with 272 additions and 32 deletions
|
@ -137,13 +137,6 @@ pub enum HeaderField<'a> {
|
||||||
Optional(&'a str, String)
|
Optional(&'a str, String)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_name(input: &str) -> IResult<&str, &str> {
|
|
||||||
terminated(
|
|
||||||
take_while1(|c| c >= '\x21' && c <= '\x7E' && c != '\x3A'),
|
|
||||||
pair(tag(":"), space0)
|
|
||||||
)(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse one known header field
|
/// Parse one known header field
|
||||||
///
|
///
|
||||||
/// RFC5322 optional-field seems to be a generalization of the field terminology.
|
/// RFC5322 optional-field seems to be a generalization of the field terminology.
|
||||||
|
@ -247,13 +240,18 @@ fn keywords(input: &str) -> IResult<&str, HeaderField> {
|
||||||
/// ```
|
/// ```
|
||||||
fn unknown_field(input: &str) -> IResult<&str, HeaderField> {
|
fn unknown_field(input: &str) -> IResult<&str, HeaderField> {
|
||||||
// Extract field name
|
// Extract field name
|
||||||
let (input, field_name) = take_while1(|c| c >= '\x21' && c <= '\x7E' && c != '\x3A')(input)?;
|
let (input, field_name) = field_name(input)?;
|
||||||
let (input, _) = tuple((tag(":"), space0))(input)?;
|
|
||||||
let (input, body) = unstructured(input)?;
|
let (input, body) = unstructured(input)?;
|
||||||
Ok((input, HeaderField::Optional(field_name, body)))
|
Ok((input, HeaderField::Optional(field_name, body)))
|
||||||
}
|
}
|
||||||
|
pub fn field_name(input: &str) -> IResult<&str, &str> {
|
||||||
|
terminated(
|
||||||
|
take_while1(|c| c >= '\x21' && c <= '\x7E' && c != '\x3A'),
|
||||||
|
pair(tag(":"), space0)
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
fn datetime(input: &str) -> IResult<&str, HeaderDate> {
|
pub fn datetime(input: &str) -> IResult<&str, HeaderDate> {
|
||||||
// @FIXME want to extract datetime our way in the future
|
// @FIXME want to extract datetime our way in the future
|
||||||
// to better handle obsolete/bad cases instead of returning raw text.
|
// to better handle obsolete/bad cases instead of returning raw text.
|
||||||
let (input, raw_date) = unstructured(input)?;
|
let (input, raw_date) = unstructured(input)?;
|
||||||
|
|
|
@ -41,7 +41,7 @@ fn name_addr(input: &str) -> IResult<&str, MailboxRef> {
|
||||||
/// angle-addr = [CFWS] "<" addr-spec ">" [CFWS] /
|
/// angle-addr = [CFWS] "<" addr-spec ">" [CFWS] /
|
||||||
/// obs-angle-addr
|
/// obs-angle-addr
|
||||||
/// ```
|
/// ```
|
||||||
fn angle_addr(input: &str) -> IResult<&str, MailboxRef> {
|
pub fn angle_addr(input: &str) -> IResult<&str, MailboxRef> {
|
||||||
delimited(
|
delimited(
|
||||||
pair(opt(cfws), tag("<")),
|
pair(opt(cfws), tag("<")),
|
||||||
into(addr_spec),
|
into(addr_spec),
|
||||||
|
@ -76,7 +76,7 @@ fn local_part(input: &str) -> IResult<&str, String> {
|
||||||
/// ```abnf
|
/// ```abnf
|
||||||
/// domain = dot-atom / domain-literal / obs-domain
|
/// domain = dot-atom / domain-literal / obs-domain
|
||||||
/// ```
|
/// ```
|
||||||
fn domain_part(input: &str) -> IResult<&str, String> {
|
pub fn domain_part(input: &str) -> IResult<&str, String> {
|
||||||
alt((into(dot_atom), domain_litteral))(input)
|
alt((into(dot_atom), domain_litteral))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,11 +63,11 @@ pub struct MessageId<'a> {
|
||||||
pub right: &'a str,
|
pub right: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Default)]
|
||||||
pub struct Trace<'a> {
|
pub struct Trace<'a> {
|
||||||
// 3.6.7 Traces
|
// 3.6.7 Traces
|
||||||
pub received: Vec<String>,
|
pub received: Vec<&'a str>,
|
||||||
pub return_path: Option<String>,
|
pub return_path: Option<MailboxRef>,
|
||||||
|
|
||||||
// 3.6.6. Resent Fields
|
// 3.6.6. Resent Fields
|
||||||
pub resent_date: HeaderDate,
|
pub resent_date: HeaderDate,
|
||||||
|
|
276
src/trace.rs
276
src/trace.rs
|
@ -1,8 +1,14 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
|
branch::alt,
|
||||||
|
bytes::complete::tag,
|
||||||
|
character::complete::space0,
|
||||||
|
combinator::{map, not, opt, recognize},
|
||||||
|
multi::{fold_many0, many0, many1},
|
||||||
|
sequence::{delimited, preceded, terminated, pair, tuple},
|
||||||
};
|
};
|
||||||
use crate::model;
|
use crate::{address, common_fields, identification, mailbox, model, misc_token, whitespace};
|
||||||
|
|
||||||
enum RestField<'a> {
|
enum RestField<'a> {
|
||||||
// 3.6.6. Resent Fields
|
// 3.6.6. Resent Fields
|
||||||
|
@ -15,7 +21,7 @@ enum RestField<'a> {
|
||||||
ResentMessageID(model::MessageId<'a>),
|
ResentMessageID(model::MessageId<'a>),
|
||||||
|
|
||||||
// 3.6.8. Optional fields
|
// 3.6.8. Optional fields
|
||||||
Optional(&'a str, String),
|
OptionalField(&'a str, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PreludeField {
|
enum PreludeField {
|
||||||
|
@ -26,7 +32,7 @@ enum PreludeField {
|
||||||
|
|
||||||
/// Section
|
/// Section
|
||||||
///
|
///
|
||||||
/// Rewritten section for more compatibility
|
/// Optional fields are allowed everywhere in this implementation...
|
||||||
///
|
///
|
||||||
/// ```abnf
|
/// ```abnf
|
||||||
///*(trace
|
///*(trace
|
||||||
|
@ -40,17 +46,44 @@ enum PreludeField {
|
||||||
/// resent-msg-id))
|
/// resent-msg-id))
|
||||||
/// ```
|
/// ```
|
||||||
pub fn section(input: &str) -> IResult<&str, model::Trace> {
|
pub fn section(input: &str) -> IResult<&str, model::Trace> {
|
||||||
let (input, mut prelude_trace) = prelude(input)?;
|
let (input, (path, recv)) = prelude(input)?;
|
||||||
/*let (input, full_trace) = fold_many0(
|
let (input, mut full_trace) = fold_many0(
|
||||||
rest_field,
|
alt((resent_field, unknown_field)),
|
||||||
prelude_trace,
|
model::Trace::default,
|
||||||
|mut trace, field| {
|
|mut trace, field| {
|
||||||
match field {
|
match field {
|
||||||
|
RestField::ResentDate(date) => {
|
||||||
}
|
trace.resent_date = date;
|
||||||
}*/
|
}
|
||||||
|
RestField::ResentFrom(from) => {
|
||||||
|
trace.resent_from = from;
|
||||||
|
}
|
||||||
|
RestField::ResentSender(sender) => {
|
||||||
|
trace.resent_sender = Some(sender);
|
||||||
|
}
|
||||||
|
RestField::ResentTo(to) => {
|
||||||
|
trace.resent_to = to;
|
||||||
|
}
|
||||||
|
RestField::ResentCc(cc) => {
|
||||||
|
trace.resent_cc = cc;
|
||||||
|
}
|
||||||
|
RestField::ResentBcc(bcc) => {
|
||||||
|
trace.resent_bcc = bcc;
|
||||||
|
}
|
||||||
|
RestField::ResentMessageID(mid) => {
|
||||||
|
trace.resent_msg_id = Some(mid);
|
||||||
|
}
|
||||||
|
RestField::OptionalField(name, body) => {
|
||||||
|
trace.optional.insert(name, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
trace
|
||||||
|
}
|
||||||
|
)(input)?;
|
||||||
|
full_trace.received = recv;
|
||||||
|
full_trace.return_path = path;
|
||||||
|
|
||||||
unimplemented!();
|
Ok((input, full_trace))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trace prelude
|
/// Trace prelude
|
||||||
|
@ -63,11 +96,220 @@ pub fn section(input: &str) -> IResult<&str, model::Trace> {
|
||||||
/// received = "Received:" *received-token ";" date-time CRLF
|
/// received = "Received:" *received-token ";" date-time CRLF
|
||||||
/// received-token = word / angle-addr / addr-spec / domain
|
/// received-token = word / angle-addr / addr-spec / domain
|
||||||
/// ```
|
/// ```
|
||||||
fn prelude(input: &str) -> IResult<&str, model::Trace> {
|
fn prelude(input: &str) -> IResult<&str, (Option<model::MailboxRef>, Vec<&str>)> {
|
||||||
unimplemented!();
|
let (input, (return_path, received)) = pair(
|
||||||
|
opt(return_path_field),
|
||||||
|
many1(received_field),
|
||||||
|
)(input)?;
|
||||||
|
|
||||||
|
Ok((input, (return_path.flatten(), received)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rest_field(input: &str) -> IResult<&str, RestField> {
|
fn return_path_field(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
||||||
unimplemented!();
|
delimited(
|
||||||
// Ensure this is not a new prelude
|
pair(tag("Return-Path:"), space0),
|
||||||
|
path,
|
||||||
|
whitespace::perm_crlf,
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
||||||
|
alt((
|
||||||
|
map(mailbox::angle_addr, |a| Some(a)),
|
||||||
|
empty_path
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_path(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
||||||
|
let (input, _) = tuple((
|
||||||
|
opt(whitespace::cfws),
|
||||||
|
tag("<"),
|
||||||
|
opt(whitespace::cfws),
|
||||||
|
tag(">"),
|
||||||
|
opt(whitespace::cfws),
|
||||||
|
))(input)?;
|
||||||
|
Ok((input, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn received_field(input: &str) -> IResult<&str, &str> {
|
||||||
|
let (input, (_, tk, _, _, _)) = tuple((
|
||||||
|
pair(tag("Received:"), space0),
|
||||||
|
recognize(many0(received_tokens)),
|
||||||
|
tag(";"),
|
||||||
|
common_fields::datetime,
|
||||||
|
whitespace::perm_crlf,
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
Ok((input, tk))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn received_tokens(input: &str) -> IResult<&str, &str> {
|
||||||
|
alt((
|
||||||
|
recognize(mailbox::angle_addr),
|
||||||
|
recognize(mailbox::addr_spec),
|
||||||
|
recognize(mailbox::domain_part),
|
||||||
|
recognize(misc_token::word),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_field(input: &str) -> IResult<&str, RestField> {
|
||||||
|
terminated(
|
||||||
|
alt((
|
||||||
|
resent_date,
|
||||||
|
resent_from,
|
||||||
|
resent_sender,
|
||||||
|
resent_to,
|
||||||
|
resent_cc,
|
||||||
|
resent_bcc,
|
||||||
|
resent_msg_id,
|
||||||
|
)),
|
||||||
|
whitespace::perm_crlf,
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_date(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(pair(tag("Resent-Date:"), space0), common_fields::datetime)(input)?;
|
||||||
|
Ok((input, RestField::ResentDate(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_from(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(pair(tag("Resent-From:"), space0), address::mailbox_list)(input)?;
|
||||||
|
Ok((input, RestField::ResentFrom(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_sender(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(pair(tag("Resent-Sender:"), space0), mailbox::mailbox)(input)?;
|
||||||
|
Ok((input, RestField::ResentSender(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_to(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(pair(tag("Resent-To:"), space0), address::address_list)(input)?;
|
||||||
|
Ok((input, RestField::ResentTo(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_cc(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(pair(tag("Resent-Cc:"), space0), address::address_list)(input)?;
|
||||||
|
Ok((input, RestField::ResentCc(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_bcc(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(
|
||||||
|
pair(tag("Resent-Bcc:"), space0),
|
||||||
|
opt(alt((address::address_list, address::address_list_cfws))),
|
||||||
|
)(input)?;
|
||||||
|
|
||||||
|
Ok((input, RestField::ResentBcc(body.unwrap_or(vec![]))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resent_msg_id(input: &str) -> IResult<&str, RestField> {
|
||||||
|
let (input, body) = preceded(pair(tag("Resent-Message-ID:"), space0), identification::msg_id)(input)?;
|
||||||
|
Ok((input, RestField::ResentMessageID(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unknown_field(input: &str) -> IResult<&str, RestField> {
|
||||||
|
// Check that we:
|
||||||
|
// 1. do not start a new trace
|
||||||
|
// 2. do not start the common fields
|
||||||
|
not(prelude)(input)?;
|
||||||
|
not(common_fields::header_field)(input)?;
|
||||||
|
|
||||||
|
// Extract field name
|
||||||
|
let (input, field_name) = common_fields::field_name(input)?;
|
||||||
|
let (input, body) = misc_token::unstructured(input)?;
|
||||||
|
let (input, _) = whitespace::perm_crlf(input)?;
|
||||||
|
Ok((input, RestField::OptionalField(field_name, body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use chrono::{FixedOffset, TimeZone};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_section() {
|
||||||
|
let hdrs = r#"Return-Path: <gitlab@example.com>
|
||||||
|
Received: from smtp.example.com ([10.83.2.2])
|
||||||
|
by server with LMTP
|
||||||
|
id xxxxxxxxx
|
||||||
|
(envelope-from <gitlab@example.com>)
|
||||||
|
for <me@example.com>; Tue, 13 Jun 2023 19:01:08 +0000
|
||||||
|
Resent-Date: Tue, 13 Jun 2023 21:01:07 +0200
|
||||||
|
Resent-From: <you@example.com>
|
||||||
|
Resent-Sender: you@example.com
|
||||||
|
X-Specific: XOXO
|
||||||
|
Resent-To: Annah <annah@example.com>
|
||||||
|
Resent-Cc: Empty:;
|
||||||
|
Resent-Bcc:
|
||||||
|
Resent-Message-ID: <note_1985938@example.com>
|
||||||
|
"#;
|
||||||
|
assert_eq!(
|
||||||
|
section(hdrs),
|
||||||
|
Ok(("", model::Trace {
|
||||||
|
return_path: Some(model::MailboxRef {
|
||||||
|
name: None,
|
||||||
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "gitlab".into(),
|
||||||
|
domain: "example.com".into(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
received: vec![
|
||||||
|
r#"from smtp.example.com ([10.83.2.2])
|
||||||
|
by server with LMTP
|
||||||
|
id xxxxxxxxx
|
||||||
|
(envelope-from <gitlab@example.com>)
|
||||||
|
for <me@example.com>"#,
|
||||||
|
],
|
||||||
|
|
||||||
|
resent_date: model::HeaderDate::Parsed(
|
||||||
|
FixedOffset::east_opt(2 * 3600)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 06, 13, 21, 1, 7)
|
||||||
|
.unwrap()),
|
||||||
|
|
||||||
|
resent_from: vec![
|
||||||
|
model::MailboxRef {
|
||||||
|
name: None,
|
||||||
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "you".into(),
|
||||||
|
domain: "example.com".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
resent_sender: Some(model::MailboxRef {
|
||||||
|
name: None,
|
||||||
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "you".into(),
|
||||||
|
domain: "example.com".into(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
resent_to: vec![
|
||||||
|
model::AddressRef::Single(model::MailboxRef {
|
||||||
|
name: Some("Annah".into()),
|
||||||
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "annah".into(),
|
||||||
|
domain: "example.com".into(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
resent_cc: vec![
|
||||||
|
model::AddressRef::Many(model::GroupRef {
|
||||||
|
name: "Empty".into(),
|
||||||
|
participants: vec![],
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
resent_bcc: vec![],
|
||||||
|
|
||||||
|
resent_msg_id: Some(model::MessageId {
|
||||||
|
left: "note_1985938",
|
||||||
|
right: "example.com",
|
||||||
|
}),
|
||||||
|
|
||||||
|
optional: HashMap::from([("X-Specific", "XOXO".into())]),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue