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#[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}