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