mailparsing/
headermap.rs

1use crate::{
2    AddressList, AuthenticationResults, Header, MailParsingError, Mailbox, MailboxList, MessageID,
3    MimeParameters, Result, SharedString,
4};
5use bstr::BString;
6use chrono::{DateTime, FixedOffset, TimeZone};
7use pastey::paste;
8
9/// Represents an ordered list of headers.
10/// Note that there may be multiple headers with the same name.
11/// Derefs to the underlying `Vec<Header>` for mutation,
12/// but provides some accessors for retrieving headers by name.
13#[derive(Debug, Clone, Default, PartialEq)]
14pub struct HeaderMap<'a> {
15    pub(crate) headers: Vec<Header<'a>>,
16}
17
18impl<'a> std::ops::Deref for HeaderMap<'a> {
19    type Target = Vec<Header<'a>>;
20    fn deref(&self) -> &Vec<Header<'a>> {
21        &self.headers
22    }
23}
24
25impl<'a> std::ops::DerefMut for HeaderMap<'a> {
26    fn deref_mut(&mut self) -> &mut Vec<Header<'a>> {
27        &mut self.headers
28    }
29}
30
31pub trait EncodeHeaderValue {
32    fn encode_value(&self) -> SharedString<'static>;
33    fn as_header(&self, _name: &[u8]) -> Option<Header<'static>> {
34        None
35    }
36}
37
38impl EncodeHeaderValue for &[u8] {
39    fn encode_value(&self) -> SharedString<'static> {
40        unimplemented!();
41    }
42
43    fn as_header(&self, name: &[u8]) -> Option<Header<'static>> {
44        Some(Header::new_unstructured(name.to_vec(), self.to_vec()))
45    }
46}
47
48impl EncodeHeaderValue for &str {
49    fn encode_value(&self) -> SharedString<'static> {
50        unimplemented!();
51    }
52
53    fn as_header(&self, name: &[u8]) -> Option<Header<'static>> {
54        Some(Header::new_unstructured(name.to_vec(), self.to_string()))
55    }
56}
57
58impl<T: TimeZone> EncodeHeaderValue for DateTime<T>
59where
60    <T as TimeZone>::Offset: std::fmt::Display,
61{
62    fn encode_value(&self) -> SharedString<'static> {
63        (*self).to_rfc2822().into()
64    }
65}
66
67macro_rules! accessor {
68    ($func_name:ident, $header_name:literal, $ty:path, $parser:ident) => {
69        pub fn $func_name(&self) -> Result<Option<$ty>> {
70            match self.get_first($header_name) {
71                None => Ok(None),
72                Some(header) => Ok(Some(header.$parser().map_err(|error| {
73                    MailParsingError::InvalidHeaderValueDuringGet {
74                        header_name: $header_name.to_string(),
75                        error: error.into(),
76                    }
77                })?)),
78            }
79        }
80
81        paste! {
82            pub fn [<set_ $func_name>](&mut self, v: impl EncodeHeaderValue) -> Result<()> {
83                if let Some(idx) = self
84                    .headers
85                    .iter()
86                    .position(|header| header.get_name().eq_ignore_ascii_case($header_name.as_bytes()))
87                {
88                    if let Some(hdr) = v.as_header(self.headers[idx].get_name()) {
89                        hdr.$parser().map_err(|error| {
90                            MailParsingError::InvalidHeaderValueDuringAssignment {
91                                header_name: $header_name.to_string(),
92                                error: error.into(),
93                            }
94                        })?;
95                        self.headers[idx] = hdr;
96                    } else {
97                        self.headers[idx].assign(v);
98                    }
99                } else {
100                    if let Some(hdr) = v.as_header($header_name.as_bytes()) {
101                        hdr.$parser().map_err(|error| {
102                            MailParsingError::InvalidHeaderValueDuringAssignment {
103                                header_name: $header_name.to_string(),
104                                error: error.into(),
105                            }
106                        })?;
107                        self.headers.push(hdr);
108                    } else {
109                        self.headers
110                            .push(Header::with_name_value($header_name, v.encode_value()));
111                    }
112                }
113                Ok(())
114            }
115        }
116    };
117}
118
119impl<'a> HeaderMap<'a> {
120    pub fn new(headers: Vec<Header<'a>>) -> Self {
121        Self { headers }
122    }
123
124    pub fn to_owned(&'a self) -> HeaderMap<'static> {
125        HeaderMap {
126            headers: self.headers.iter().map(|h: &Header| h.to_owned()).collect(),
127        }
128    }
129
130    pub fn remove_all_named(&mut self, name: &str) {
131        self.headers
132            .retain(|hdr| !hdr.get_name().eq_ignore_ascii_case(name.as_bytes()));
133    }
134
135    pub fn prepend<V: Into<SharedString<'a>>>(&mut self, name: &str, v: V) {
136        self.headers
137            .insert(0, Header::new_unstructured(name.to_string(), v));
138    }
139
140    pub fn append_header<V: Into<SharedString<'a>>>(&mut self, name: &str, v: V) {
141        self.headers
142            .push(Header::new_unstructured(name.to_string(), v));
143    }
144
145    pub fn get_first(&'a self, name: &str) -> Option<&'a Header<'a>> {
146        self.iter_named(name).next()
147    }
148
149    pub fn get_first_mut(&'a mut self, name: &str) -> Option<&'a mut Header<'a>> {
150        self.iter_named_mut(name).next()
151    }
152
153    pub fn get_last(&'a self, name: &str) -> Option<&'a Header<'a>> {
154        self.iter_named(name).next_back()
155    }
156
157    pub fn get_last_mut(&'a mut self, name: &str) -> Option<&'a mut Header<'a>> {
158        self.iter_named_mut(name).next_back()
159    }
160
161    pub fn iter_named<'name>(
162        &'a self,
163        name: &'name str,
164    ) -> impl DoubleEndedIterator<Item = &'a Header<'a>> + 'name
165    where
166        'a: 'name,
167    {
168        self.headers
169            .iter()
170            .filter(|header| header.get_name().eq_ignore_ascii_case(name.as_bytes()))
171    }
172
173    pub fn iter_named_mut<'name>(
174        &'a mut self,
175        name: &'name str,
176    ) -> impl DoubleEndedIterator<Item = &'a mut Header<'a>> + 'name
177    where
178        'a: 'name,
179    {
180        self.headers
181            .iter_mut()
182            .filter(|header| header.get_name().eq_ignore_ascii_case(name.as_bytes()))
183    }
184
185    accessor!(from, "From", MailboxList, as_mailbox_list);
186    accessor!(resent_from, "Resent-From", MailboxList, as_mailbox_list);
187
188    accessor!(to, "To", AddressList, as_address_list);
189    accessor!(reply_to, "Reply-To", AddressList, as_address_list);
190    accessor!(cc, "Cc", AddressList, as_address_list);
191    accessor!(bcc, "Bcc", AddressList, as_address_list);
192    accessor!(resent_to, "Resent-To", AddressList, as_address_list);
193    accessor!(resent_cc, "Resent-Cc", AddressList, as_address_list);
194    accessor!(resent_bcc, "Resent-Bcc", AddressList, as_address_list);
195
196    accessor!(date, "Date", DateTime<FixedOffset>, as_date);
197
198    accessor!(sender, "Sender", Mailbox, as_mailbox);
199    accessor!(resent_sender, "Resent-Sender", Mailbox, as_mailbox);
200
201    accessor!(message_id, "Message-ID", MessageID, as_message_id);
202    accessor!(content_id, "Content-ID", MessageID, as_content_id);
203    accessor!(references, "References", Vec<MessageID>, as_message_id_list);
204
205    accessor!(subject, "Subject", BString, as_unstructured);
206    accessor!(comments, "Comments", BString, as_unstructured);
207    accessor!(
208        content_transfer_encoding,
209        "Content-Transfer-Encoding",
210        MimeParameters,
211        as_content_transfer_encoding
212    );
213    accessor!(mime_version, "Mime-Version", BString, as_unstructured);
214    accessor!(
215        content_disposition,
216        "Content-Disposition",
217        MimeParameters,
218        as_content_disposition
219    );
220
221    accessor!(
222        content_type,
223        "Content-Type",
224        MimeParameters,
225        as_content_type
226    );
227
228    accessor!(
229        authentication_results,
230        "Authentication-Results",
231        AuthenticationResults,
232        as_authentication_results
233    );
234}