diff --git a/src/fragments/field_raw.rs b/src/fragments/field_raw.rs new file mode 100644 index 0000000..581cc53 --- /dev/null +++ b/src/fragments/field_raw.rs @@ -0,0 +1,98 @@ +use std::convert::From; + +use nom::{ + IResult, + bytes::complete::{take_while1, tag}, + character::complete::space0, + sequence::{terminated, tuple}, +}; + +#[derive(Debug, PartialEq)] +pub enum Field<'a> { + // 3.6.1. The Origination Date Field + Date(&'a str), + + // 3.6.2. Originator Fields + From(&'a str), + Sender(&'a str), + ReplyTo(&'a str), + + // 3.6.3. Destination Address Fields + To(&'a str), + Cc(&'a str), + Bcc(&'a str), + + // 3.6.4. Identification Fields + MessageID(&'a str), + InReplyTo(&'a str), + References(&'a str), + + // 3.6.5. Informational Fields + Subject(&'a str), + Comments(&'a str), + Keywords(&'a str), + + // 3.6.6 Resent Fields (not implemented) + // 3.6.7 Trace Fields + Received(&'a str), + ReturnPath(&'a str), + + // 3.6.8. Optional Fields + Optional(&'a str, &'a str), + + // None + Rescue(&'a str), +} +use Field::*; + +impl<'a> From<&'a str> for Field<'a> { + fn from(input: &'a str) -> Self { + match correct_field(input) { + Ok((_, field)) => field, + Err(_) => Rescue(input), + } + } +} +/// Optional field +/// +/// ```abnf +/// field = field-name ":" unstructured CRLF +/// field-name = 1*ftext +/// ftext = %d33-57 / ; Printable US-ASCII +/// %d59-126 ; characters not including +/// ; ":". +/// ``` +fn field_name(input: &str) -> IResult<&str, &str> { + terminated( + take_while1(|c| c >= '\x21' && c <= '\x7E' && c != '\x3A'), + tuple((space0, tag(":"), space0)) + )(input) +} + +fn correct_field(input: &str) -> IResult<&str, Field> { + field_name(input) + .map(|(rest, name)| ("", match name.to_lowercase().as_ref() { + "date" => Date(rest), + + "from" => From(rest), + "sender" => Sender(rest), + "reply-to" => ReplyTo(rest), + + "to" => To(rest), + "cc" => Cc(rest), + "bcc" => Bcc(rest), + + "message-id" => MessageID(rest), + "in-reply-to" => InReplyTo(rest), + "references" => References(rest), + + "subject" => Subject(rest), + "comments" => Comments(rest), + "keywords" => Keywords(rest), + + "return-path" => ReturnPath(rest), + "received" => Received(rest), + + _ => Optional(name, rest), + })) +} diff --git a/src/fragments/mod.rs b/src/fragments/mod.rs index 3724153..a7108c5 100644 --- a/src/fragments/mod.rs +++ b/src/fragments/mod.rs @@ -13,6 +13,7 @@ mod address; mod identification; mod trace; mod datetime; +pub mod field_raw; // Header blocks pub mod header; diff --git a/src/multipass/mod.rs b/src/multipass/mod.rs index 7d24bda..c93f382 100644 --- a/src/multipass/mod.rs +++ b/src/multipass/mod.rs @@ -1,3 +1,4 @@ pub mod segment; pub mod guess_charset; pub mod extract_fields; +pub mod parse_field_names; diff --git a/src/multipass/parse_field_names.rs b/src/multipass/parse_field_names.rs index e69de29..af5d3e5 100644 --- a/src/multipass/parse_field_names.rs +++ b/src/multipass/parse_field_names.rs @@ -0,0 +1,40 @@ +use crate::fragments::field_raw; +use crate::multipass::extract_fields::ExtractFields; + +#[derive(Debug, PartialEq)] +pub struct ParseFieldName<'a> { + pub fields: Vec>, + pub body: &'a [u8], +} + +impl<'a> From <&'a ExtractFields<'a>> for ParseFieldName<'a> { + fn from(ef: &'a ExtractFields<'a>) -> Self { + ParseFieldName { + fields: ef.fields.iter().map(|e| (*e).into()).collect(), + body: ef.body, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_field_name() { + assert_eq!(ParseFieldName::from(&ExtractFields { + fields: vec![ + "From: hello@world.com,\r\n\talice@wonderlands.com\r\n", + "Date: 12 Mar 1997 07:33:25 Z\r\n", + ], + body: b"Hello world!", + }), + ParseFieldName { + fields: vec![ + field_raw::Field::From("hello@world.com,\r\n\talice@wonderlands.com\r\n"), + field_raw::Field::Date("12 Mar 1997 07:33:25 Z\r\n"), + ], + body: b"Hello world!", + }); + } +}