support multipart/digest parsing
This commit is contained in:
parent
9ddd480b0f
commit
4a46faa670
6 changed files with 51 additions and 25 deletions
|
@ -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.
|
- 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.
|
- 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.).
|
- 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)
|
[See more about this library goals in the doc/ folder](./doc/goals.md)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use nom::{
|
||||||
use crate::header::{field_name, CompFieldList};
|
use crate::header::{field_name, CompFieldList};
|
||||||
use crate::imf::identification::{msg_id, MessageID};
|
use crate::imf::identification::{msg_id, MessageID};
|
||||||
use crate::mime::mechanism::{mechanism, Mechanism};
|
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::mime::r#type::{naive_type, NaiveType};
|
||||||
use crate::text::misc_token::{unstructured, Unstructured};
|
use crate::text::misc_token::{unstructured, Unstructured};
|
||||||
use crate::text::whitespace::obs_crlf;
|
use crate::text::whitespace::obs_crlf;
|
||||||
|
@ -49,8 +49,8 @@ impl<'a> Content<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompFieldList<'a, Content<'a>> {
|
impl<'a> CompFieldList<'a, Content<'a>> {
|
||||||
pub fn to_mime(self) -> AnyMIME<'a> {
|
pub fn to_mime<T: WithDefaultType> (self) -> AnyMIMEWithDefault<'a, T> {
|
||||||
self.known().into_iter().collect::<AnyMIME>()
|
self.known().into_iter().collect::<AnyMIMEWithDefault<T>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ pub mod mechanism;
|
||||||
/// Content-Type representation
|
/// Content-Type representation
|
||||||
pub mod r#type;
|
pub mod r#type;
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::imf::identification::MessageID;
|
use crate::imf::identification::MessageID;
|
||||||
use crate::mime::field::Content;
|
use crate::mime::field::Content;
|
||||||
use crate::mime::mechanism::Mechanism;
|
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 {
|
fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self {
|
||||||
let (at, gen) = it.into_iter().fold(
|
let (at, gen) = it.into_iter().fold(
|
||||||
(AnyType::default(), Generic::default()),
|
(T::default_type(), Generic::default()),
|
||||||
|(mut at, mut section), field| {
|
|(mut at, mut section), field| {
|
||||||
match field {
|
match field {
|
||||||
Content::Type(v) => at = v.to_type(),
|
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>>,
|
|
||||||
}
|
|
||||||
|
|
|
@ -57,17 +57,12 @@ pub enum AnyType {
|
||||||
Binary(Binary),
|
Binary(Binary),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AnyType {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Text(Text::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> From<&'a NaiveType<'a>> for AnyType {
|
impl<'a> From<&'a NaiveType<'a>> for AnyType {
|
||||||
fn from(nt: &'a NaiveType<'a>) -> Self {
|
fn from(nt: &'a NaiveType<'a>) -> Self {
|
||||||
match nt.main.to_ascii_lowercase().as_slice() {
|
match nt.main.to_ascii_lowercase().as_slice() {
|
||||||
b"multipart" => Multipart::try_from(nt)
|
b"multipart" => Multipart::try_from(nt)
|
||||||
.map(Self::Multipart)
|
.map(Self::Multipart)
|
||||||
.unwrap_or(Self::default()),
|
.unwrap_or(Self::Text(Text::default())),
|
||||||
b"message" => Self::Message(Message::from(nt)),
|
b"message" => Self::Message(Message::from(nt)),
|
||||||
b"text" => Self::Text(Text::from(nt)),
|
b"text" => Self::Text(Text::from(nt)),
|
||||||
_ => Self::Binary(Binary::default()),
|
_ => Self::Binary(Binary::default()),
|
||||||
|
|
|
@ -47,7 +47,10 @@ pub fn multipart<'a>(
|
||||||
|
|
||||||
// parse mime headers
|
// parse mime headers
|
||||||
let (input, fields) = header(mime::field::content)(input)?;
|
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
|
// parse raw part
|
||||||
let (input, rpart) = part::part_raw(bound)(input)?;
|
let (input, rpart) = part::part_raw(bound)(input)?;
|
||||||
|
@ -75,7 +78,7 @@ pub fn message<'a>(
|
||||||
move |input: &[u8]| {
|
move |input: &[u8]| {
|
||||||
let (input, fields): (_, CompFieldList<part::field::MixedField>) =
|
let (input, fields): (_, CompFieldList<part::field::MixedField>) =
|
||||||
header(part::field::mixed_field)(input)?;
|
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);
|
let part = part::to_anypart(in_mime, input);
|
||||||
|
|
||||||
|
|
|
@ -36,19 +36,19 @@ impl<'a> MixedField<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> CompFieldList<'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 k = self.known();
|
||||||
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
|
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
|
||||||
k.into_iter().partition(|v| v.mime().is_some());
|
k.into_iter().partition(|v| v.mime().is_some());
|
||||||
let mime = v1
|
let mime = v1
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.to_mime())
|
.filter_map(|v| v.to_mime())
|
||||||
.collect::<mime::AnyMIME>();
|
.collect::<mime::AnyMIMEWithDefault<T>>();
|
||||||
let imf = v2
|
let imf = v2
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.to_imf())
|
.filter_map(|v| v.to_imf())
|
||||||
.collect::<imf::Imf>();
|
.collect::<imf::Imf>();
|
||||||
(mime, imf)
|
(mime.into(), imf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
|
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
|
||||||
|
|
Loading…
Reference in a new issue