1use mail_builder::{headers::text::Text, MessageBuilder};
6use mail_parser::{Message, MessageParser};
7
8#[cfg(feature = "pgp")]
9use crate::{message::header, pgp::Pgp};
10use crate::{message::MmlBodyCompiler, Error, Result};
11
12#[derive(Clone, Debug, Default)]
17pub struct MmlCompilerBuilder {
18 mml_body_compiler: MmlBodyCompiler,
20}
21
22impl MmlCompilerBuilder {
23 pub fn new() -> Self {
25 Self::default()
26 }
27
28 #[cfg(feature = "pgp")]
30 pub fn set_pgp(&mut self, pgp: impl Into<Pgp>) {
31 self.mml_body_compiler.set_pgp(pgp);
32 }
33
34 #[cfg(feature = "pgp")]
36 pub fn with_pgp(mut self, pgp: impl Into<Pgp>) -> Self {
37 self.mml_body_compiler.set_pgp(pgp);
38 self
39 }
40
41 #[cfg(feature = "pgp")]
43 pub fn set_some_pgp(&mut self, pgp: Option<impl Into<Pgp>>) {
44 self.mml_body_compiler.set_some_pgp(pgp);
45 }
46
47 #[cfg(feature = "pgp")]
49 pub fn with_some_pgp(mut self, pgp: Option<impl Into<Pgp>>) -> Self {
50 self.mml_body_compiler.set_some_pgp(pgp);
51 self
52 }
53
54 pub fn build(self, mml_msg: &str) -> Result<MmlCompiler<'_>> {
56 let mml_msg = MessageParser::new()
57 .parse(mml_msg.as_bytes())
58 .ok_or(Error::ParseMessageError)?;
59 let mml_body_compiler = self.mml_body_compiler;
60
61 #[cfg(feature = "pgp")]
62 let mml_body_compiler = mml_body_compiler
63 .with_pgp_recipients(header::extract_emails(mml_msg.to()))
64 .with_pgp_sender(header::extract_first_email(mml_msg.from()));
65
66 Ok(MmlCompiler {
67 mml_msg,
68 mml_body_compiler,
69 })
70 }
71}
72
73#[derive(Clone, Debug, Default)]
78pub struct MmlCompiler<'a> {
79 mml_msg: Message<'a>,
80 mml_body_compiler: MmlBodyCompiler,
81}
82
83impl MmlCompiler<'_> {
84 pub async fn compile(&self) -> Result<MmlCompileResult<'_>> {
89 let mml_body = self
90 .mml_msg
91 .text_bodies()
92 .next()
93 .ok_or(Error::ParseMmlEmptyBodyError)?
94 .text_contents()
95 .ok_or(Error::ParseMmlEmptyBodyContentError)?;
96
97 let mml_body_compiler = &self.mml_body_compiler;
98
99 let mut mime_msg_builder = mml_body_compiler.compile(mml_body).await?;
100
101 mime_msg_builder = mime_msg_builder.header("MIME-Version", Text::new("1.0"));
102
103 for header in self.mml_msg.headers() {
104 let key = header.name.as_str();
105 let val = super::header::to_builder_val(header);
106 mime_msg_builder = mime_msg_builder.header(key, val);
107 }
108
109 Ok(MmlCompileResult { mime_msg_builder })
110 }
111}
112
113#[derive(Clone, Debug, Default)]
118pub struct MmlCompileResult<'a> {
119 mime_msg_builder: MessageBuilder<'a>,
120}
121
122impl<'a> MmlCompileResult<'a> {
123 pub fn as_msg_builder(&self) -> &MessageBuilder {
125 &self.mime_msg_builder
126 }
127
128 pub fn to_msg_builder(&self) -> MessageBuilder {
130 self.mime_msg_builder.clone()
131 }
132
133 pub fn into_msg_builder(self) -> MessageBuilder<'a> {
135 self.mime_msg_builder
136 }
137
138 pub fn into_vec(self) -> Result<Vec<u8>> {
140 self.mime_msg_builder
141 .write_to_vec()
142 .map_err(Error::CompileMmlMessageToVecError)
143 }
144
145 pub fn into_string(self) -> Result<String> {
147 self.mime_msg_builder
148 .write_to_string()
149 .map_err(Error::CompileMmlMessageToStringError)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use concat_with::concat_line;
156
157 use crate::{MimeInterpreterBuilder, MmlCompilerBuilder};
158
159 #[tokio::test]
160 async fn non_ascii_headers() {
161 let mml = concat_line!(
162 "Message-ID: <id@localhost>",
163 "Date: Thu, 1 Jan 1970 00:00:00 +0000",
164 "From: Frȯm <from@localhost>",
165 "To: Tó <to@localhost>",
166 "Subject: Subjêct",
167 "",
168 "Hello, world!",
169 "",
170 );
171
172 let mml_compiler = MmlCompilerBuilder::new().build(mml).unwrap();
173 let mime_msg_builder = mml_compiler.compile().await.unwrap().into_msg_builder();
174
175 let mml_msg = MimeInterpreterBuilder::new()
176 .with_show_only_headers(["From", "To", "Subject"])
177 .build()
178 .from_msg_builder(mime_msg_builder)
179 .await
180 .unwrap();
181
182 let expected_mml_msg = concat_line!(
183 "From: Frȯm <from@localhost>",
184 "To: Tó <to@localhost>",
185 "Subject: Subjêct",
186 "",
187 "Hello, world!",
188 "",
189 );
190
191 assert_eq!(mml_msg, expected_mml_msg);
192 }
193
194 #[tokio::test]
195 async fn message_id_with_angles() {
196 let mml = concat_line!(
197 "From: Hugo Osvaldo Barrera <hugo@localhost>",
198 "To: Hugo Osvaldo Barrera <hugo@localhost>",
199 "Cc:",
200 "Subject: Blah",
201 "Message-ID: <bfb64e12-b7d4-474c-a658-8a221365f8ca@localhost>",
202 "",
203 "Test message",
204 "",
205 );
206
207 let mml_compiler = MmlCompilerBuilder::new().build(mml).unwrap();
208 let mime_msg_builder = mml_compiler.compile().await.unwrap().into_msg_builder();
209
210 let mml_msg = MimeInterpreterBuilder::new()
211 .with_show_only_headers(["Message-ID"])
212 .build()
213 .from_msg_builder(mime_msg_builder)
214 .await
215 .unwrap();
216
217 let expected_mml_msg = concat_line!(
218 "Message-ID: <bfb64e12-b7d4-474c-a658-8a221365f8ca@localhost>",
219 "",
220 "Test message",
221 "",
222 );
223
224 assert_eq!(mml_msg, expected_mml_msg);
225 }
226
227 #[tokio::test]
228 async fn message_id_without_angles() {
229 let mml = concat_line!(
230 "From: Hugo Osvaldo Barrera <hugo@localhost>",
231 "To: Hugo Osvaldo Barrera <hugo@localhost>",
232 "Cc:",
233 "Subject: Blah",
234 "Message-ID: bfb64e12-b7d4-474c-a658-8a221365f8ca@localhost",
235 "",
236 "Test message",
237 "",
238 );
239
240 let mml_compiler = MmlCompilerBuilder::new().build(mml).unwrap();
241 let mime_msg_builder = mml_compiler.compile().await.unwrap().into_msg_builder();
242
243 let mml_msg = MimeInterpreterBuilder::new()
244 .with_show_only_headers(["Message-ID"])
245 .build()
246 .from_msg_builder(mime_msg_builder)
247 .await
248 .unwrap();
249
250 let expected_mml_msg = concat_line!(
251 "Message-ID: <bfb64e12-b7d4-474c-a658-8a221365f8ca@localhost>",
252 "",
253 "Test message",
254 "",
255 );
256
257 assert_eq!(mml_msg, expected_mml_msg);
258 }
259
260 #[tokio::test]
261 async fn mml_markup_unescaped() {
262 let mml = concat_line!(
263 "Message-ID: <id@localhost>",
264 "Date: Thu, 1 Jan 1970 00:00:00 +0000",
265 "From: from@localhost",
266 "To: to@localhost",
267 "Subject: subject",
268 "",
269 "<#!part>This should be unescaped<#!/part>",
270 "",
271 );
272
273 let mml_compiler = MmlCompilerBuilder::new().build(mml).unwrap();
274 let compile_mml_res = mml_compiler.compile().await.unwrap();
275 let mime_msg_builder = compile_mml_res.clone().into_msg_builder();
276 let mime_msg_str = compile_mml_res.into_string().unwrap();
277
278 let mml_msg = MimeInterpreterBuilder::new()
279 .with_show_only_headers(["From", "To", "Subject"])
280 .build()
281 .from_msg_builder(mime_msg_builder)
282 .await
283 .unwrap();
284
285 let expected_mml_msg = concat_line!(
286 "From: from@localhost",
287 "To: to@localhost",
288 "Subject: subject",
289 "",
290 "<#!part>This should be unescaped<#!/part>",
291 "",
292 );
293
294 assert!(!mime_msg_str.contains("<#!part>"));
295 assert!(mime_msg_str.contains("<#part>"));
296
297 assert!(!mime_msg_str.contains("<#!/part>"));
298 assert!(mime_msg_str.contains("<#/part>"));
299
300 assert_eq!(mml_msg, expected_mml_msg);
301 }
302}