mod_mimepart/
headers.rs

1use crate::PartRef;
2use bstr::BString;
3use config::{SerdeWrappedValue, any_err};
4use mailparsing::{
5    AddressList, BStringUtf8, Header, HeaderMap, MailParsingError, Mailbox, MailboxList, MessageID,
6    MimeParameters, ParsedHeader,
7};
8use mlua::{
9    IntoLua, Lua, MetaMethod, MultiValue, UserData, UserDataFields, UserDataMethods, Value,
10};
11use serde::{Deserialize, Serialize};
12use serde_with::serde_as;
13use std::collections::BTreeMap;
14
15#[derive(Clone)]
16pub struct HeaderMapRef(PartRef);
17
18impl HeaderMapRef {
19    pub fn new(part: PartRef) -> Self {
20        Self(part)
21    }
22}
23
24struct HeaderIterInner {
25    part: PartRef,
26    idx: usize,
27    name: Option<mlua::String>,
28}
29
30impl UserData for HeaderMapRef {
31    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
32        methods.add_meta_method(MetaMethod::ToString, move |lua, this, ()| {
33            let part = this.0.resolve().map_err(any_err)?;
34            let headers = part.headers();
35            let mut out = vec![];
36            for hdr in headers.iter() {
37                hdr.write_header(&mut out).map_err(any_err)?;
38            }
39            lua.create_string(&out)
40        });
41
42        methods.add_method(
43            "append",
44            |_lua, this, (name, value): (String, mlua::String)| {
45                this.0
46                    .mutate(|part| {
47                        part.headers_mut()
48                            .append_header(&name, BString::new(value.as_bytes().to_vec()));
49                        Ok(())
50                    })
51                    .map_err(any_err)
52            },
53        );
54
55        methods.add_method(
56            "prepend",
57            |_lua, this, (name, value): (String, mlua::String)| {
58                this.0
59                    .mutate(|part| {
60                        part.headers_mut()
61                            .prepend(&name, BString::new(value.as_bytes().to_vec()));
62                        Ok(())
63                    })
64                    .map_err(any_err)
65            },
66        );
67
68        methods.add_method("remove_all_named", |_lua, this, name: String| {
69            this.0
70                .mutate(|part| {
71                    part.headers_mut().remove_all_named(&name);
72                    Ok(())
73                })
74                .map_err(any_err)
75        });
76
77        methods.add_method("get_first_named", |_lua, this, name: String| {
78            let part = this.0.resolve().map_err(any_err)?;
79            Ok(part
80                .headers()
81                .get_first(&name)
82                .map(|hdr| HeaderWrap(hdr.to_owned())))
83        });
84
85        methods.add_method("iter", |lua, this, name: Option<mlua::String>| {
86            let mut iter = HeaderIterInner {
87                part: this.0.clone(),
88                idx: 0,
89                name,
90            };
91
92            let iter_func =
93                lua.create_function_mut(move |lua: &Lua, (_state, _idx): (Value, Value)| {
94                    let part = iter.part.resolve().map_err(any_err)?;
95                    let headers = part.headers();
96                    let mut result = vec![];
97
98                    while let Some(hdr) = headers.get(iter.idx) {
99                        iter.idx += 1;
100
101                        let matched = iter
102                            .name
103                            .as_ref()
104                            .map(|name| {
105                                hdr.get_name()
106                                    .eq_ignore_ascii_case(name.as_bytes().as_ref())
107                            })
108                            .unwrap_or(true);
109
110                        if matched {
111                            result.push(HeaderWrap(hdr.to_owned()).into_lua(lua)?);
112                            break;
113                        }
114                    }
115
116                    Ok(MultiValue::from_vec(result))
117                })?;
118
119            Ok(iter_func)
120        });
121
122        fn getter<T, GET>(
123            get: GET,
124        ) -> impl Fn(&Lua, &HeaderMapRef, ()) -> mlua::Result<Option<SerdeWrappedValue<T>>>
125        where
126            T: serde::de::DeserializeOwned,
127            GET: Fn(&HeaderMap) -> Result<Option<T>, MailParsingError>,
128        {
129            move |_lua, this, ()| {
130                let part = this.0.resolve().map_err(any_err)?;
131                let value = (get)(part.headers())
132                    .map_err(any_err)?
133                    .map(SerdeWrappedValue);
134                Ok(value)
135            }
136        }
137
138        fn setter_unstructured<APPLY>(
139            apply: APPLY,
140        ) -> impl Fn(&Lua, &HeaderMapRef, mlua::String) -> mlua::Result<()>
141        where
142            APPLY: Fn(&mut HeaderMap, &[u8]) -> Result<(), MailParsingError>,
143        {
144            move |_lua, this, value: mlua::String| {
145                this.0
146                    .mutate(|part| Ok(apply(part.headers_mut(), &value.as_bytes())?))
147                    .map_err(any_err)
148            }
149        }
150
151        fn getter_unstructured<GET>(
152            get: GET,
153        ) -> impl Fn(&Lua, &HeaderMapRef, ()) -> mlua::Result<Option<BString>>
154        where
155            GET: Fn(&HeaderMap) -> Result<Option<BString>, MailParsingError>,
156        {
157            move |_lua, this, ()| {
158                let part = this.0.resolve().map_err(any_err)?;
159                let value = (get)(part.headers()).map_err(any_err)?;
160                Ok(value)
161            }
162        }
163
164        /// Helper to ensure that we picked a reasonably typed accessor
165        fn assert_type<T>(
166            value: Result<Option<T>, MailParsingError>,
167        ) -> Result<Option<T>, MailParsingError> {
168            value
169        }
170
171        macro_rules! accessor {
172            ($get_name:literal, $getter:ident, $set_name:literal, $setter:ident, $ty:path) => {
173                methods.add_method(
174                    $get_name,
175                    getter(|headers| assert_type::<$ty>(headers.$getter())),
176                );
177                methods.add_method($set_name, |lua, this, value: mlua::Value| match value {
178                    Value::String(s) => {
179                        let s = s.to_str()?;
180                        this.0
181                            .mutate(|part| {
182                                part.headers_mut().$setter(&*s)?;
183                                Ok(())
184                            })
185                            .map_err(any_err)
186                    }
187                    value => {
188                        let typed = config::from_lua_value::<$ty>(lua, value)?;
189                        this.0
190                            .mutate(|part| {
191                                part.headers_mut().$setter(typed)?;
192                                Ok(())
193                            })
194                            .map_err(any_err)
195                    }
196                });
197            };
198
199            ($get_name:literal, $getter:ident, $set_name:literal, $setter:ident, $ty:path, $via_ty:path) => {
200                methods.add_method(
201                    $get_name,
202                    getter(|headers| headers.$getter().map(|opt| opt.map(<$ty>::from))),
203                );
204                methods.add_method($set_name, |lua, this, value: mlua::Value| match value {
205                    Value::String(s) => {
206                        let s = s.to_str()?;
207                        this.0
208                            .mutate(|part| {
209                                part.headers_mut().$setter(&*s)?;
210                                Ok(())
211                            })
212                            .map_err(any_err)
213                    }
214                    value => {
215                        let typed: $via_ty = config::from_lua_value::<$ty>(lua, value)?.into();
216                        this.0
217                            .mutate(|part| {
218                                part.headers_mut().$setter(typed)?;
219                                Ok(())
220                            })
221                            .map_err(any_err)
222                    }
223                });
224            };
225        }
226
227        macro_rules! unstructured {
228            ($get_name:literal, $getter:ident, $set_name:literal, $setter:ident) => {
229                methods.add_method($get_name, getter_unstructured(|headers| headers.$getter()));
230                methods.add_method(
231                    $set_name,
232                    setter_unstructured(|headers, value| headers.$setter(value)),
233                );
234            };
235        }
236
237        accessor!("from", from, "set_from", set_from, MailboxList);
238        accessor!(
239            "resent_from",
240            resent_from,
241            "set_resent_from",
242            set_from,
243            MailboxList
244        );
245        accessor!("to", to, "set_to", set_to, AddressList);
246        accessor!(
247            "reply_to",
248            reply_to,
249            "set_reply_to",
250            set_reply_to,
251            AddressList
252        );
253        accessor!("cc", cc, "set_cc", set_cc, AddressList);
254        accessor!("bcc", bcc, "set_bcc", set_bcc, AddressList);
255        accessor!(
256            "resent_to",
257            resent_to,
258            "set_resent_to",
259            set_resent_to,
260            AddressList
261        );
262        accessor!(
263            "resent_cc",
264            resent_cc,
265            "set_resent_cc",
266            set_resent_cc,
267            AddressList
268        );
269        accessor!(
270            "resent_bcc",
271            resent_bcc,
272            "set_resent_bcc",
273            set_resent_bcc,
274            AddressList
275        );
276
277        // accessor!("date", date, "set_date", set_date, DateTime<FixedOffset>); FIXME: establish lua time type
278
279        accessor!("sender", sender, "set_sender", set_sender, Mailbox);
280        accessor!(
281            "resent_sender",
282            resent_sender,
283            "set_resent_sender",
284            set_resent_sender,
285            Mailbox
286        );
287
288        accessor!(
289            "message_id",
290            message_id,
291            "set_message_id",
292            set_message_id,
293            MessageID
294        );
295        accessor!(
296            "content_id",
297            content_id,
298            "set_content_id",
299            set_content_id,
300            MessageID
301        );
302        accessor!(
303            "references",
304            references,
305            "set_references",
306            set_references,
307            Vec<MessageID>
308        );
309
310        unstructured!("subject", subject, "set_subject", set_subject);
311        unstructured!("comments", comments, "set_comments", set_comments);
312        unstructured!(
313            "mime_version",
314            mime_version,
315            "set_mime_version",
316            set_mime_version
317        );
318
319        accessor!(
320            "content_transfer_encoding",
321            content_transfer_encoding,
322            "set_content_transfer_encoding",
323            set_content_transfer_encoding,
324            MimeParams,
325            MimeParameters
326        );
327        accessor!(
328            "content_disposition",
329            content_disposition,
330            "set_content_disposition",
331            set_content_disposition,
332            MimeParams,
333            MimeParameters
334        );
335        accessor!(
336            "content_type",
337            content_type,
338            "set_content_type",
339            set_content_type,
340            MimeParams,
341            MimeParameters
342        );
343    }
344}
345
346/// A fully-decoded representation of the underlying MimeParameters value,
347/// to make it more convenient to inspect from lua
348#[serde_as]
349#[derive(Clone, Serialize, Deserialize)]
350#[serde(deny_unknown_fields)]
351pub struct MimeParams {
352    #[serde_as(as = "BStringUtf8")]
353    pub value: BString,
354    #[serde_as(as = "BTreeMap<BStringUtf8, BStringUtf8>")]
355    pub parameters: BTreeMap<BString, BString>,
356}
357
358impl From<MimeParameters> for MimeParams {
359    fn from(params: MimeParameters) -> MimeParams {
360        let map = params.parameter_map();
361
362        MimeParams {
363            value: params.value,
364            parameters: map,
365        }
366    }
367}
368
369impl From<MimeParams> for MimeParameters {
370    fn from(params: MimeParams) -> MimeParameters {
371        let mut result = MimeParameters::new(&params.value);
372        for (name, value) in params.parameters {
373            result.set(&name, &value);
374        }
375        result
376    }
377}
378
379#[derive(Clone)]
380pub struct HeaderWrap(Header<'static>);
381
382/// Parse `header` according to the grammar implied by its name (see
383/// `mailparsing::ParsedHeader`) and convert the result to a Lua value.
384fn header_value_to_lua(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
385    // For backwards compatibility, Date is returned as its raw string rather
386    // than a parsed timestamp: `value` yields what should be an RFC 2822
387    // compatible string, whereas parsing would produce an RFC 3339 string or a
388    // kumo.time Time object. We return the value as-is, without parsing, which
389    // means that a non-conforming Date is not detected here.
390    if header.get_name().eq_ignore_ascii_case(b"Date") {
391        return SerdeWrappedValue(header.as_unstructured().map_err(any_err)?).to_lua_value(lua);
392    }
393
394    match header.structured().map_err(any_err)? {
395        ParsedHeader::MailboxList(v) => SerdeWrappedValue(v).to_lua_value(lua),
396        ParsedHeader::Mailbox(v) => SerdeWrappedValue(v).to_lua_value(lua),
397        ParsedHeader::AddressList(v) => SerdeWrappedValue(v).to_lua_value(lua),
398        ParsedHeader::MessageId(v) => SerdeWrappedValue(v).to_lua_value(lua),
399        ParsedHeader::MessageIdList(v) => SerdeWrappedValue(v).to_lua_value(lua),
400        ParsedHeader::MimeParameters(v) => SerdeWrappedValue(MimeParams::from(v)).to_lua_value(lua),
401        ParsedHeader::AuthenticationResults(v) => SerdeWrappedValue(v).to_lua_value(lua),
402        // The early return above intercepts every Date header before
403        // structured() runs, so this arm is not reached in practice; it keeps
404        // the match exhaustive and returns the same raw string rather than
405        // introduce a panic path.
406        ParsedHeader::Date(_) => {
407            SerdeWrappedValue(header.as_unstructured().map_err(any_err)?).to_lua_value(lua)
408        }
409        ParsedHeader::Unstructured(v) => SerdeWrappedValue(v).to_lua_value(lua),
410    }
411}
412
413impl UserData for HeaderWrap {
414    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
415        methods.add_meta_method(MetaMethod::ToString, |_lua, this, ()| {
416            Ok(this.0.to_header_string())
417        });
418    }
419
420    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
421        fields.add_field_method_get("name", |lua, this| lua.create_string(this.0.get_name()));
422
423        fields.add_field_method_get("value", |lua, this| header_value_to_lua(lua, &this.0));
424
425        fields.add_field_method_get("raw_value", |lua, this| {
426            lua.create_string(this.0.get_raw_value())
427        });
428        fields.add_field_method_get("unstructured", |_lua, this| {
429            this.0.as_unstructured().map_err(any_err)
430        });
431        fields.add_field_method_get("mailbox_list", |_lua, this| {
432            Ok(SerdeWrappedValue(
433                this.0.as_mailbox_list().map_err(any_err)?,
434            ))
435        });
436        fields.add_field_method_get("address_list", |_lua, this| {
437            Ok(SerdeWrappedValue(
438                this.0.as_address_list().map_err(any_err)?,
439            ))
440        });
441        fields.add_field_method_get("message_id", |_lua, this| {
442            Ok(SerdeWrappedValue(this.0.as_message_id().map_err(any_err)?))
443        });
444        fields.add_field_method_get("message_id_list", |_lua, this| {
445            Ok(SerdeWrappedValue(
446                this.0.as_message_id_list().map_err(any_err)?,
447            ))
448        });
449        fields.add_field_method_get("mime_params", |_lua, this| {
450            Ok(SerdeWrappedValue(MimeParams::from(
451                this.0.as_content_transfer_encoding().map_err(any_err)?,
452            )))
453        });
454        fields.add_field_method_get("authentication_results", |_lua, this| {
455            Ok(SerdeWrappedValue(
456                this.0.as_authentication_results().map_err(any_err)?,
457            ))
458        });
459    }
460}