1use std::cmp::Reverse;
8
9use mail_parser::{
10 decoders::html::html_to_text, Encoding, HeaderName, Message, MessagePart, PartType,
11};
12
13use crate::{
14 compiler::{
15 grammar::actions::action_mime::{Enclose, ExtractText, Replace},
16 VariableType,
17 },
18 Context, Event,
19};
20
21use super::action_editheader::RemoveCrLf;
22
23#[cfg(not(test))]
24use mail_builder::headers::message_id::generate_message_id_header;
25
26impl Replace {
27 pub(crate) fn exec(&self, ctx: &mut Context) {
28 let mut part_ids = ctx.find_nested_parts_ids(false);
30 part_ids.sort_unstable_by_key(|a| Reverse(*a));
31 for part_id in part_ids {
32 ctx.message.parts.remove(part_id as usize);
33 }
34 ctx.has_changes = true;
35
36 let body = ctx.eval_value(&self.replacement).to_string().into_owned();
38 let body_len = body.len();
39
40 let part = &mut ctx.message.parts[ctx.part as usize];
41
42 ctx.message_size = ctx.message_size + body_len
43 - (if part.offset_body != 0 {
44 (part.offset_end - part.offset_header) as usize
45 } else {
46 part.body.len()
47 });
48 part.body = PartType::Text(body.into());
49 part.encoding = if !self.mime {
50 Encoding::QuotedPrintable
51 } else {
52 Encoding::None
53 };
54 part.offset_body = 0;
55 let prev_headers = std::mem::take(&mut part.headers);
56 let mut add_date = true;
57
58 if ctx.part == 0 {
59 for mut header in prev_headers {
60 let mut size = (header.offset_end - header.offset_field) as usize;
61 match &header.name {
62 HeaderName::Subject => {
63 if self.subject.is_some() {
64 header.name = HeaderName::Other("Original-Subject".into());
65 header.offset_field = header.offset_start;
66 size += "Original-".len();
67 }
68 }
69 HeaderName::From => {
70 if self.from.is_some() {
71 header.name = HeaderName::Other("Original-From".into());
72 header.offset_field = header.offset_start;
73 size += "Original-".len();
74 }
75 }
76
77 HeaderName::To | HeaderName::Cc | HeaderName::Bcc | HeaderName::Received => (),
78 HeaderName::Date => {
79 add_date = false;
80 }
81 _ => continue,
82 }
83 ctx.message_size += size;
84 part.headers.push(header);
85 }
86
87 let mut add_from = true;
89 if let Some(from) = self.from.as_ref().map(|f| ctx.eval_value(f)) {
90 if !from.is_empty() {
91 ctx.insert_header(
92 0,
93 HeaderName::Other("From".into()),
94 from.to_string()
95 .as_ref()
96 .remove_crlf(ctx.runtime.max_header_size),
97 true,
98 );
99 add_from = false;
100 }
101 }
102 if add_from {
103 ctx.insert_header(
104 0,
105 HeaderName::Other("From".to_string().into()),
106 ctx.user_from_field(),
107 true,
108 );
109 }
110
111 if let Some(subject) = self.subject.as_ref().map(|f| ctx.eval_value(f)) {
113 if !subject.is_empty() {
114 ctx.insert_header(
115 0,
116 HeaderName::Other("Subject".into()),
117 subject
118 .to_string()
119 .as_ref()
120 .remove_crlf(ctx.runtime.max_header_size),
121 true,
122 );
123 }
124 }
125
126 if add_date {
128 #[cfg(not(test))]
129 let header_value = mail_builder::headers::date::Date::now().to_rfc822();
130 #[cfg(test)]
131 let header_value = "Tue, 20 Nov 2022 05:14:20 -0300".to_string();
132
133 ctx.insert_header(
134 0,
135 HeaderName::Other("Date".to_string().into()),
136 header_value,
137 true,
138 );
139 }
140
141 let mut header_value = Vec::with_capacity(20);
143 #[cfg(not(test))]
144 generate_message_id_header(&mut header_value, &ctx.runtime.local_hostname).unwrap();
145 #[cfg(test)]
146 header_value.extend_from_slice(b"<auto-generated@message-id>");
147
148 ctx.insert_header(
149 0,
150 HeaderName::Other("Message-ID".to_string().into()),
151 String::from_utf8(header_value).unwrap(),
152 true,
153 );
154 }
155
156 if !self.mime {
157 ctx.insert_header(
158 ctx.part,
159 HeaderName::Other("Content-Type".into()),
160 "text/plain; charset=utf-8".to_string(),
161 true,
162 );
163 }
164 }
165}
166
167impl Enclose {
168 pub(crate) fn exec(&self, ctx: &mut Context) {
169 let body = ctx.eval_value(&self.value).to_string().into_owned();
170 let subject = self
171 .subject
172 .as_ref()
173 .map(|s| {
174 ctx.eval_value(s)
175 .to_string()
176 .as_ref()
177 .remove_crlf(ctx.runtime.max_header_size)
178 })
179 .or_else(|| ctx.message.subject().map(|s| s.to_string()))
180 .unwrap_or_default();
181
182 let message = std::mem::take(&mut ctx.message);
183 #[cfg(test)]
184 let boundary = make_test_boundary();
185 #[cfg(not(test))]
186 let boundary = mail_builder::mime::make_boundary(".");
187
188 ctx.message_size += ((boundary.len() + 6) * 3) + body.len() + 2;
189 ctx.part = 0;
190 ctx.has_changes = true;
191 ctx.message = Message {
192 html_body: Vec::with_capacity(0),
193 text_body: Vec::with_capacity(0),
194 attachments: Vec::with_capacity(0),
195 parts: vec![
196 MessagePart {
197 headers: vec![],
198 is_encoding_problem: false,
199 body: PartType::Multipart(vec![1, 2]),
200 encoding: Encoding::None,
201 offset_header: 0,
202 offset_body: 0,
203 offset_end: 0,
204 },
205 MessagePart {
206 headers: vec![],
207 is_encoding_problem: false,
208 body: PartType::Text(body.into()),
209 encoding: Encoding::QuotedPrintable, offset_header: 0,
211 offset_body: 0,
212 offset_end: 0,
213 },
214 MessagePart {
215 headers: vec![],
216 is_encoding_problem: false,
217 body: PartType::Message(message),
218 encoding: Encoding::QuotedPrintable, offset_header: 0,
220 offset_body: 0,
221 offset_end: 0,
222 },
223 ],
224 raw_message: b""[..].into(),
225 };
226
227 ctx.insert_header(
228 0,
229 HeaderName::Other("Content-Type".into()),
230 format!("multipart/mixed; boundary=\"{boundary}\""),
231 true,
232 );
233 ctx.insert_header(0, HeaderName::Other("Subject".into()), subject, true);
234 ctx.insert_header(
235 1,
236 HeaderName::Other("Content-Type".into()),
237 "text/plain; charset=utf-8",
238 true,
239 );
240 ctx.insert_header(
241 2,
242 HeaderName::Other("Content-Type".into()),
243 "message/rfc822",
244 true,
245 );
246
247 let mut add_date = true;
248 let mut add_message_id = true;
249 let mut add_from = true;
250
251 for header in &self.headers {
252 let header = ctx.eval_value(header);
253 if let Some((mut header_name, mut header_value)) =
254 header.to_string().as_ref().split_once(':')
255 {
256 header_name = header_name.trim();
257 header_value = header_value.trim();
258 if !header_value.is_empty() {
259 if let Some(name) = HeaderName::parse(header_name) {
260 if !ctx.runtime.protected_headers.contains(&name) {
261 match &name {
262 HeaderName::Date => {
263 add_date = false;
264 }
265 HeaderName::From => {
266 add_from = false;
267 }
268 HeaderName::MessageId => {
269 add_message_id = false;
270 }
271 _ => (),
272 }
273
274 ctx.insert_header(
275 0,
276 HeaderName::Other(header_name.to_string().into()),
277 header_value.remove_crlf(ctx.runtime.max_header_size),
278 true,
279 );
280 }
281 }
282 }
283 }
284 }
285
286 if add_from {
287 ctx.insert_header(
288 0,
289 HeaderName::Other("From".to_string().into()),
290 ctx.user_from_field(),
291 true,
292 );
293 }
294
295 if add_date {
296 #[cfg(not(test))]
297 let header_value = mail_builder::headers::date::Date::now().to_rfc822();
298 #[cfg(test)]
299 let header_value = "Tue, 20 Nov 2022 05:14:20 -0300".to_string();
300
301 ctx.insert_header(
302 0,
303 HeaderName::Other("Date".to_string().into()),
304 header_value,
305 true,
306 );
307 }
308
309 if add_message_id {
310 let mut header_value = Vec::with_capacity(20);
311 #[cfg(not(test))]
312 generate_message_id_header(&mut header_value, &ctx.runtime.local_hostname).unwrap();
313 #[cfg(test)]
314 header_value.extend_from_slice(b"<auto-generated@message-id>");
315
316 ctx.insert_header(
317 0,
318 HeaderName::Other("Message-ID".to_string().into()),
319 String::from_utf8(header_value).unwrap(),
320 true,
321 );
322 }
323 }
324}
325
326impl ExtractText {
327 pub(crate) fn exec(&self, ctx: &mut Context) {
328 let mut value = String::new();
329
330 if !ctx.part_iter_stack.is_empty() {
331 match ctx.message.parts.get(ctx.part as usize).map(|p| &p.body) {
332 Some(PartType::Text(text)) => {
333 value = if let Some(first) = &self.first {
334 text.chars().take(*first).collect()
335 } else {
336 text.as_ref().to_string()
337 };
338 }
339 Some(PartType::Html(html)) => {
340 value = if let Some(first) = &self.first {
341 html_to_text(html.as_ref()).chars().take(*first).collect()
342 } else {
343 html_to_text(html.as_ref())
344 };
345 }
346 _ => (),
347 }
348
349 if !self.modifiers.is_empty() && !value.is_empty() {
350 for modifier in &self.modifiers {
351 value = modifier.apply(&value, ctx);
352 }
353 }
354 }
355
356 match &self.name {
357 VariableType::Local(var_id) => {
358 if let Some(var) = ctx.vars_local.get_mut(*var_id) {
359 *var = value.into();
360 } else {
361 debug_assert!(false, "Non-existent local variable {var_id}");
362 }
363 }
364 VariableType::Global(var_name) => {
365 ctx.vars_global
366 .insert(var_name.to_string().into(), value.into());
367 }
368 VariableType::Envelope(env) => {
369 ctx.add_set_envelope_event(*env, value);
370 }
371 _ => (),
372 }
373 }
374}
375
376enum StackItem<'x> {
377 Message(&'x Message<'x>),
378 Boundary(&'x str),
379 None,
380}
381
382impl Context<'_> {
383 pub(crate) fn build_message_id(&mut self) -> Option<Event> {
384 if self.has_changes {
385 self.last_message_id += 1;
386 self.main_message_id = self.last_message_id;
387 self.has_changes = false;
388 let message = self.build_message();
389 Some(Event::CreatedMessage {
390 message_id: self.main_message_id,
391 message,
392 })
393 } else {
394 None
395 }
396 }
397
398 pub(crate) fn build_message(&mut self) -> Vec<u8> {
399 let mut current_message = &self.message;
400 let mut current_boundary = "";
401 let mut message = Vec::with_capacity(self.message_size);
402 let mut iter = [0u32].iter();
403 let mut iter_stack = Vec::new();
404 let mut last_offset = 0;
405
406 'outer: loop {
407 while let Some(part) = iter
408 .next()
409 .and_then(|p| current_message.parts.get(*p as usize))
410 {
411 if last_offset > 0 {
412 message.extend_from_slice(
413 ¤t_message.raw_message
414 [last_offset as usize..part.offset_header as usize],
415 );
416 } else if !current_boundary.is_empty()
417 && part.offset_end == 0
418 && !matches!(iter_stack.last(), Some((StackItem::Message(_), _, _)))
419 {
420 message.extend_from_slice(b"\r\n--");
421 message.extend_from_slice(current_boundary.as_bytes());
422 message.extend_from_slice(b"\r\n");
423 }
424
425 let mut ct_pos = usize::MAX;
426
427 for (header_pos, header) in part.headers.iter().enumerate() {
428 if header.offset_end != 0 {
429 if header.offset_field != header.offset_start {
430 message.extend_from_slice(
431 ¤t_message.raw_message
432 [header.offset_field as usize..header.offset_end as usize],
433 );
434 } else {
435 message.extend_from_slice(header.name.as_str().as_bytes());
437 message.extend_from_slice(b":");
438 message.extend_from_slice(
439 ¤t_message.raw_message
440 [header.offset_start as usize..header.offset_end as usize],
441 );
442 }
443 } else {
444 if header.name == HeaderName::Other("Content-Type".into()) {
445 ct_pos = header_pos;
446 }
447
448 message.extend_from_slice(header.name.as_str().as_bytes());
449 message.extend_from_slice(b": ");
450 message.extend_from_slice(header.value.as_text().unwrap_or("").as_bytes());
451 message.extend_from_slice(b"\r\n");
452 }
453 }
454
455 if part.offset_body != 0 || part.encoding != Encoding::None {
456 message.extend_from_slice(b"\r\n");
458 }
459
460 if part.offset_body != 0 {
461 if let PartType::Multipart(subparts) = &part.body {
464 iter_stack.push((
466 StackItem::None,
467 part,
468 std::mem::replace(&mut iter, subparts.iter()),
469 ));
470 last_offset = part.offset_body;
471 continue 'outer;
472 } else {
473 message.extend_from_slice(
474 ¤t_message.raw_message
475 [part.offset_body as usize..part.offset_end as usize],
476 )
477 }
478 } else {
479 match &part.body {
480 PartType::Message(nested_message) => {
481 iter_stack.push((
483 StackItem::Message(current_message),
484 part,
485 std::mem::replace(&mut iter, [0].iter()),
486 ));
487 current_message = nested_message;
488 continue 'outer;
489 }
490 PartType::Multipart(subparts) => {
491 let prev_boundary = std::mem::replace(
493 &mut current_boundary,
494 if ct_pos != usize::MAX {
495 part.headers[ct_pos]
496 .value
497 .as_text()
498 .and_then(|h| h.split_once("boundary=\""))
499 .and_then(|(_, h)| h.split_once('\"'))
500 .map(|(h, _)| h)
501 } else {
502 None
503 }
504 .unwrap_or("invalid-boundary"),
505 );
506
507 iter_stack.push((
509 StackItem::Boundary(prev_boundary),
510 part,
511 std::mem::replace(&mut iter, subparts.iter()),
512 ));
513 continue 'outer;
514 }
515 _ => {
516 message.extend_from_slice(part.contents());
518 }
519 }
520 }
521 last_offset = part.offset_end;
522 }
523
524 if let Some((prev_item, prev_part, prev_iter)) = iter_stack.pop() {
525 match prev_item {
526 StackItem::Message(prev_message) => {
527 if last_offset > 0 {
528 if let Some(bytes) =
529 current_message.raw_message.get(last_offset as usize..)
530 {
531 message.extend_from_slice(bytes);
532 }
533 last_offset = 0;
534 }
535 current_message = prev_message;
536 }
537 StackItem::Boundary(prev_boundary) => {
538 if !current_boundary.is_empty() {
539 message.extend_from_slice(b"\r\n--");
540 message.extend_from_slice(current_boundary.as_bytes());
541 message.extend_from_slice(b"--\r\n");
542 }
543 current_boundary = prev_boundary;
544 }
545 StackItem::None => {
546 message.extend_from_slice(
547 ¤t_message.raw_message
548 [last_offset as usize..prev_part.offset_end as usize],
549 );
550 last_offset = prev_part.offset_end;
551 }
552 }
553 iter = prev_iter;
554 } else {
555 break;
556 }
557 }
558
559 if last_offset > 0 {
560 if let Some(bytes) = current_message.raw_message.get(last_offset as usize..) {
561 message.extend_from_slice(bytes);
562 }
563 }
564
565 message
566 }
567}
568
569#[cfg(test)]
570thread_local!(static COUNTER: std::cell::Cell<u64> = 0.into());
571
572#[cfg(test)]
573pub(crate) fn make_test_boundary() -> String {
574 format!("boundary_{}", COUNTER.with(|c| { c.replace(c.get() + 1) }))
575}
576
577#[cfg(test)]
578pub(crate) fn reset_test_boundary() {
579 COUNTER.with(|c| c.replace(0));
580}