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::{
IResult,
branch::alt,
bytes::complete::{tag_no_case, tag, take_while1},
character::complete::space0,
combinator::map,
multi::many0,
sequence::{pair, terminated, tuple},
};
use crate::text::whitespace::{foldable_line, obs_crlf};
use crate::text::misc_token::{Unstructured, unstructured};
#[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]> {
move |input| {
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)> {
pair(
terminated(
take_while1(|c| c >= 0x21 && c <= 0x7E && c != 0x3A),
tuple((space0, tag(b":"), space0)),
),
unstructured,
)(input)
terminated(
pair(
terminated(
take_while1(|c| c >= 0x21 && c <= 0x7E && c != 0x3A),
tuple((space0, tag(b":"), space0)),
),
unstructured,
), obs_crlf)(input)
}

View file

@ -8,9 +8,10 @@ use nom::{
use crate::text::whitespace::obs_crlf;
use crate::text::misc_token::{Unstructured, unstructured};
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::mechanism::{Mechanism, mechanism};
//use crate::mime::mime::MIME;
#[derive(Debug, PartialEq)]
pub enum Content<'a> {
@ -19,8 +20,11 @@ pub enum Content<'a> {
ID(MessageID<'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((
preceded(field_name(b"content-type"), map(naive_type, Content::Type)),
preceded(field_name(b"content-transfer-encoding"), map(mechanism, Content::TransferEncoding)),
@ -34,10 +38,12 @@ mod tests {
use super::*;
use crate::mime::r#type::*;
use crate::mime::charset::EmailCharset;
use crate::text::misc_token::MIMEWord;
use crate::text::quoted::QuotedString;
#[test]
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);
if let Content::Type(nt) = content {
@ -52,4 +58,41 @@ mod tests {
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
#[derive(Debug, PartialEq)]
pub struct NaiveType<'a> {
main: &'a [u8],
sub: &'a [u8],
params: Vec<Parameter<'a>>,
pub main: &'a [u8],
pub sub: &'a [u8],
pub params: Vec<Parameter<'a>>,
}
impl<'a> NaiveType<'a> {
pub fn to_type(&self) -> Type { self.into() }
@ -29,8 +29,8 @@ pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> {
#[derive(Debug, PartialEq)]
pub struct Parameter<'a> {
name: &'a [u8],
value: MIMEWord<'a>,
pub name: &'a [u8],
pub value: MIMEWord<'a>,
}
pub fn parameter(input: &[u8]) -> IResult<&[u8], Parameter> {
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::mime::{Version, version};
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};
#[derive(Debug, PartialEq)]
@ -87,15 +87,6 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
)), 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)]
mod tests {
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.";
assert_eq!(
map(header, |v| FieldList(v.known()).message())(fullmail),
map(header(field), |v| FieldList(v.known()).message())(fullmail),
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."[..],
Message {