split part mod in multiple files

This commit is contained in:
Quentin 2023-07-24 12:26:53 +02:00
parent e76cd78e4f
commit 5cfa06dbf8
Signed by: quentin
GPG key ID: E9602264D639FF68
8 changed files with 261 additions and 216 deletions

View file

@ -19,8 +19,8 @@ between the header information and the body of the message."#;
let email = eml_codec::email(input).unwrap();
println!(
"{} just sent you an email with subject \"{}\"",
email.1.from[0].to_string(),
email.1.subject.unwrap().to_string(),
email.imf.from[0].to_string(),
email.imf.subject.unwrap().to_string(),
);
```

View file

@ -12,7 +12,7 @@ between the header information and the body of the message."#;
let email = eml_codec::email(input).unwrap();
println!(
"{} just sent you an email with subject \"{}\"",
email.1.from[0].to_string(),
email.1.subject.unwrap().to_string(),
email.imf.from[0].to_string(),
email.imf.subject.unwrap().to_string(),
);
}

View file

@ -5,8 +5,8 @@ mod part;
mod imf;
mod text;
pub fn email(input: &[u8]) -> Result<part::part::Message, error::EMLError> {
part::part::message(mime::mime::Message::default())(input)
pub fn email(input: &[u8]) -> Result<part::composite::Message, error::EMLError> {
part::composite::message(mime::mime::Message::default())(input)
.map(|(_, v)| v)
.map_err(error::EMLError::ParseError)
}

View file

@ -10,6 +10,6 @@ fn main() {
let eml = eml_codec::email(&rawmail).unwrap();
println!("{:#?}", eml);
assert!(eml.1.date.is_some());
assert!(!eml.1.from.is_empty());
assert!(eml.imf.date.is_some());
assert!(!eml.imf.from.is_empty());
}

View file

@ -1,128 +1,16 @@
use std::fmt;
use nom::{
branch::alt,
bytes::complete::is_not,
combinator::{map, not, recognize},
multi::many0,
sequence::pair,
IResult,
};
use nom::IResult;
use crate::header::{header, CompFieldList};
use crate::mime;
use crate::mime::mime::AnyMIME;
use crate::imf::{self as imf};
use crate::text::ascii::CRLF;
use crate::text::boundary::{boundary, Delimiter};
use crate::text::whitespace::obs_crlf;
use crate::part::{AnyPart, self};
//--- Multipart
#[derive(Debug, PartialEq)]
pub struct Multipart<'a>(pub mime::mime::Multipart<'a>, pub Vec<AnyPart<'a>>);
#[derive(Debug, PartialEq)]
pub struct Message<'a>(
pub mime::mime::Message<'a>,
pub imf::Imf<'a>,
pub Box<AnyPart<'a>>,
);
#[derive(PartialEq)]
pub struct Text<'a>(pub mime::mime::Text<'a>, pub &'a [u8]);
impl<'a> fmt::Debug for Text<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("part::Text")
.field("mime", &self.0)
.field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.1)))
.finish()
}
}
#[derive(PartialEq)]
pub struct Binary<'a>(pub mime::mime::Binary<'a>, pub &'a [u8]);
impl<'a> fmt::Debug for Binary<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("part::Binary")
.field("mime", &self.0)
.field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.1)))
.finish()
}
}
#[derive(Debug, PartialEq)]
pub enum AnyPart<'a> {
Mult(Multipart<'a>),
Msg(Message<'a>),
Txt(Text<'a>),
Bin(Binary<'a>),
}
pub enum MixedField<'a> {
MIME(mime::field::Content<'a>),
IMF(imf::field::Field<'a>),
}
#[allow(dead_code)]
impl<'a> MixedField<'a> {
pub fn mime(&self) -> Option<&mime::field::Content<'a>> {
match self {
Self::MIME(v) => Some(v),
_ => None,
}
}
pub fn to_mime(self) -> Option<mime::field::Content<'a>> {
match self {
Self::MIME(v) => Some(v),
_ => None,
}
}
pub fn imf(&self) -> Option<&imf::field::Field<'a>> {
match self {
Self::IMF(v) => Some(v),
_ => None,
}
}
pub fn to_imf(self) -> Option<imf::field::Field<'a>> {
match self {
Self::IMF(v) => Some(v),
_ => None,
}
}
}
impl<'a> CompFieldList<'a, MixedField<'a>> {
pub fn sections(self) -> (mime::mime::AnyMIME<'a>, imf::Imf<'a>) {
let k = self.known();
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
k.into_iter().partition(|v| v.mime().is_some());
let mime = v1
.into_iter()
.filter_map(|v| v.to_mime())
.collect::<mime::mime::AnyMIME>();
let imf = v2
.into_iter()
.filter_map(|v| v.to_imf())
.collect::<imf::Imf>();
(mime, imf)
}
}
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
alt((
map(mime::field::content, MixedField::MIME),
map(imf::field::field, MixedField::IMF),
))(input)
}
pub fn message<'a>(
m: mime::mime::Message<'a>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
move |input: &[u8]| {
let (input, fields) = header(mixed_field)(input)?;
let (in_mime, imf) = fields.sections();
let part = to_anypart(in_mime, input);
Ok((&[], Message(m.clone(), imf, Box::new(part))))
}
pub struct Multipart<'a> {
pub interpreted: mime::mime::Multipart<'a>,
pub children: Vec<AnyPart<'a>>
}
pub fn multipart<'a>(
@ -132,12 +20,12 @@ pub fn multipart<'a>(
move |input| {
let bound = m.0.boundary.as_bytes();
let (mut input_loop, _) = part_raw(bound)(input)?;
let (mut input_loop, _) = part::part_raw(bound)(input)?;
let mut mparts: Vec<AnyPart> = vec![];
loop {
let input = match boundary(bound)(input_loop) {
Err(_) => return Ok((input_loop, Multipart(m.clone(), mparts))),
Ok((inp, Delimiter::Last)) => return Ok((inp, Multipart(m.clone(), mparts))),
Err(_) => return Ok((input_loop, Multipart { interpreted: m.clone(), children: mparts })),
Ok((inp, Delimiter::Last)) => return Ok((inp, Multipart{ interpreted: m.clone(), children: mparts})),
Ok((inp, Delimiter::Next)) => inp,
};
@ -146,35 +34,35 @@ pub fn multipart<'a>(
let mime = fields.to_mime();
// parse raw part
let (input, rpart) = part_raw(bound)(input)?;
let (input, rpart) = part::part_raw(bound)(input)?;
// parse mime body
mparts.push(to_anypart(mime, rpart));
mparts.push(part::to_anypart(mime, rpart));
input_loop = input;
}
}
}
pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> {
match m {
AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart)
.map(|v| v.1)
.unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))),
AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart)
.map(|v| v.1)
.unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))),
AnyMIME::Txt(a) => AnyPart::Txt(Text(a, rpart)),
AnyMIME::Bin(a) => AnyPart::Bin(Binary(a, rpart)),
}
//--- Message
#[derive(Debug, PartialEq)]
pub struct Message<'a> {
pub interpreted: mime::mime::Message<'a>,
pub imf: imf::Imf<'a>,
pub child: Box<AnyPart<'a>>,
}
pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ {
move |input| {
recognize(many0(pair(
not(boundary(bound)),
alt((is_not(CRLF), obs_crlf)),
)))(input)
pub fn message<'a>(
m: mime::mime::Message<'a>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
move |input: &[u8]| {
let (input, fields): (_, CompFieldList<part::field::MixedField>) = header(part::field::mixed_field)(input)?;
let (in_mime, imf) = fields.sections();
let part = part::to_anypart(in_mime, input);
Ok((&[], Message { interpreted: m.clone(), imf, child: Box::new(part) }))
}
}
@ -183,47 +71,10 @@ mod tests {
use super::*;
use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord};
use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word};
use crate::part::discrete::Text;
use crate::part::AnyPart;
use chrono::{FixedOffset, TimeZone};
#[test]
fn test_preamble() {
assert_eq!(
part_raw(b"hello")(
b"blip
bloup
blip
bloup--
--bim
--bim--
--hello
Field: Body
"
),
Ok((
&b"\n--hello\nField: Body\n"[..],
&b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..],
))
);
}
#[test]
fn test_part_raw() {
assert_eq!(
part_raw(b"simple boundary")(b"Content-type: text/plain; charset=us-ascii
This is explicitly typed plain US-ASCII text.
It DOES end with a linebreak.
--simple boundary--
"),
Ok((
&b"\n--simple boundary--\n"[..],
&b"Content-type: text/plain; charset=us-ascii\n\nThis is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
))
);
}
#[test]
fn test_multipart() {
@ -255,31 +106,31 @@ It DOES end with a linebreak.
This is the epilogue. It is also to be ignored.
"),
Ok((&b"\nThis is the epilogue. It is also to be ignored.\n"[..],
Multipart(
base_mime,
vec![
AnyPart::Txt(Text(
mime::mime::Text(
Multipart {
interpreted: base_mime,
children: vec![
AnyPart::Txt(Text {
interpreted: mime::mime::Text(
mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Plain,
charset: mime::charset::EmailCharset::US_ASCII,
},
mime::mime::Generic::default(),
),
&b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..],
)),
AnyPart::Txt(Text(
mime::mime::Text(
body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..],
}),
AnyPart::Txt(Text {
interpreted: mime::mime::Text(
mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Plain,
charset: mime::charset::EmailCharset::US_ASCII,
},
mime::mime::Generic::default(),
),
&b"This is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
)),
body: &b"This is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
}),
],
),
},
))
);
}
@ -336,9 +187,9 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
message(base_mime.clone())(fullmail),
Ok((
&[][..],
Message (
base_mime,
imf::Imf {
Message {
interpreted: base_mime,
imf: imf::Imf {
date: Some(FixedOffset::east_opt(2 * 3600)
.unwrap()
.with_ymd_and_hms(2023, 07, 8, 7, 14, 29)
@ -403,17 +254,17 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
mime_version: Some(imf::mime::Version { major: 1, minor: 0}),
..imf::Imf::default()
},
Box::new(AnyPart::Mult(Multipart (
mime::mime::Multipart(
child: Box::new(AnyPart::Mult(Multipart {
interpreted: mime::mime::Multipart(
mime::r#type::Multipart {
subtype: mime::r#type::MultipartSubtype::Alternative,
boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(),
},
mime::mime::Generic::default(),
),
vec![
AnyPart::Txt(Text(
mime::mime::Text(
children: vec![
AnyPart::Txt(Text {
interpreted: mime::mime::Text(
mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Plain,
charset: mime::charset::EmailCharset::UTF_8,
@ -423,17 +274,17 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
..mime::mime::Generic::default()
}
),
&b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
)),
AnyPart::Txt(Text(
mime::mime::Text(
body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
}),
AnyPart::Txt(Text {
interpreted: mime::mime::Text(
mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Html,
charset: mime::charset::EmailCharset::US_ASCII,
},
mime::mime::Generic::default(),
),
&br#"<div style="text-align: center;"><strong>GZ</strong><br />
body: &br#"<div style="text-align: center;"><strong>GZ</strong><br />
OoOoO<br />
oOoOoOoOo<br />
oOoOoOoOoOoOoOoOo<br />
@ -442,10 +293,10 @@ oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
</div>
"#[..],
)),
}),
],
))),
),
})),
},
))
);
}

35
src/part/discrete.rs Normal file
View file

@ -0,0 +1,35 @@
use std::fmt;
use crate::mime;
#[derive(PartialEq)]
pub struct Text<'a> {
pub interpreted: mime::mime::Text<'a>,
pub body: &'a [u8]
}
impl<'a> fmt::Debug for Text<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("part::Text")
.field("mime", &self.interpreted)
.field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.body)))
.finish()
}
}
#[derive(PartialEq)]
pub struct Binary<'a> {
pub interpreted: mime::mime::Binary<'a>,
pub body: &'a [u8]
}
impl<'a> fmt::Debug for Binary<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("part::Binary")
.field("mime", &self.interpreted)
.field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.body)))
.finish()
}
}

View file

@ -0,0 +1,65 @@
use nom::{
IResult,
branch::alt,
combinator::map,
};
use crate::mime;
use crate::imf;
use crate::part::CompFieldList;
pub enum MixedField<'a> {
MIME(mime::field::Content<'a>),
IMF(imf::field::Field<'a>),
}
#[allow(dead_code)]
impl<'a> MixedField<'a> {
pub fn mime(&self) -> Option<&mime::field::Content<'a>> {
match self {
Self::MIME(v) => Some(v),
_ => None,
}
}
pub fn to_mime(self) -> Option<mime::field::Content<'a>> {
match self {
Self::MIME(v) => Some(v),
_ => None,
}
}
pub fn imf(&self) -> Option<&imf::field::Field<'a>> {
match self {
Self::IMF(v) => Some(v),
_ => None,
}
}
pub fn to_imf(self) -> Option<imf::field::Field<'a>> {
match self {
Self::IMF(v) => Some(v),
_ => None,
}
}
}
impl<'a> CompFieldList<'a, MixedField<'a>> {
pub fn sections(self) -> (mime::mime::AnyMIME<'a>, imf::Imf<'a>) {
let k = self.known();
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
k.into_iter().partition(|v| v.mime().is_some());
let mime = v1
.into_iter()
.filter_map(|v| v.to_mime())
.collect::<mime::mime::AnyMIME>();
let imf = v2
.into_iter()
.filter_map(|v| v.to_imf())
.collect::<imf::Imf>();
(mime, imf)
}
}
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
alt((
map(mime::field::content, MixedField::MIME),
map(imf::field::field, MixedField::IMF),
))(input)
}

View file

@ -1 +1,95 @@
pub mod part;
pub mod field;
pub mod composite;
pub mod discrete;
use nom::{
branch::alt,
bytes::complete::is_not,
combinator::{map, not, recognize},
multi::many0,
sequence::pair,
IResult,
};
use crate::header::CompFieldList;
use crate::mime;
use crate::mime::mime::AnyMIME;
use crate::text::ascii::CRLF;
use crate::text::boundary::boundary;
use crate::text::whitespace::obs_crlf;
use crate::part::{composite::{Multipart, Message, multipart, message}, discrete::{Text, Binary}};
#[derive(Debug, PartialEq)]
pub enum AnyPart<'a> {
Mult(Multipart<'a>),
Msg(Message<'a>),
Txt(Text<'a>),
Bin(Binary<'a>),
}
pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> {
match m {
AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart)
.map(|v| v.1)
.unwrap_or(AnyPart::Txt(Text { interpreted: mime::mime::Text::default(), body: rpart })),
AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart)
.map(|v| v.1)
.unwrap_or(AnyPart::Txt(Text { interpreted: mime::mime::Text::default(), body: rpart })),
AnyMIME::Txt(a) => AnyPart::Txt(Text { interpreted: a, body: rpart}),
AnyMIME::Bin(a) => AnyPart::Bin(Binary{ interpreted: a, body: rpart }),
}
}
pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ {
move |input| {
recognize(many0(pair(
not(boundary(bound)),
alt((is_not(CRLF), obs_crlf)),
)))(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preamble() {
assert_eq!(
part_raw(b"hello")(
b"blip
bloup
blip
bloup--
--bim
--bim--
--hello
Field: Body
"
),
Ok((
&b"\n--hello\nField: Body\n"[..],
&b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..],
))
);
}
#[test]
fn test_part_raw() {
assert_eq!(
part_raw(b"simple boundary")(b"Content-type: text/plain; charset=us-ascii
This is explicitly typed plain US-ASCII text.
It DOES end with a linebreak.
--simple boundary--
"),
Ok((
&b"\n--simple boundary--\n"[..],
&b"Content-type: text/plain; charset=us-ascii\n\nThis is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
))
);
}
}