mod_mimepart/
headers.rs

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