support multipart/digest parsing

This commit is contained in:
Quentin 2023-07-24 18:01:37 +02:00
parent 9ddd480b0f
commit 4a46faa670
Signed by: quentin
GPG key ID: E9602264D639FF68
6 changed files with 51 additions and 25 deletions

View file

@ -37,7 +37,7 @@ This library does not aim at implementing a specific RFC, but to be a swiss-army
- Maintainability - modifying the code does not create regression and is possible for someone exterior to the project.
- Compatibility - always try to parse something, do not panic or return an error.
- Exhaustivity - serve as a common project to encode knowledge about emails (existing mime types, existing headers, etc.).
- Type safe - do not manipulate only strings/bytes but leverage Rust type system instead so you benefit of its safety checks at compile time.
- Type safe - do not manipulate only strings/bytes but leverage Rust type system instead so you benefit from its safety checks at compile time.
[See more about this library goals in the doc/ folder](./doc/goals.md)

View file

@ -8,7 +8,7 @@ use nom::{
use crate::header::{field_name, CompFieldList};
use crate::imf::identification::{msg_id, MessageID};
use crate::mime::mechanism::{mechanism, Mechanism};
use crate::mime::AnyMIME;
use crate::mime::{AnyMIMEWithDefault, WithDefaultType};
use crate::mime::r#type::{naive_type, NaiveType};
use crate::text::misc_token::{unstructured, Unstructured};
use crate::text::whitespace::obs_crlf;
@ -49,8 +49,8 @@ impl<'a> Content<'a> {
}
impl<'a> CompFieldList<'a, Content<'a>> {
pub fn to_mime(self) -> AnyMIME<'a> {
self.known().into_iter().collect::<AnyMIME>()
pub fn to_mime<T: WithDefaultType> (self) -> AnyMIMEWithDefault<'a, T> {
self.known().into_iter().collect::<AnyMIMEWithDefault<T>>()
}
}

View file

@ -10,6 +10,8 @@ pub mod mechanism;
/// Content-Type representation
pub mod r#type;
use std::marker::PhantomData;
use crate::imf::identification::MessageID;
use crate::mime::field::Content;
use crate::mime::mechanism::Mechanism;
@ -47,10 +49,43 @@ impl<'a> AnyMIME<'a> {
}
}
impl<'a> FromIterator<Content<'a>> for AnyMIME<'a> {
impl<'a, T: WithDefaultType> From<AnyMIMEWithDefault<'a, T>> for AnyMIME<'a> {
fn from(a: AnyMIMEWithDefault<'a, T>) -> Self {
a.0
}
}
#[derive(Debug, PartialEq, Default, Clone)]
pub struct Generic<'a> {
pub transfer_encoding: Mechanism<'a>,
pub id: Option<MessageID<'a>>,
pub description: Option<Unstructured<'a>>,
}
pub trait WithDefaultType {
fn default_type() -> AnyType;
}
pub struct WithGenericDefault {}
impl WithDefaultType for WithGenericDefault {
fn default_type() -> AnyType {
AnyType::Text(r#type::Text::default())
}
}
pub struct WithDigestDefault {}
impl WithDefaultType for WithDigestDefault {
fn default_type() -> AnyType {
AnyType::Message(r#type::Message::default())
}
}
#[derive(Debug, PartialEq)]
pub struct AnyMIMEWithDefault<'a, T: WithDefaultType>(pub AnyMIME<'a>, PhantomData<T>);
impl<'a, T: WithDefaultType> FromIterator<Content<'a>> for AnyMIMEWithDefault<'a, T> {
fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self {
let (at, gen) = it.into_iter().fold(
(AnyType::default(), Generic::default()),
(T::default_type(), Generic::default()),
|(mut at, mut section), field| {
match field {
Content::Type(v) => at = v.to_type(),
@ -62,13 +97,6 @@ impl<'a> FromIterator<Content<'a>> for AnyMIME<'a> {
},
);
Self::from_pair(at, gen)
Self(AnyMIME::from_pair(at, gen), PhantomData)
}
}
#[derive(Debug, PartialEq, Default, Clone)]
pub struct Generic<'a> {
pub transfer_encoding: Mechanism<'a>,
pub id: Option<MessageID<'a>>,
pub description: Option<Unstructured<'a>>,
}

View file

@ -57,17 +57,12 @@ pub enum AnyType {
Binary(Binary),
}
impl Default for AnyType {
fn default() -> Self {
Self::Text(Text::default())
}
}
impl<'a> From<&'a NaiveType<'a>> for AnyType {
fn from(nt: &'a NaiveType<'a>) -> Self {
match nt.main.to_ascii_lowercase().as_slice() {
b"multipart" => Multipart::try_from(nt)
.map(Self::Multipart)
.unwrap_or(Self::default()),
.unwrap_or(Self::Text(Text::default())),
b"message" => Self::Message(Message::from(nt)),
b"text" => Self::Text(Text::from(nt)),
_ => Self::Binary(Binary::default()),

View file

@ -47,7 +47,10 @@ pub fn multipart<'a>(
// parse mime headers
let (input, fields) = header(mime::field::content)(input)?;
let mime = fields.to_mime();
let mime = match m.0.subtype {
mime::r#type::MultipartSubtype::Digest => fields.to_mime::<mime::WithDigestDefault>().into(),
_ => fields.to_mime::<mime::WithGenericDefault>().into(),
};
// parse raw part
let (input, rpart) = part::part_raw(bound)(input)?;
@ -75,7 +78,7 @@ pub fn 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 (in_mime, imf) = fields.sections::<mime::WithGenericDefault>();
let part = part::to_anypart(in_mime, input);

View file

@ -36,19 +36,19 @@ impl<'a> MixedField<'a> {
}
}
impl<'a> CompFieldList<'a, MixedField<'a>> {
pub fn sections(self) -> (mime::AnyMIME<'a>, imf::Imf<'a>) {
pub fn sections<T: mime::WithDefaultType>(self) -> (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::AnyMIME>();
.collect::<mime::AnyMIMEWithDefault<T>>();
let imf = v2
.into_iter()
.filter_map(|v| v.to_imf())
.collect::<imf::Imf>();
(mime, imf)
(mime.into(), imf)
}
}
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {