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}