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,
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
382fn get_mailbox_list(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
383    SerdeWrappedValue(header.as_mailbox_list().map_err(any_err)?).to_lua_value(lua)
384}
385fn get_address_list(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
386    SerdeWrappedValue(header.as_address_list().map_err(any_err)?).to_lua_value(lua)
387}
388fn get_mailbox(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
389    SerdeWrappedValue(header.as_mailbox().map_err(any_err)?).to_lua_value(lua)
390}
391fn get_message_id(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
392    SerdeWrappedValue(header.as_message_id().map_err(any_err)?).to_lua_value(lua)
393}
394fn get_content_id(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
395    SerdeWrappedValue(header.as_content_id().map_err(any_err)?).to_lua_value(lua)
396}
397fn get_message_id_list(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
398    SerdeWrappedValue(header.as_message_id_list().map_err(any_err)?).to_lua_value(lua)
399}
400fn get_unstructured(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
401    SerdeWrappedValue(header.as_unstructured().map_err(any_err)?).to_lua_value(lua)
402}
403fn get_content_transfer_encoding(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
404    let params: MimeParams = header
405        .as_content_transfer_encoding()
406        .map_err(any_err)?
407        .into();
408    SerdeWrappedValue(params).to_lua_value(lua)
409}
410fn get_content_disposition(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
411    let params: MimeParams = header.as_content_disposition().map_err(any_err)?.into();
412    SerdeWrappedValue(params).to_lua_value(lua)
413}
414fn get_content_type(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
415    let params: MimeParams = header.as_content_type().map_err(any_err)?.into();
416    SerdeWrappedValue(params).to_lua_value(lua)
417}
418fn get_authentication_results(lua: &Lua, header: &Header) -> mlua::Result<mlua::Value> {
419    SerdeWrappedValue(header.as_authentication_results().map_err(any_err)?).to_lua_value(lua)
420}
421
422const NAME_GETTER: &[(&str, fn(&Lua, &Header) -> mlua::Result<mlua::Value>)] = &[
423    ("From", get_mailbox_list),
424    ("Reply-To", get_address_list),
425    ("To", get_address_list),
426    ("Cc", get_address_list),
427    ("Bcc", get_address_list),
428    ("Message-ID", get_message_id),
429    ("Subject", get_unstructured),
430    ("Mime-Version", get_unstructured),
431    ("Content-Transfer-Encoding", get_content_transfer_encoding),
432    ("Content-Type", get_content_type),
433    ("Content-Disposition", get_content_disposition),
434    ("Authentication-Results", get_authentication_results),
435    ("Resent-From", get_mailbox_list),
436    ("Resent-To", get_address_list),
437    ("Resent-Cc", get_address_list),
438    ("Resent-Bcc", get_address_list),
439    ("Resent-Sender", get_mailbox),
440    ("Sender", get_mailbox),
441    ("Content-ID", get_content_id),
442    ("References", get_message_id_list),
443    ("Comments", get_unstructured),
444];
445
446impl UserData for HeaderWrap {
447    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
448        methods.add_meta_method(MetaMethod::ToString, |_lua, this, ()| {
449            Ok(this.0.to_header_string())
450        });
451    }
452
453    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
454        fields.add_field_method_get("name", |lua, this| lua.create_string(this.0.get_name()));
455
456        fields.add_field_method_get("value", |lua, this| {
457            let name = this.0.get_name();
458            for (candidate, getter) in NAME_GETTER {
459                if candidate.as_bytes().eq_ignore_ascii_case(name) {
460                    return (getter)(lua, &this.0);
461                }
462            }
463            get_unstructured(lua, &this.0)
464        });
465
466        fields.add_field_method_get("raw_value", |lua, this| {
467            lua.create_string(this.0.get_raw_value())
468        });
469        fields.add_field_method_get("unstructured", |_lua, this| {
470            this.0.as_unstructured().map_err(any_err)
471        });
472        fields.add_field_method_get("mailbox_list", |_lua, this| {
473            Ok(SerdeWrappedValue(
474                this.0.as_mailbox_list().map_err(any_err)?,
475            ))
476        });
477        fields.add_field_method_get("address_list", |_lua, this| {
478            Ok(SerdeWrappedValue(
479                this.0.as_address_list().map_err(any_err)?,
480            ))
481        });
482        fields.add_field_method_get("message_id", |_lua, this| {
483            Ok(SerdeWrappedValue(this.0.as_message_id().map_err(any_err)?))
484        });
485        fields.add_field_method_get("message_id_list", |_lua, this| {
486            Ok(SerdeWrappedValue(
487                this.0.as_message_id_list().map_err(any_err)?,
488            ))
489        });
490        fields.add_field_method_get("mime_params", |_lua, this| {
491            Ok(SerdeWrappedValue(MimeParams::from(
492                this.0.as_content_transfer_encoding().map_err(any_err)?,
493            )))
494        });
495        fields.add_field_method_get("authentication_results", |_lua, this| {
496            Ok(SerdeWrappedValue(
497                this.0.as_authentication_results().map_err(any_err)?,
498            ))
499        });
500    }
501}