generic header support
This commit is contained in:
parent
4baa976283
commit
59f550b439
4 changed files with 75 additions and 26 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue