generic header support

This commit is contained in:
Quentin 2023-07-22 13:51:19 +02:00
parent 4baa976283
commit 59f550b439
Signed by: quentin
GPG key ID: E9602264D639FF68
4 changed files with 75 additions and 26 deletions

View file

@ -1,9 +1,13 @@
use nom::{ use nom::{
IResult, IResult,
branch::alt,
bytes::complete::{tag_no_case, tag, take_while1}, bytes::complete::{tag_no_case, tag, take_while1},
character::complete::space0, character::complete::space0,
combinator::map,
multi::many0,
sequence::{pair, terminated, tuple}, sequence::{pair, terminated, tuple},
}; };
use crate::text::whitespace::{foldable_line, obs_crlf};
use crate::text::misc_token::{Unstructured, unstructured}; use crate::text::misc_token::{Unstructured, unstructured};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -24,6 +28,16 @@ impl<'a, T> CompFieldList<'a, T> {
} }
} }
pub fn header<'a, T>(fx: impl Fn(&'a [u8]) -> IResult<&[u8], T> + Copy)
-> impl Fn(&'a [u8]) -> IResult<&[u8], CompFieldList<T>>
{
move |input| map(terminated(many0(alt((
map(fx, CompField::Known),
map(opt_field, |(k,v)| CompField::Unknown(k,v)),
map(foldable_line, CompField::Bad),
))), obs_crlf), CompFieldList)(input)
}
pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> { pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
move |input| { move |input| {
terminated( terminated(
@ -43,13 +57,14 @@ pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [
/// ; ":". /// ; ":".
/// ``` /// ```
pub fn opt_field(input: &[u8]) -> IResult<&[u8], (&[u8], Unstructured)> { pub fn opt_field(input: &[u8]) -> IResult<&[u8], (&[u8], Unstructured)> {
terminated(
pair( pair(
terminated( terminated(
take_while1(|c| c >= 0x21 && c <= 0x7E && c != 0x3A), take_while1(|c| c >= 0x21 && c <= 0x7E && c != 0x3A),
tuple((space0, tag(b":"), space0)), tuple((space0, tag(b":"), space0)),
), ),
unstructured, unstructured,
)(input) ), obs_crlf)(input)
} }

View file

@ -8,9 +8,10 @@ use nom::{
use crate::text::whitespace::obs_crlf; use crate::text::whitespace::obs_crlf;
use crate::text::misc_token::{Unstructured, unstructured}; use crate::text::misc_token::{Unstructured, unstructured};
use crate::rfc5322::identification::{MessageID, msg_id}; use crate::rfc5322::identification::{MessageID, msg_id};
use crate::header::field_name; use crate::header::{field_name, header, CompFieldList};
use crate::mime::r#type::{NaiveType, naive_type}; use crate::mime::r#type::{NaiveType, naive_type};
use crate::mime::mechanism::{Mechanism, mechanism}; use crate::mime::mechanism::{Mechanism, mechanism};
//use crate::mime::mime::MIME;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Content<'a> { pub enum Content<'a> {
@ -19,8 +20,11 @@ pub enum Content<'a> {
ID(MessageID<'a>), ID(MessageID<'a>),
Description(Unstructured<'a>), Description(Unstructured<'a>),
} }
/*impl<'a> CompFieldList<Content<'a>> {
pub fn to_mime(&self) -> MIME { self.into() }
}*/
fn field(input: &[u8]) -> IResult<&[u8], Content> { fn content(input: &[u8]) -> IResult<&[u8], Content> {
terminated(alt(( terminated(alt((
preceded(field_name(b"content-type"), map(naive_type, Content::Type)), preceded(field_name(b"content-type"), map(naive_type, Content::Type)),
preceded(field_name(b"content-transfer-encoding"), map(mechanism, Content::TransferEncoding)), preceded(field_name(b"content-transfer-encoding"), map(mechanism, Content::TransferEncoding)),
@ -34,10 +38,12 @@ mod tests {
use super::*; use super::*;
use crate::mime::r#type::*; use crate::mime::r#type::*;
use crate::mime::charset::EmailCharset; use crate::mime::charset::EmailCharset;
use crate::text::misc_token::MIMEWord;
use crate::text::quoted::QuotedString;
#[test] #[test]
fn test_content_type() { fn test_content_type() {
let (rest, content) = field(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap(); let (rest, content) = content(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap();
assert_eq!(&b""[..], rest); assert_eq!(&b""[..], rest);
if let Content::Type(nt) = content { if let Content::Type(nt) = content {
@ -52,4 +58,41 @@ mod tests {
panic!("Expected Content::Type, got {:?}", content); panic!("Expected Content::Type, got {:?}", content);
} }
} }
#[test]
fn test_header() {
let fullmail: &[u8] = r#"Date: Sat, 8 Jul 2023 07:14:29 +0200
From: Grrrnd Zero <grrrndzero@example.org>
To: John Doe <jdoe@machine.example>
Subject: Re: Saying Hello
Message-ID: <NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="b1_e376dc71bafc953c0b0fdeb9983a9956"
Content-Transfer-Encoding: 7bit
This is a multipart message.
"#.as_bytes();
assert_eq!(
map(header(content), CompFieldList::known)(fullmail),
Ok((
&b"This is a multipart message.\n\n"[..],
vec![
Content::Type(NaiveType {
main: &b"multipart"[..],
sub: &b"alternative"[..],
params: vec![
Parameter {
name: &b"boundary"[..],
value: MIMEWord::Quoted(QuotedString(vec![&b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]])),
}
]
}),
Content::TransferEncoding(Mechanism::_7Bit),
],
)),
);
}
} }

View file

@ -13,9 +13,9 @@ use crate::text::words::{mime_atom};
// --------- NAIVE TYPE // --------- NAIVE TYPE
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct NaiveType<'a> { pub struct NaiveType<'a> {
main: &'a [u8], pub main: &'a [u8],
sub: &'a [u8], pub sub: &'a [u8],
params: Vec<Parameter<'a>>, pub params: Vec<Parameter<'a>>,
} }
impl<'a> NaiveType<'a> { impl<'a> NaiveType<'a> {
pub fn to_type(&self) -> Type { self.into() } pub fn to_type(&self) -> Type { self.into() }
@ -29,8 +29,8 @@ pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Parameter<'a> { pub struct Parameter<'a> {
name: &'a [u8], pub name: &'a [u8],
value: MIMEWord<'a>, pub value: MIMEWord<'a>,
} }
pub fn parameter(input: &[u8]) -> IResult<&[u8], Parameter> { pub fn parameter(input: &[u8]) -> IResult<&[u8], Parameter> {
map(tuple((mime_atom, tag(b"="), mime_word)), |(name, _, value)| Parameter { name, value })(input) map(tuple((mime_atom, tag(b"="), mime_word)), |(name, _, value)| Parameter { name, value })(input)

View file

@ -15,7 +15,7 @@ use crate::rfc5322::identification::{MessageID, MessageIDList, msg_id, msg_list}
use crate::rfc5322::trace::{ReceivedLog, return_path, received_log}; use crate::rfc5322::trace::{ReceivedLog, return_path, received_log};
use crate::rfc5322::mime::{Version, version}; use crate::rfc5322::mime::{Version, version};
use crate::rfc5322::message::Message; use crate::rfc5322::message::Message;
use crate::header::*; use crate::header::{header, field_name, CompFieldList};
use crate::text::misc_token::{Unstructured, PhraseList, unstructured, phrase_list}; use crate::text::misc_token::{Unstructured, PhraseList, unstructured, phrase_list};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -87,15 +87,6 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
)), obs_crlf)(input) )), obs_crlf)(input)
} }
pub fn header(input: &[u8]) -> IResult<&[u8], CompFieldList<Field>> {
map(terminated(many0(alt((
map(field, CompField::Known),
map(opt_field, |(k,v)| CompField::Unknown(k,v)),
map(foldable_line, CompField::Bad),
))), obs_crlf), CompFieldList)(input)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -115,7 +106,7 @@ This is the plain text body of the message. Note the blank line
between the header information and the body of the message."; between the header information and the body of the message.";
assert_eq!( assert_eq!(
map(header, |v| FieldList(v.known()).message())(fullmail), map(header(field), |v| FieldList(v.known()).message())(fullmail),
Ok(( Ok((
&b"This is the plain text body of the message. Note the blank line\nbetween the header information and the body of the message."[..], &b"This is the plain text body of the message. Note the blank line\nbetween the header information and the body of the message."[..],
Message { Message {