eml_codec/mime/
field.rs

1use nom::combinator::map;
2
3use crate::header;
4use crate::imf::identification::{msg_id, MessageID};
5use crate::mime::mechanism::{mechanism, Mechanism};
6use crate::mime::r#type::{naive_type, NaiveType};
7use crate::text::misc_token::{unstructured, Unstructured};
8
9#[derive(Debug, PartialEq)]
10pub enum Content<'a> {
11    Type(NaiveType<'a>),
12    TransferEncoding(Mechanism<'a>),
13    ID(MessageID<'a>),
14    Description(Unstructured<'a>),
15}
16#[allow(dead_code)]
17impl<'a> Content<'a> {
18    pub fn ctype(&'a self) -> Option<&'a NaiveType<'a>> {
19        match self {
20            Content::Type(v) => Some(v),
21            _ => None,
22        }
23    }
24    pub fn transfer_encoding(&'a self) -> Option<&'a Mechanism<'a>> {
25        match self {
26            Content::TransferEncoding(v) => Some(v),
27            _ => None,
28        }
29    }
30    pub fn id(&'a self) -> Option<&'a MessageID<'a>> {
31        match self {
32            Content::ID(v) => Some(v),
33            _ => None,
34        }
35    }
36    pub fn description(&'a self) -> Option<&'a Unstructured<'a>> {
37        match self {
38            Content::Description(v) => Some(v),
39            _ => None,
40        }
41    }
42}
43
44impl<'a> TryFrom<&header::Field<'a>> for Content<'a> {
45    type Error = ();
46    fn try_from(f: &header::Field<'a>) -> Result<Self, Self::Error> {
47        let content = match f {
48            header::Field::Good(header::Kv2(key, value)) => match key
49                .to_ascii_lowercase()
50                .as_slice()
51            {
52                b"content-type" => map(naive_type, Content::Type)(value),
53                b"content-transfer-encoding" => map(mechanism, Content::TransferEncoding)(value),
54                b"content-id" => map(msg_id, Content::ID)(value),
55                b"content-description" => map(unstructured, Content::Description)(value),
56                _ => return Err(()),
57            },
58            _ => return Err(()),
59        };
60
61        //@TODO check that the full value is parsed, otherwise maybe log an error ?!
62        content.map(|(_, content)| content).or(Err(()))
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::header;
70    //use crate::mime::charset::EmailCharset;
71    use crate::mime::r#type::*;
72    use crate::text::misc_token::MIMEWord;
73    use crate::text::quoted::QuotedString;
74
75    /*
76    #[test]
77    fn test_content_type() {
78        let (rest, content) =
79            content(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap();
80        assert_eq!(&b""[..], rest);
81
82        if let Content::Type(nt) = content {
83            assert_eq!(
84                nt.to_type(),
85                AnyType::Text(Deductible::Explicit(Text {
86                    charset: Deductible::Explicit(EmailCharset::UTF_8),
87                    subtype: TextSubtype::Plain,
88                })),
89            );
90        } else {
91            panic!("Expected Content::Type, got {:?}", content);
92        }
93    }*/
94
95    #[test]
96    fn test_header() {
97        let fullmail: &[u8] = r#"Date: Sat, 8 Jul 2023 07:14:29 +0200
98From: Grrrnd Zero <grrrndzero@example.org>
99To: John Doe <jdoe@machine.example>
100Subject: Re: Saying Hello
101Message-ID: <NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>
102MIME-Version: 1.0
103Content-Type: multipart/alternative;
104 boundary="b1_e376dc71bafc953c0b0fdeb9983a9956"
105Content-Transfer-Encoding: 7bit
106
107This is a multipart message.
108
109"#
110        .as_bytes();
111
112        assert_eq!(
113            map(header::header_kv, |k| k
114                .iter()
115                .flat_map(Content::try_from)
116                .collect())(fullmail),
117            Ok((
118                &b"This is a multipart message.\n\n"[..],
119                vec![
120                    Content::Type(NaiveType {
121                        main: &b"multipart"[..],
122                        sub: &b"alternative"[..],
123                        params: vec![Parameter {
124                            name: &b"boundary"[..],
125                            value: MIMEWord::Quoted(QuotedString(vec![
126                                &b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]
127                            ])),
128                        }]
129                    }),
130                    Content::TransferEncoding(Mechanism::_7Bit),
131                ],
132            )),
133        );
134    }
135}