refactor mime logic
This commit is contained in:
parent
6c8e738d43
commit
7195e5f661
5 changed files with 139 additions and 158 deletions
|
@ -37,6 +37,7 @@ pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::mime::r#type::*;
|
||||
use crate::mime::mime::*;
|
||||
use crate::mime::charset::EmailCharset;
|
||||
use crate::text::misc_token::MIMEWord;
|
||||
use crate::text::quoted::QuotedString;
|
||||
|
|
|
@ -8,8 +8,9 @@ use nom::{
|
|||
use crate::text::whitespace::cfws;
|
||||
use crate::text::words::mime_atom as token;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub enum Mechanism<'a> {
|
||||
#[default]
|
||||
_7Bit,
|
||||
_8Bit,
|
||||
Binary,
|
||||
|
|
137
src/mime/mime.rs
137
src/mime/mime.rs
|
@ -3,13 +3,14 @@ use crate::mime::mechanism::Mechanism;
|
|||
use crate::rfc5322::identification::MessageID;
|
||||
use crate::text::misc_token::Unstructured;
|
||||
use crate::mime::field::Content;
|
||||
use crate::mime::charset::EmailCharset;
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct MIME<'a> {
|
||||
pub content_type: Option<NaiveType<'a>>,
|
||||
pub content_transfer_encoding: Option<Mechanism<'a>>,
|
||||
pub content_id: Option<MessageID<'a>>,
|
||||
pub content_description: Option<Unstructured<'a>>,
|
||||
pub part_type: Type,
|
||||
pub transfer_encoding: Mechanism<'a>,
|
||||
pub id: Option<MessageID<'a>>,
|
||||
pub description: Option<Unstructured<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<Content<'a>> for MIME<'a> {
|
||||
|
@ -18,13 +19,133 @@ impl<'a> FromIterator<Content<'a>> for MIME<'a> {
|
|||
MIME::default(),
|
||||
|mut section, field| {
|
||||
match field {
|
||||
Content::Type(v) => section.content_type = Some(v),
|
||||
Content::TransferEncoding(v) => section.content_transfer_encoding = Some(v),
|
||||
Content::ID(v) => section.content_id = Some(v),
|
||||
Content::Description(v) => section.content_description = Some(v),
|
||||
Content::Type(v) => section.part_type = v.to_type(),
|
||||
Content::TransferEncoding(v) => section.transfer_encoding = v,
|
||||
Content::ID(v) => section.id = Some(v),
|
||||
Content::Description(v) => section.description = Some(v),
|
||||
};
|
||||
section
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// -------- TYPE
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Type {
|
||||
// Composite types
|
||||
Multipart(Multipart),
|
||||
Message(Message),
|
||||
|
||||
// Discrete types
|
||||
Text(Text),
|
||||
Binary,
|
||||
}
|
||||
impl Default for Type {
|
||||
fn default() -> Self {
|
||||
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" => 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 Multipart {
|
||||
pub subtype: MultipartSubtype,
|
||||
pub boundary: String,
|
||||
}
|
||||
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| Multipart {
|
||||
subtype: MultipartSubtype::from(nt),
|
||||
boundary: boundary.value.to_string(),
|
||||
})
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum MultipartSubtype {
|
||||
Alternative,
|
||||
Mixed,
|
||||
Digest,
|
||||
Parallel,
|
||||
Report,
|
||||
Unknown,
|
||||
}
|
||||
impl<'a> From<&NaiveType<'a>> for MultipartSubtype {
|
||||
fn from(nt: &NaiveType<'a>) -> Self {
|
||||
match nt.sub.to_ascii_lowercase().as_slice() {
|
||||
b"alternative" => Self::Alternative,
|
||||
b"mixed" => Self::Mixed,
|
||||
b"digest" => Self::Digest,
|
||||
b"parallel" => Self::Parallel,
|
||||
b"report" => Self::Report,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Message {
|
||||
RFC822,
|
||||
Partial,
|
||||
External,
|
||||
Unknown,
|
||||
}
|
||||
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,
|
||||
b"partial" => Self::Partial,
|
||||
b"external" => Self::External,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct Text {
|
||||
pub subtype: TextSubtype,
|
||||
pub charset: EmailCharset,
|
||||
}
|
||||
impl<'a> From<&NaiveType<'a>> for Text {
|
||||
fn from(nt: &NaiveType<'a>) -> Self {
|
||||
Self {
|
||||
subtype: TextSubtype::from(nt),
|
||||
charset: nt.params.iter()
|
||||
.find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset")
|
||||
.map(|x| EmailCharset::from(x.value.to_string().as_bytes()))
|
||||
.unwrap_or(EmailCharset::US_ASCII),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub enum TextSubtype {
|
||||
#[default]
|
||||
Plain,
|
||||
Html,
|
||||
Unknown,
|
||||
}
|
||||
impl<'a> From<&NaiveType<'a>> for TextSubtype {
|
||||
fn from(nt: &NaiveType<'a>) -> Self {
|
||||
match nt.sub.to_ascii_lowercase().as_slice() {
|
||||
b"plain" => Self::Plain,
|
||||
b"html" => Self::Html,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
123
src/mime/type.rs
123
src/mime/type.rs
|
@ -6,9 +6,9 @@ use nom::{
|
|||
IResult,
|
||||
};
|
||||
|
||||
use crate::mime::charset::EmailCharset;
|
||||
use crate::text::misc_token::{MIMEWord, mime_word};
|
||||
use crate::text::words::{mime_atom};
|
||||
use crate::mime::mime::{Type};
|
||||
|
||||
// --------- NAIVE TYPE
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -39,130 +39,13 @@ pub fn parameter_list(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> {
|
|||
many0(preceded(tag(";"), parameter))(input)
|
||||
}
|
||||
|
||||
// -------- TYPE
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Type {
|
||||
// Composite types
|
||||
Multipart(Multipart),
|
||||
Message(Message),
|
||||
|
||||
// Discrete types
|
||||
Text(Text),
|
||||
Binary,
|
||||
}
|
||||
impl Default for Type {
|
||||
fn default() -> Self {
|
||||
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" => 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 Multipart {
|
||||
pub subtype: MultipartSubtype,
|
||||
pub boundary: String,
|
||||
}
|
||||
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| Multipart {
|
||||
subtype: MultipartSubtype::from(nt),
|
||||
boundary: boundary.value.to_string(),
|
||||
})
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum MultipartSubtype {
|
||||
Alternative,
|
||||
Mixed,
|
||||
Digest,
|
||||
Parallel,
|
||||
Report,
|
||||
Unknown,
|
||||
}
|
||||
impl<'a> From<&NaiveType<'a>> for MultipartSubtype {
|
||||
fn from(nt: &NaiveType<'a>) -> Self {
|
||||
match nt.sub.to_ascii_lowercase().as_slice() {
|
||||
b"alternative" => Self::Alternative,
|
||||
b"mixed" => Self::Mixed,
|
||||
b"digest" => Self::Digest,
|
||||
b"parallel" => Self::Parallel,
|
||||
b"report" => Self::Report,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Message {
|
||||
RFC822,
|
||||
Partial,
|
||||
External,
|
||||
Unknown,
|
||||
}
|
||||
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,
|
||||
b"partial" => Self::Partial,
|
||||
b"external" => Self::External,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct Text {
|
||||
pub subtype: TextSubtype,
|
||||
pub charset: EmailCharset,
|
||||
}
|
||||
impl<'a> From<&NaiveType<'a>> for Text {
|
||||
fn from(nt: &NaiveType<'a>) -> Self {
|
||||
Self {
|
||||
subtype: TextSubtype::from(nt),
|
||||
charset: nt.params.iter()
|
||||
.find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset")
|
||||
.map(|x| EmailCharset::from(x.value.to_string().as_bytes()))
|
||||
.unwrap_or(EmailCharset::US_ASCII),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub enum TextSubtype {
|
||||
#[default]
|
||||
Plain,
|
||||
Html,
|
||||
Unknown,
|
||||
}
|
||||
impl<'a> From<&NaiveType<'a>> for TextSubtype {
|
||||
fn from(nt: &NaiveType<'a>) -> Self {
|
||||
match nt.sub.to_ascii_lowercase().as_slice() {
|
||||
b"plain" => Self::Plain,
|
||||
b"html" => Self::Html,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mime::charset::EmailCharset;
|
||||
use crate::text::quoted::QuotedString;
|
||||
use crate::mime::mime::*;
|
||||
|
||||
#[test]
|
||||
fn test_parameter() {
|
||||
|
|
|
@ -19,7 +19,7 @@ pub struct Part<'a> {
|
|||
pub fn message() -> IResult<&[u8], Part> {
|
||||
}
|
||||
|
||||
pub fn multipart<'a>(ctype: Type) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part<'a>> {
|
||||
pub fn multipart<'a>(ctype: Multipart) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part<'a>> {
|
||||
move |input: &[u8]| {
|
||||
let (mut input_loop, _) = preamble(ctype.boundary)(input)?;
|
||||
let mut parts: Vec<Part> = vec![];
|
||||
|
@ -31,7 +31,9 @@ pub fn multipart<'a>(ctype: Type) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part
|
|||
};
|
||||
|
||||
// parse mime headers
|
||||
header(content)(input)?;
|
||||
let (input, fields) = header_in_boundaries(ctype.boundary, content)(input)?;
|
||||
let mime = fields.to_mime();
|
||||
match mime.
|
||||
|
||||
// based on headers, parse part
|
||||
|
||||
|
@ -72,33 +74,6 @@ pub fn preamble<'a>(bound: &'a [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &
|
|||
|
||||
// FIXME parse email here
|
||||
|
||||
|
||||
// Returns Ok even if an error is encountered while parsing
|
||||
// the different mimes.
|
||||
pub fn multipart<'a>(bound: &'a [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> {
|
||||
move |input: &[u8]| {
|
||||
let (mut input_loop, _) = preamble(bound)(input)?;
|
||||
let mut parts: Vec<&[u8]> = vec![];
|
||||
loop {
|
||||
let input = match boundary(bound)(input_loop) {
|
||||
Err(_) => return Ok((input_loop, parts)),
|
||||
Ok((inp, Delimiter::Last)) => return Ok((inp, parts)),
|
||||
Ok((inp, Delimiter::Next)) => inp,
|
||||
};
|
||||
|
||||
let input = match part(bound)(input) {
|
||||
Err(_) => return Ok((input, parts)),
|
||||
Ok((inp, part)) => {
|
||||
parts.push(part);
|
||||
inp
|
||||
}
|
||||
};
|
||||
|
||||
input_loop = input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Reference in a new issue