split part mod in multiple files
This commit is contained in:
parent
e76cd78e4f
commit
5cfa06dbf8
8 changed files with 261 additions and 216 deletions
|
@ -19,8 +19,8 @@ between the header information and the body of the message."#;
|
||||||
let email = eml_codec::email(input).unwrap();
|
let email = eml_codec::email(input).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"{} just sent you an email with subject \"{}\"",
|
"{} just sent you an email with subject \"{}\"",
|
||||||
email.1.from[0].to_string(),
|
email.imf.from[0].to_string(),
|
||||||
email.1.subject.unwrap().to_string(),
|
email.imf.subject.unwrap().to_string(),
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ between the header information and the body of the message."#;
|
||||||
let email = eml_codec::email(input).unwrap();
|
let email = eml_codec::email(input).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"{} just sent you an email with subject \"{}\"",
|
"{} just sent you an email with subject \"{}\"",
|
||||||
email.1.from[0].to_string(),
|
email.imf.from[0].to_string(),
|
||||||
email.1.subject.unwrap().to_string(),
|
email.imf.subject.unwrap().to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ mod part;
|
||||||
mod imf;
|
mod imf;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
pub fn email(input: &[u8]) -> Result<part::part::Message, error::EMLError> {
|
pub fn email(input: &[u8]) -> Result<part::composite::Message, error::EMLError> {
|
||||||
part::part::message(mime::mime::Message::default())(input)
|
part::composite::message(mime::mime::Message::default())(input)
|
||||||
.map(|(_, v)| v)
|
.map(|(_, v)| v)
|
||||||
.map_err(error::EMLError::ParseError)
|
.map_err(error::EMLError::ParseError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ fn main() {
|
||||||
|
|
||||||
let eml = eml_codec::email(&rawmail).unwrap();
|
let eml = eml_codec::email(&rawmail).unwrap();
|
||||||
println!("{:#?}", eml);
|
println!("{:#?}", eml);
|
||||||
assert!(eml.1.date.is_some());
|
assert!(eml.imf.date.is_some());
|
||||||
assert!(!eml.1.from.is_empty());
|
assert!(!eml.imf.from.is_empty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,128 +1,16 @@
|
||||||
use std::fmt;
|
use nom::IResult;
|
||||||
use nom::{
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::is_not,
|
|
||||||
combinator::{map, not, recognize},
|
|
||||||
multi::many0,
|
|
||||||
sequence::pair,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::header::{header, CompFieldList};
|
use crate::header::{header, CompFieldList};
|
||||||
use crate::mime;
|
use crate::mime;
|
||||||
use crate::mime::mime::AnyMIME;
|
|
||||||
use crate::imf::{self as imf};
|
use crate::imf::{self as imf};
|
||||||
use crate::text::ascii::CRLF;
|
|
||||||
use crate::text::boundary::{boundary, Delimiter};
|
use crate::text::boundary::{boundary, Delimiter};
|
||||||
use crate::text::whitespace::obs_crlf;
|
use crate::part::{AnyPart, self};
|
||||||
|
|
||||||
|
//--- Multipart
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Multipart<'a>(pub mime::mime::Multipart<'a>, pub Vec<AnyPart<'a>>);
|
pub struct Multipart<'a> {
|
||||||
|
pub interpreted: mime::mime::Multipart<'a>,
|
||||||
#[derive(Debug, PartialEq)]
|
pub children: Vec<AnyPart<'a>>
|
||||||
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 fn multipart<'a>(
|
pub fn multipart<'a>(
|
||||||
|
@ -132,12 +20,12 @@ pub fn multipart<'a>(
|
||||||
|
|
||||||
move |input| {
|
move |input| {
|
||||||
let bound = m.0.boundary.as_bytes();
|
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![];
|
let mut mparts: Vec<AnyPart> = vec![];
|
||||||
loop {
|
loop {
|
||||||
let input = match boundary(bound)(input_loop) {
|
let input = match boundary(bound)(input_loop) {
|
||||||
Err(_) => return Ok((input_loop, Multipart(m.clone(), mparts))),
|
Err(_) => return Ok((input_loop, Multipart { interpreted: m.clone(), children: mparts })),
|
||||||
Ok((inp, Delimiter::Last)) => return Ok((inp, Multipart(m.clone(), mparts))),
|
Ok((inp, Delimiter::Last)) => return Ok((inp, Multipart{ interpreted: m.clone(), children: mparts})),
|
||||||
Ok((inp, Delimiter::Next)) => inp,
|
Ok((inp, Delimiter::Next)) => inp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,35 +34,35 @@ pub fn multipart<'a>(
|
||||||
let mime = fields.to_mime();
|
let mime = fields.to_mime();
|
||||||
|
|
||||||
// parse raw part
|
// parse raw part
|
||||||
let (input, rpart) = part_raw(bound)(input)?;
|
let (input, rpart) = part::part_raw(bound)(input)?;
|
||||||
|
|
||||||
// parse mime body
|
// parse mime body
|
||||||
mparts.push(to_anypart(mime, rpart));
|
mparts.push(part::to_anypart(mime, rpart));
|
||||||
|
|
||||||
input_loop = input;
|
input_loop = input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> {
|
//--- Message
|
||||||
match m {
|
|
||||||
AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart)
|
#[derive(Debug, PartialEq)]
|
||||||
.map(|v| v.1)
|
pub struct Message<'a> {
|
||||||
.unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))),
|
pub interpreted: mime::mime::Message<'a>,
|
||||||
AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart)
|
pub imf: imf::Imf<'a>,
|
||||||
.map(|v| v.1)
|
pub child: Box<AnyPart<'a>>,
|
||||||
.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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ {
|
pub fn message<'a>(
|
||||||
move |input| {
|
m: mime::mime::Message<'a>,
|
||||||
recognize(many0(pair(
|
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
|
||||||
not(boundary(bound)),
|
move |input: &[u8]| {
|
||||||
alt((is_not(CRLF), obs_crlf)),
|
let (input, fields): (_, CompFieldList<part::field::MixedField>) = header(part::field::mixed_field)(input)?;
|
||||||
)))(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 super::*;
|
||||||
use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord};
|
use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord};
|
||||||
use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word};
|
use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word};
|
||||||
|
use crate::part::discrete::Text;
|
||||||
|
use crate::part::AnyPart;
|
||||||
use chrono::{FixedOffset, TimeZone};
|
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]
|
#[test]
|
||||||
fn test_multipart() {
|
fn test_multipart() {
|
||||||
|
@ -255,31 +106,31 @@ It DOES end with a linebreak.
|
||||||
This is the epilogue. It is also to be ignored.
|
This is the epilogue. It is also to be ignored.
|
||||||
"),
|
"),
|
||||||
Ok((&b"\nThis is the epilogue. It is also to be ignored.\n"[..],
|
Ok((&b"\nThis is the epilogue. It is also to be ignored.\n"[..],
|
||||||
Multipart(
|
Multipart {
|
||||||
base_mime,
|
interpreted: base_mime,
|
||||||
vec![
|
children: vec![
|
||||||
AnyPart::Txt(Text(
|
AnyPart::Txt(Text {
|
||||||
mime::mime::Text(
|
interpreted: mime::mime::Text(
|
||||||
mime::r#type::Text {
|
mime::r#type::Text {
|
||||||
subtype: mime::r#type::TextSubtype::Plain,
|
subtype: mime::r#type::TextSubtype::Plain,
|
||||||
charset: mime::charset::EmailCharset::US_ASCII,
|
charset: mime::charset::EmailCharset::US_ASCII,
|
||||||
},
|
},
|
||||||
mime::mime::Generic::default(),
|
mime::mime::Generic::default(),
|
||||||
),
|
),
|
||||||
&b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..],
|
body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..],
|
||||||
)),
|
}),
|
||||||
AnyPart::Txt(Text(
|
AnyPart::Txt(Text {
|
||||||
mime::mime::Text(
|
interpreted: mime::mime::Text(
|
||||||
mime::r#type::Text {
|
mime::r#type::Text {
|
||||||
subtype: mime::r#type::TextSubtype::Plain,
|
subtype: mime::r#type::TextSubtype::Plain,
|
||||||
charset: mime::charset::EmailCharset::US_ASCII,
|
charset: mime::charset::EmailCharset::US_ASCII,
|
||||||
},
|
},
|
||||||
mime::mime::Generic::default(),
|
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),
|
message(base_mime.clone())(fullmail),
|
||||||
Ok((
|
Ok((
|
||||||
&[][..],
|
&[][..],
|
||||||
Message (
|
Message {
|
||||||
base_mime,
|
interpreted: base_mime,
|
||||||
imf::Imf {
|
imf: imf::Imf {
|
||||||
date: Some(FixedOffset::east_opt(2 * 3600)
|
date: Some(FixedOffset::east_opt(2 * 3600)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_ymd_and_hms(2023, 07, 8, 7, 14, 29)
|
.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}),
|
mime_version: Some(imf::mime::Version { major: 1, minor: 0}),
|
||||||
..imf::Imf::default()
|
..imf::Imf::default()
|
||||||
},
|
},
|
||||||
Box::new(AnyPart::Mult(Multipart (
|
child: Box::new(AnyPart::Mult(Multipart {
|
||||||
mime::mime::Multipart(
|
interpreted: mime::mime::Multipart(
|
||||||
mime::r#type::Multipart {
|
mime::r#type::Multipart {
|
||||||
subtype: mime::r#type::MultipartSubtype::Alternative,
|
subtype: mime::r#type::MultipartSubtype::Alternative,
|
||||||
boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(),
|
boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(),
|
||||||
},
|
},
|
||||||
mime::mime::Generic::default(),
|
mime::mime::Generic::default(),
|
||||||
),
|
),
|
||||||
vec![
|
children: vec![
|
||||||
AnyPart::Txt(Text(
|
AnyPart::Txt(Text {
|
||||||
mime::mime::Text(
|
interpreted: mime::mime::Text(
|
||||||
mime::r#type::Text {
|
mime::r#type::Text {
|
||||||
subtype: mime::r#type::TextSubtype::Plain,
|
subtype: mime::r#type::TextSubtype::Plain,
|
||||||
charset: mime::charset::EmailCharset::UTF_8,
|
charset: mime::charset::EmailCharset::UTF_8,
|
||||||
|
@ -423,17 +274,17 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
|
||||||
..mime::mime::Generic::default()
|
..mime::mime::Generic::default()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
&b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
|
body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
|
||||||
)),
|
}),
|
||||||
AnyPart::Txt(Text(
|
AnyPart::Txt(Text {
|
||||||
mime::mime::Text(
|
interpreted: mime::mime::Text(
|
||||||
mime::r#type::Text {
|
mime::r#type::Text {
|
||||||
subtype: mime::r#type::TextSubtype::Html,
|
subtype: mime::r#type::TextSubtype::Html,
|
||||||
charset: mime::charset::EmailCharset::US_ASCII,
|
charset: mime::charset::EmailCharset::US_ASCII,
|
||||||
},
|
},
|
||||||
mime::mime::Generic::default(),
|
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 />
|
OoOoO<br />
|
||||||
oOoOoOoOo<br />
|
oOoOoOoOo<br />
|
||||||
oOoOoOoOoOoOoOoOo<br />
|
oOoOoOoOoOoOoOoOo<br />
|
||||||
|
@ -442,10 +293,10 @@ oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
|
||||||
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
|
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
|
||||||
</div>
|
</div>
|
||||||
"#[..],
|
"#[..],
|
||||||
)),
|
}),
|
||||||
],
|
],
|
||||||
))),
|
})),
|
||||||
),
|
},
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
35
src/part/discrete.rs
Normal file
35
src/part/discrete.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"[..],
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue