simplify content type structs

This commit is contained in:
Quentin 2023-07-22 14:38:43 +02:00
parent a8ae9c3714
commit 5df9b253ec
Signed by: quentin
GPG key ID: E9602264D639FF68
10 changed files with 26 additions and 692 deletions

View file

@ -43,41 +43,41 @@ pub fn parameter_list(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> {
#[derive(Debug, PartialEq)]
pub enum Type {
// Composite types
Multipart(MultipartDesc),
Message(MessageSubtype),
Multipart(Multipart),
Message(Message),
// Discrete types
Text(TextDesc),
Text(Text),
Binary,
}
impl Default for Type {
fn default() -> Self {
Self::Text(TextDesc::default())
Self::Text(Text::default())
}
}
impl<'a> From<&'a NaiveType<'a>> for Type {
fn from(nt: &'a NaiveType<'a>) -> Self {
match nt.main.to_ascii_lowercase().as_slice() {
b"multipart" => MultipartDesc::try_from(nt).map(Self::Multipart).unwrap_or(Self::default()),
b"message" => Self::Message(MessageSubtype::from(nt)),
b"text" => Self::Text(TextDesc::from(nt)),
b"multipart" => Multipart::try_from(nt).map(Self::Multipart).unwrap_or(Self::default()),
b"message" => Self::Message(Message::from(nt)),
b"text" => Self::Text(Text::from(nt)),
_ => Self::Binary,
}
}
}
#[derive(Debug, PartialEq)]
pub struct MultipartDesc {
pub struct Multipart {
pub subtype: MultipartSubtype,
pub boundary: String,
}
impl<'a> TryFrom<&'a NaiveType<'a>> for MultipartDesc {
impl<'a> TryFrom<&'a NaiveType<'a>> for Multipart {
type Error = ();
fn try_from(nt: &'a NaiveType<'a>) -> Result<Self, Self::Error> {
nt.params.iter()
.find(|x| x.name.to_ascii_lowercase().as_slice() == b"boundary")
.map(|boundary| MultipartDesc {
.map(|boundary| Multipart {
subtype: MultipartSubtype::from(nt),
boundary: boundary.value.to_string(),
})
@ -108,13 +108,13 @@ impl<'a> From<&NaiveType<'a>> for MultipartSubtype {
}
#[derive(Debug, PartialEq)]
pub enum MessageSubtype {
pub enum Message {
RFC822,
Partial,
External,
Unknown,
}
impl<'a> From<&NaiveType<'a>> for MessageSubtype {
impl<'a> From<&NaiveType<'a>> for Message {
fn from(nt: &NaiveType<'a>) -> Self {
match nt.sub.to_ascii_lowercase().as_slice() {
b"rfc822" => Self::RFC822,
@ -126,13 +126,13 @@ impl<'a> From<&NaiveType<'a>> for MessageSubtype {
}
#[derive(Debug, PartialEq, Default)]
pub struct TextDesc {
pub struct Text {
pub subtype: TextSubtype,
pub charset: EmailCharset,
}
impl<'a> From<&NaiveType<'a>> for TextDesc {
impl<'a> From<&NaiveType<'a>> for Text {
fn from(nt: &NaiveType<'a>) -> Self {
TextDesc {
Self {
subtype: TextSubtype::from(nt),
charset: nt.params.iter()
.find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset")
@ -189,7 +189,7 @@ mod tests {
assert_eq!(
nt.to_type(),
Type::Text(TextDesc {
Type::Text(Text {
charset: EmailCharset::UTF_8,
subtype: TextSubtype::Plain,
})
@ -203,7 +203,7 @@ mod tests {
assert_eq!(rest, &[]);
assert_eq!(
nt.to_type(),
Type::Multipart(MultipartDesc {
Type::Multipart(Multipart {
subtype: MultipartSubtype::Mixed,
boundary: "--==_mimepart_64a3f2c69114f_2a13d020975fe".into(),
})
@ -217,7 +217,7 @@ mod tests {
assert_eq!(
nt.to_type(),
Type::Message(MessageSubtype::RFC822),
Type::Message(Message::RFC822),
);
}

View file

@ -1,17 +0,0 @@
use crate::fragments::part;
use crate::fragments::section::Section;
use crate::multipass::header_section;
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub fields: Section<'a>,
pub body: part::PartNode<'a>,
}
pub fn new<'a>(p: &'a header_section::Parsed<'a>) -> Parsed<'a> {
todo!();
/*Parsed {
fields: p.fields,
body: p.body,
}*/
}

View file

@ -1,58 +0,0 @@
use nom::{
bytes::complete::is_not,
character::complete::space1,
combinator::{all_consuming, recognize},
multi::{many0, many1},
sequence::{pair, tuple},
IResult,
};
use crate::error::IMFError;
use crate::fragments::fields;
use crate::multipass::field_lazy;
use crate::multipass::guess_charset;
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub fields: Vec<&'a str>,
pub body: &'a [u8],
}
pub fn new<'a>(gcha: &'a guess_charset::Parsed<'a>) -> Result<Parsed<'a>, IMFError<'a>> {
fields(&gcha.header)
.map_err(|e| IMFError::ExtractFields(e))
.map(|(_, fields)| Parsed {
fields,
body: gcha.body,
})
}
impl<'a> Parsed<'a> {
pub fn names(&'a self) -> field_lazy::Parsed<'a> {
field_lazy::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract() {
assert_eq!(
new(&guess_charset::Parsed {
header: "From: hello@world.com,\r\n\talice@wonderlands.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n".into(),
encoding: encoding_rs::UTF_8,
malformed: false,
body: b"Hello world!",
}),
Ok(Parsed {
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!",
})
);
}
}

View file

@ -1,336 +0,0 @@
use crate::fragments::eager;
use crate::multipass::field_lazy;
use crate::multipass::header_section;
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub fields: Vec<eager::Field<'a>>,
pub body: &'a [u8],
}
pub fn new<'a>(p: &'a field_lazy::Parsed<'a>) -> Parsed<'a> {
Parsed {
fields: p
.fields
.iter()
.filter_map(|entry| entry.try_into().ok())
.collect(),
body: p.body,
}
}
impl<'a> Parsed<'a> {
pub fn section(&'a self) -> header_section::Parsed<'a> {
header_section::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fragments::lazy;
use crate::fragments::model;
use chrono::{FixedOffset, TimeZone};
#[test]
fn test_field_body() {
assert_eq!(
new(&field_lazy::Parsed {
fields: vec![
lazy::Field::From(lazy::MailboxList(
"hello@world.com,\r\n\talice@wonderlands.com\r\n"
)),
lazy::Field::Date(lazy::DateTime("12 Mar 1997 07:33:25 Z\r\n")),
],
body: b"Hello world!",
}),
Parsed {
fields: vec![
eager::Field::From(vec![
model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "hello".into(),
domain: "world.com".into()
}
},
model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "alice".into(),
domain: "wonderlands.com".into()
}
},
]),
eager::Field::Date(
FixedOffset::east_opt(0)
.unwrap()
.with_ymd_and_hms(1997, 03, 12, 7, 33, 25)
.unwrap()
),
],
body: b"Hello world!",
}
);
}
use crate::fragments::misc_token;
use crate::multipass::extract_fields;
fn lazy_eager<F>(input: &str, func: F)
where
F: Fn(&eager::Field),
{
let field = extract_fields::Parsed {
fields: vec![input],
body: b"",
};
let lazy = field_lazy::new(&field);
let eager = new(&lazy);
func(eager.fields.first().unwrap())
}
#[test]
fn test_from() {
lazy_eager(
"From: \"Joe Q. Public\" <john.q.public@example.com>\r\n",
|from| {
assert_eq!(
from,
&eager::Field::From(vec![model::MailboxRef {
name: Some("Joe Q. Public".into()),
addrspec: model::AddrSpec {
local_part: "john.q.public".into(),
domain: "example.com".into(),
}
}])
)
},
);
}
#[test]
fn test_sender() {
lazy_eager(
"Sender: Michael Jones <mjones@machine.example>\r\n",
|sender| {
assert_eq!(
sender,
&eager::Field::Sender(model::MailboxRef {
name: Some("Michael Jones".into()),
addrspec: model::AddrSpec {
local_part: "mjones".into(),
domain: "machine.example".into(),
},
})
)
},
);
}
#[test]
fn test_reply_to() {
lazy_eager(
"Reply-To: \"Mary Smith: Personal Account\" <smith@home.example>\r\n",
|reply_to| {
assert_eq!(
reply_to,
&eager::Field::ReplyTo(vec![model::AddressRef::Single(model::MailboxRef {
name: Some("Mary Smith: Personal Account".into()),
addrspec: model::AddrSpec {
local_part: "smith".into(),
domain: "home.example".into(),
},
})])
)
},
)
}
#[test]
fn test_to() {
lazy_eager(
"To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;\r\n",
|to| {
assert_eq!(
to,
&eager::Field::To(vec![model::AddressRef::Many(model::GroupRef {
name: "A Group".into(),
participants: vec![
model::MailboxRef {
name: Some("Ed Jones".into()),
addrspec: model::AddrSpec {
local_part: "c".into(),
domain: "a.test".into()
},
},
model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "joe".into(),
domain: "where.test".into()
},
},
model::MailboxRef {
name: Some("John".into()),
addrspec: model::AddrSpec {
local_part: "jdoe".into(),
domain: "one.test".into()
},
},
]
})])
)
},
)
}
#[test]
fn test_cc() {
lazy_eager("Cc: Undisclosed recipients:;\r\n", |cc| {
assert_eq!(
cc,
&eager::Field::Cc(vec![model::AddressRef::Many(model::GroupRef {
name: "Undisclosed recipients".into(),
participants: vec![],
})]),
)
})
}
#[test]
fn test_bcc() {
lazy_eager("Bcc: (empty)\r\n", |bcc| {
assert_eq!(bcc, &eager::Field::Bcc(vec![]),)
});
lazy_eager("Bcc: \r\n", |bcc| {
assert_eq!(bcc, &eager::Field::Bcc(vec![]),)
});
}
#[test]
fn test_message_id() {
lazy_eager("Message-ID: <310@[127.0.0.1]>\r\n", |msg_id| {
assert_eq!(
msg_id,
&eager::Field::MessageID(model::MessageId {
left: "310",
right: "127.0.0.1"
},)
)
})
}
#[test]
fn test_in_reply_to() {
lazy_eager("In-Reply-To: <a@b> <c@example.com>\r\n", |irt| {
assert_eq!(
irt,
&eager::Field::InReplyTo(vec![
model::MessageId {
left: "a",
right: "b"
},
model::MessageId {
left: "c",
right: "example.com"
},
])
)
})
}
#[test]
fn test_references() {
lazy_eager(
"References: <1234@local.machine.example> <3456@example.net>\r\n",
|refer| {
assert_eq!(
refer,
&eager::Field::References(vec![
model::MessageId {
left: "1234",
right: "local.machine.example"
},
model::MessageId {
left: "3456",
right: "example.net"
},
])
)
},
)
}
#[test]
fn test_subject() {
lazy_eager("Subject: Aérogramme\r\n", |subject| {
assert_eq!(
subject,
&eager::Field::Subject(misc_token::Unstructured("Aérogramme".into()))
)
})
}
#[test]
fn test_comments() {
lazy_eager("Comments: 😛 easter egg!\r\n", |comments| {
assert_eq!(
comments,
&eager::Field::Comments(misc_token::Unstructured("😛 easter egg!".into())),
)
})
}
#[test]
fn test_keywords() {
lazy_eager(
"Keywords: fantasque, farfelu, fanfreluche\r\n",
|keywords| {
assert_eq!(
keywords,
&eager::Field::Keywords(misc_token::PhraseList(vec![
"fantasque".into(),
"farfelu".into(),
"fanfreluche".into()
]))
)
},
)
}
//@FIXME non ported tests:
/*
#[test]
fn test_invalid_field_name() {
assert!(known_field("Unknown: unknown\r\n").is_err());
}
#[test]
fn test_rescue_field() {
assert_eq!(
rescue_field("Héron: élan\r\n\tnoël: test\r\nFrom: ..."),
Ok(("From: ...", Field::Rescue("Héron: élan\r\n\tnoël: test"))),
);
}
#[test]
fn test_wrong_fields() {
let fullmail = r#"Return-Path: xoxo
From: !!!!
Hello world"#;
assert_eq!(
section(fullmail),
Ok(("Hello world", HeaderSection {
bad_fields: vec![
Field::ReturnPath(FieldBody::Failed("xoxo")),
Field::From(FieldBody::Failed("!!!!")),
],
..Default::default()
}))
);
}
*/
}

View file

@ -1,75 +0,0 @@
use crate::fragments::lazy;
use crate::multipass::extract_fields;
use crate::multipass::field_eager;
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub fields: Vec<lazy::Field<'a>>,
pub body: &'a [u8],
}
pub fn new<'a>(ef: &'a extract_fields::Parsed<'a>) -> Parsed<'a> {
Parsed {
fields: ef.fields.iter().map(|e| (*e).into()).collect(),
body: ef.body,
}
}
impl<'a> Parsed<'a> {
pub fn body(&'a self) -> field_eager::Parsed<'a> {
field_eager::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_name() {
assert_eq!(
new(&extract_fields::Parsed {
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!",
}),
Parsed {
fields: vec![
lazy::Field::From(lazy::MailboxList(
"hello@world.com,\r\n\talice@wonderlands.com\r\n"
)),
lazy::Field::Date(lazy::DateTime("12 Mar 1997 07:33:25 Z\r\n")),
],
body: b"Hello world!",
}
);
}
#[test]
fn test_mime_fields() {
assert_eq!(
new(&extract_fields::Parsed {
fields: vec![
"MIME-Version: 1.0 \r\n",
"Content-Type: multipart/alternative; boundary=\"bound\"\r\n",
"Content-Transfer-Encoding: 7bit\r\n",
"Content-ID: <foo4*foo1@bar.net>\r\n",
"Content-Description: hello world\r\n",
],
body: b"Hello world!",
}),
Parsed {
fields: vec![
lazy::Field::MIMEVersion(lazy::Version("1.0 \r\n")),
lazy::Field::MIME(lazy::MIMEField::ContentType(lazy::Type("multipart/alternative; boundary=\"bound\"\r\n"))),
lazy::Field::MIME(lazy::MIMEField::ContentTransferEncoding(lazy::Mechanism("7bit\r\n"))),
lazy::Field::MIME(lazy::MIMEField::ContentID(lazy::Identifier("<foo4*foo1@bar.net>\r\n"))),
lazy::Field::MIME(lazy::MIMEField::ContentDescription(lazy::Unstructured("hello world\r\n"))),
],
body: b"Hello world!",
}
);
}
}

View file

@ -1,46 +0,0 @@
use std::borrow::Cow;
use crate::error::IMFError;
use crate::fragments::encoding;
use crate::multipass::extract_fields;
use crate::multipass::segment;
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub header: Cow<'a, str>,
pub body: &'a [u8],
}
pub fn new<'a>(seg: &'a segment::Parsed<'a>) -> Parsed<'a> {
Parsed {
header: encoding::header_decode(&seg.header),
body: seg.body,
}
}
impl<'a> Parsed<'a> {
pub fn fields(&'a self) -> Result<extract_fields::Parsed<'a>, IMFError<'a>> {
extract_fields::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_charset() {
assert_eq!(
new(&segment::Parsed {
body: b"Hello world!",
header: b"From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n",
}),
Parsed {
header: "From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n".into(),
encoding: encoding_rs::UTF_8,
malformed: false,
body: b"Hello world!",
}
);
}
}

View file

@ -1,92 +0,0 @@
use crate::fragments::section::Section;
use crate::multipass::{field_eager, body_structure};
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub fields: Section<'a>,
pub body: &'a [u8],
}
pub fn new<'a>(p: &'a field_eager::Parsed<'a>) -> Parsed<'a> {
Parsed {
fields: Section::from_iter(p.fields.iter()),
body: p.body,
}
}
impl<'a> Parsed<'a> {
pub fn body_structure(&self) -> body_structure::Parsed<'a> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fragments::eager;
use crate::fragments::model;
use chrono::{FixedOffset, TimeZone};
#[test]
fn test_section() {
assert_eq!(
new(&field_eager::Parsed {
fields: vec![
eager::Field::From(vec![
model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "hello".into(),
domain: "world.com".into()
}
},
model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "alice".into(),
domain: "wonderlands.com".into()
}
},
]),
eager::Field::Date(
FixedOffset::east_opt(0)
.unwrap()
.with_ymd_and_hms(1997, 03, 12, 7, 33, 25)
.unwrap()
),
],
body: b"Hello world!",
}),
Parsed {
fields: Section {
from: vec![
&model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "hello".into(),
domain: "world.com".into()
}
},
&model::MailboxRef {
name: None,
addrspec: model::AddrSpec {
local_part: "alice".into(),
domain: "wonderlands.com".into()
}
},
],
date: Some(
&FixedOffset::east_opt(0)
.unwrap()
.with_ymd_and_hms(1997, 03, 12, 7, 33, 25)
.unwrap()
),
..Default::default()
},
body: b"Hello world!",
}
);
}
}

View file

@ -1,7 +0,0 @@
pub mod extract_fields;
pub mod field_eager;
pub mod field_lazy;
pub mod guess_charset;
pub mod header_section;
pub mod segment;
pub mod body_structure;

View file

@ -1,37 +0,0 @@
use crate::error::IMFError;
use crate::multipass::guess_charset;
use crate::fragments::whitespace::headers;
#[derive(Debug, PartialEq)]
pub struct Parsed<'a> {
pub header: &'a [u8],
pub body: &'a [u8],
}
pub fn new<'a>(buffer: &'a [u8]) -> Result<Parsed<'a>, IMFError<'a>> {
headers(buffer)
.map_err(|e| IMFError::Segment(e))
.map(|(body, header)| Parsed { header, body })
}
impl<'a> Parsed<'a> {
pub fn charset(&'a self) -> guess_charset::Parsed<'a> {
guess_charset::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_segment() {
assert_eq!(
new(&b"From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n\r\nHello world!"[..]),
Ok(Parsed {
header: b"From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n",
body: b"Hello world!",
})
);
}
}

View file

@ -7,12 +7,14 @@ use nom::{
combinator::{not, opt, recognize},
};
use crate::fragments::mime::{Mechanism, Type};
use crate::fragments::model::MessageId;
use crate::fragments::misc_token::Unstructured;
use crate::fragments::whitespace::{CRLF, headers, line, obs_crlf};
use crate::fragments::{eager,lazy};
use crate::fragments::section::MIMESection;
pub struct Part<'a, T> {
}
impl<'a> Part<'a, r#type::Text<'a>> {
}