mod_mimepart/
mimepart.rs

1use config::any_err;
2use mailparsing::{DecodedBody, MimePart, PartPointer};
3use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef};
4use parking_lot::Mutex;
5use std::sync::Arc;
6
7#[derive(Clone)]
8pub struct PartRef {
9    root_part: Arc<Mutex<MimePart<'static>>>,
10    ptr: PartPointer,
11}
12
13impl PartRef {
14    pub fn new(part: MimePart<'static>) -> Self {
15        Self {
16            root_part: Arc::new(Mutex::new(part)),
17            ptr: PartPointer::root(),
18        }
19    }
20
21    pub fn resolve(&self) -> anyhow::Result<MimePart<'_>> {
22        let root = self.root_part.lock();
23        root.resolve_ptr(self.ptr.clone())
24            .ok_or_else(|| anyhow::anyhow!("failed to resolve PartRef to MimePart"))
25            .cloned()
26    }
27
28    pub fn mutate<F: FnOnce(&mut MimePart) -> anyhow::Result<R>, R>(
29        &self,
30        f: F,
31    ) -> anyhow::Result<R> {
32        let mut root = self.root_part.lock();
33        let part = root.resolve_ptr_mut(self.ptr.clone());
34        match part {
35            Some(p) => (f)(p),
36            None => anyhow::bail!("failed to resolve PartRef to MimePart"),
37        }
38    }
39
40    pub fn make_ref(&self, ptr: PartPointer) -> Self {
41        Self {
42            root_part: self.root_part.clone(),
43            ptr,
44        }
45    }
46
47    pub fn get_simple_structure(&self) -> anyhow::Result<SimpleStructure> {
48        let part = self.resolve()?;
49
50        let parts = part.simplified_structure_pointers().map_err(any_err)?;
51
52        let mut attachments = vec![];
53        for ptr in parts.attachments {
54            attachments.push(self.make_ref(ptr));
55        }
56
57        Ok(SimpleStructure {
58            text_part: parts.text_part.map(|ptr| self.make_ref(ptr)),
59            html_part: parts.html_part.map(|ptr| self.make_ref(ptr)),
60            header_part: self.make_ref(parts.header_part),
61            attachments,
62        })
63    }
64
65    pub fn replace_body(
66        &self,
67        body: mlua::String,
68        mut content_type: Option<String>,
69    ) -> anyhow::Result<()> {
70        self.mutate(|part| {
71            if content_type.is_none() {
72                if let Ok(Some(params)) = part.headers().content_type() {
73                    content_type.replace(params.value);
74                }
75            }
76
77            match body.to_str() {
78                Ok(s) => {
79                    part.replace_text_body(content_type.as_deref().unwrap_or("text/plain"), &s)?;
80                }
81                _ => {
82                    part.replace_binary_body(
83                        content_type
84                            .as_deref()
85                            .unwrap_or("application/octet-stream"),
86                        &body.as_bytes(),
87                    )?;
88                }
89            }
90            Ok(())
91        })
92    }
93}
94
95impl UserData for PartRef {
96    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
97        methods.add_method(
98            "append_part",
99            move |_lua, this, part: UserDataRef<PartRef>| {
100                let part_to_append = part.resolve().map_err(any_err)?.to_owned();
101
102                this.mutate(|this_part| {
103                    this_part.child_parts_mut().push(part_to_append);
104                    Ok(())
105                })
106                .map_err(any_err)?;
107
108                Ok(())
109            },
110        );
111
112        methods.add_method("get_simple_structure", move |lua, this, ()| {
113            let s = this.get_simple_structure().map_err(any_err)?;
114
115            let result = lua.create_table()?;
116            result.set("text_part", s.text_part)?;
117            result.set("html_part", s.html_part)?;
118            result.set("header_part", s.header_part)?;
119
120            let attachments = lua.create_table()?;
121            for a in s.attachments {
122                let a_part = a.resolve().map_err(any_err)?;
123                let attach = lua.create_table()?;
124
125                let mut file_name = None;
126                let mut inline = true;
127                let mut content_id = None;
128                let mut content_type = None;
129
130                let info = a_part.rfc2045_info();
131                if let Some(mut opts) = info.attachment_options {
132                    if let Some(name) = opts.file_name.take() {
133                        file_name.replace(name);
134                    }
135                    inline = opts.inline;
136                    content_id = opts.content_id;
137                }
138
139                if let Some(ct) = info.content_type {
140                    content_type.replace(ct.value);
141                }
142
143                attach.set("file_name", file_name)?;
144                attach.set("inline", inline)?;
145                attach.set("content_id", content_id)?;
146                match content_type {
147                    Some(ct) => {
148                        attach.set("content_type", ct)?;
149                    }
150                    None => {
151                        attach.set("content_type", "application/octet-stream")?;
152                    }
153                }
154
155                attach.set("part", a)?;
156                attachments.push(attach)?;
157            }
158
159            result.set("attachments", attachments)?;
160
161            Ok(result)
162        });
163
164        methods.add_meta_method(MetaMethod::ToString, move |_lua, this, ()| {
165            let root = this.root_part.lock();
166            Ok(root.to_message_string())
167        });
168
169        methods.add_method("rebuild", move |_lua, this, ()| {
170            let root = this.root_part.lock();
171            let part = root.rebuild().map_err(any_err)?;
172            Ok(PartRef::new(part))
173        });
174
175        methods.add_method(
176            "replace_body",
177            move |_lua, this, (body, content_type): (mlua::String, Option<String>)| {
178                this.replace_body(body, content_type).map_err(any_err)
179            },
180        );
181    }
182
183    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
184        fields.add_field_method_get("body", |lua, this| {
185            let part = this.resolve().map_err(any_err)?;
186            let body = part.body().map_err(any_err)?;
187            match body {
188                DecodedBody::Text(s) => lua.create_string(s.as_str()),
189                DecodedBody::Binary(b) => lua.create_string(b),
190            }
191        });
192        fields.add_field_method_set("body", |_lua, this, body: mlua::String| {
193            this.replace_body(body, None).map_err(any_err)
194        });
195        fields.add_field_method_get("headers", |_lua, this| {
196            let _part = this.resolve().map_err(any_err)?;
197            Ok(crate::headers::HeaderMapRef::new(this.clone()))
198        });
199        fields.add_field_method_get("id", |_lua, this| Ok(this.ptr.id_string()));
200    }
201}
202
203pub struct SimpleStructure {
204    pub text_part: Option<PartRef>,
205    pub html_part: Option<PartRef>,
206    pub header_part: PartRef,
207    pub attachments: Vec<PartRef>,
208}