1use bounded_static::ToStatic;
2use nom::combinator::map;
3#[cfg(feature = "tracing")]
4use tracing::warn;
5
6#[cfg(feature = "arbitrary")]
7use crate::fuzz_eq::FuzzEq;
8use crate::header;
9use crate::imf::identification::{msg_id, MessageID};
10use crate::mime::mechanism::{mechanism, Mechanism};
11use crate::mime::r#type::{naive_type, AnyType, NaiveType};
12use crate::print::{Formatter, Print};
13use crate::text::misc_token::{unstructured, Unstructured};
14#[cfg(feature = "tracing-unsupported")]
15use crate::utils::bytes_to_trace_string;
16
17#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, ToStatic)]
18#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
19pub enum Entry {
20 Type,
21 TransferEncoding,
22 ID,
23 Description,
24}
25
26#[derive(Clone, Debug, PartialEq, ToStatic)]
27#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
28pub enum Field<'a> {
29 Type(AnyType<'a>),
30 TransferEncoding(Mechanism<'a>),
31 ID(MessageID<'a>),
32 Description(Unstructured<'a>),
33}
34
35impl<'a> Field<'a> {
36 pub fn raw_name(&self) -> header::FieldName<'static> {
37 match self {
38 Field::Type(_) => header::FieldName(b"Content-Type".into()),
39 Field::TransferEncoding(_) => header::FieldName(b"Content-Transfer-Encoding".into()),
40 Field::ID(_) => header::FieldName(b"Content-Id".into()),
41 Field::Description(_) => header::FieldName(b"Content-Description".into()),
42 }
43 }
44}
45impl<'a> Print for Field<'a> {
46 fn print(&self, fmt: &mut impl Formatter) {
47 match self {
48 Self::Type(nt) => header::print(fmt, b"Content-Type", nt),
49 Self::TransferEncoding(enc) => header::print(fmt, b"Content-Transfer-Encoding", enc),
50 Self::ID(id) => header::print(fmt, b"Content-Id", id),
51 Self::Description(desc) => {
52 header::print_unstructured(fmt, b"Content-Description", desc)
53 }
54 }
55 }
56}
57
58#[derive(Clone, Debug, PartialEq, ToStatic)]
59pub enum NaiveField<'a> {
60 Type(NaiveType<'a>),
61 TransferEncoding(Mechanism<'a>),
62 ID(MessageID<'a>),
63 Description(Unstructured<'a>),
64}
65
66#[derive(Clone, Copy, Debug)]
67pub enum InvalidField {
68 Name,
69 Body,
70}
71
72impl<'a> TryFrom<&header::FieldRaw<'a>> for NaiveField<'a> {
73 type Error = InvalidField;
74
75 #[cfg_attr(
76 feature = "tracing",
77 tracing::instrument(name = "mime::field::Field::try_from")
78 )]
79 fn try_from(f: &header::FieldRaw<'a>) -> Result<Self, Self::Error> {
80 let content = match f.name.bytes().to_ascii_lowercase().as_slice() {
81 b"content-type" => map(naive_type, NaiveField::Type)(f.body),
82 b"content-transfer-encoding" => map(mechanism, NaiveField::TransferEncoding)(f.body),
83 b"content-id" => map(msg_id, NaiveField::ID)(f.body),
84 b"content-description" => map(unstructured, NaiveField::Description)(f.body),
85 _ => return Err(InvalidField::Name),
86 };
87
88 match content {
89 Ok((b"", content)) => Ok(content),
90 Ok((_rest, _)) => {
91 #[cfg(feature = "tracing-unsupported")]
93 warn!(rest = %bytes_to_trace_string(_rest),
94 "leftover input after parsing");
95 Err(InvalidField::Body)
96 }
97 Err(_) => Err(InvalidField::Body),
98 }
99 }
100}
101
102pub fn is_mime_header(name: &header::FieldName) -> bool {
103 matches!(
104 name.bytes().to_ascii_lowercase().as_slice(),
105 b"content-type" | b"content-transfer-encoding" | b"content-id" | b"content-description"
106 )
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::header;
113 use crate::mime::r#type::*;
114 use crate::text::misc_token::MIMEWord;
115 use crate::text::quoted::QuotedString;
116 use crate::text::words::MIMEAtom;
117
118 #[test]
119 fn test_header() {
120 let fullmail: &[u8] = r#"Date: Sat, 8 Jul 2023 07:14:29 +0200
121From: Grrrnd Zero <grrrndzero@example.org>
122To: John Doe <jdoe@machine.example>
123Subject: Re: Saying Hello
124Message-ID: <NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>
125MIME-Version: 1.0
126Content-Type: multipart/alternative;
127 boundary="b1_e376dc71bafc953c0b0fdeb9983a9956"
128Content-Transfer-Encoding: 7bit
129
130This is a multipart message.
131
132"#
133 .as_bytes();
134
135 let (input, hdrs) = header::header_kv(fullmail);
136
137 assert_eq!(
138 (input, hdrs.iter().flat_map(NaiveField::try_from).collect()),
139 (
140 &b"This is a multipart message.\n\n"[..],
141 vec![
142 NaiveField::Type(NaiveType {
143 main: MIMEAtom(b"multipart"[..].into()),
144 sub: MIMEAtom(b"alternative"[..].into()),
145 params: vec![Parameter {
146 name: MIMEAtom(b"boundary"[..].into()),
147 value: MIMEWord::Quoted(QuotedString(vec![
148 "b1_e376dc71bafc953c0b0fdeb9983a9956"[..].into()
149 ])),
150 }]
151 }),
152 NaiveField::TransferEncoding(Mechanism::_7Bit),
153 ],
154 ),
155 );
156 }
157}