[pve-devel] [PATCH proxmox 4/6] notify: move mail formatting to separate function

Lukas Wagner l.wagner at proxmox.com
Mon Jun 24 14:31:32 CEST 2024


This way we can test this in a sane manner and refactor
safely.

Signed-off-by: Lukas Wagner <l.wagner at proxmox.com>
---
 proxmox-notify/src/endpoints/sendmail.rs | 109 +++++++++++++++++------
 1 file changed, 81 insertions(+), 28 deletions(-)

diff --git a/proxmox-notify/src/endpoints/sendmail.rs b/proxmox-notify/src/endpoints/sendmail.rs
index 0f7a61b0..241a2578 100644
--- a/proxmox-notify/src/endpoints/sendmail.rs
+++ b/proxmox-notify/src/endpoints/sendmail.rs
@@ -177,16 +177,13 @@ fn sendmail(
     mailfrom: &str,
     author: &str,
 ) -> Result<(), Error> {
-    use std::fmt::Write as _;
-
     if mailto.is_empty() {
         return Err(Error::Generic(
             "At least one recipient has to be specified!".into(),
         ));
     }
-    let recipients = mailto.join(",");
-
     let now = proxmox_time::epoch_i64();
+    let body = format_mail(mailto, mailfrom, author, subject, text, html, now)?;
 
     let mut sendmail_process = match Command::new("/usr/sbin/sendmail")
         .arg("-B")
@@ -205,13 +202,46 @@ fn sendmail(
         }
         Ok(process) => process,
     };
+
+    if let Err(err) = sendmail_process
+        .stdin
+        .take()
+        .unwrap()
+        .write_all(body.as_bytes())
+    {
+        return Err(Error::Generic(format!(
+            "couldn't write to sendmail stdin: {err}"
+        )));
+    };
+
+    // wait() closes stdin of the child
+    if let Err(err) = sendmail_process.wait() {
+        return Err(Error::Generic(format!(
+            "sendmail did not exit successfully: {err}"
+        )));
+    }
+
+    Ok(())
+}
+
+fn format_mail(
+    mailto: &[&str],
+    mailfrom: &str,
+    author: &str,
+    subject: &str,
+    text: Option<&str>,
+    html: Option<&str>,
+    timestamp: i64,
+) -> Result<String, Error> {
+    use std::fmt::Write as _;
+
+    let recipients = mailto.join(",");
     let mut is_multipart = false;
     if let (Some(_), Some(_)) = (text, html) {
         is_multipart = true;
     }
-
     let mut body = String::new();
-    let boundary = format!("----_=_NextPart_001_{}", now);
+    let boundary = format!("----_=_NextPart_001_{}", timestamp);
     if is_multipart {
         body.push_str("Content-Type: multipart/alternative;\n");
         let _ = writeln!(body, "\tboundary=\"{}\"", boundary);
@@ -226,11 +256,10 @@ fn sendmail(
     }
     let _ = writeln!(body, "From: {} <{}>", author, mailfrom);
     let _ = writeln!(body, "To: {}", &recipients);
-    let rfc2822_date = proxmox_time::epoch_to_rfc2822(now)
+    let rfc2822_date = proxmox_time::epoch_to_rfc2822(timestamp)
         .map_err(|err| Error::Generic(format!("failed to format time: {err}")))?;
     let _ = writeln!(body, "Date: {}", rfc2822_date);
     body.push_str("Auto-Submitted: auto-generated;\n");
-
     if is_multipart {
         body.push('\n');
         body.push_str("This is a multi-part message in MIME format.\n");
@@ -256,26 +285,7 @@ fn sendmail(
             let _ = write!(body, "\n--{}--", boundary);
         }
     }
-
-    if let Err(err) = sendmail_process
-        .stdin
-        .take()
-        .unwrap()
-        .write_all(body.as_bytes())
-    {
-        return Err(Error::Generic(format!(
-            "couldn't write to sendmail stdin: {err}"
-        )));
-    };
-
-    // wait() closes stdin of the child
-    if let Err(err) = sendmail_process.wait() {
-        return Err(Error::Generic(format!(
-            "sendmail did not exit successfully: {err}"
-        )));
-    }
-
-    Ok(())
+    Ok(body)
 }
 
 /// Forwards an email message to a given list of recipients.
@@ -342,4 +352,47 @@ mod test {
         );
         assert!(result.is_err());
     }
+
+    #[test]
+    fn test_format_mail_multipart() {
+        let message = format_mail(
+            &["Tony Est <test at example.com>"],
+            "foobar at example.com",
+            "Fred Oobar",
+            "This is the subject",
+            Some("This is the plain body"),
+            Some("<body>This is the HTML body</body>"),
+            1718977850,
+        )
+        .expect("format_message failed");
+
+        assert_eq!(
+            message,
+            r#"Content-Type: multipart/alternative;
+	boundary="----_=_NextPart_001_1718977850"
+MIME-Version: 1.0
+Subject: This is the subject
+From: Fred Oobar <foobar at example.com>
+To: Tony Est <test at example.com>
+Date: Fri, 21 Jun 2024 15:50:50 +0200
+Auto-Submitted: auto-generated;
+
+This is a multi-part message in MIME format.
+
+------_=_NextPart_001_1718977850
+Content-Type: text/plain;
+	charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+
+This is the plain body
+------_=_NextPart_001_1718977850
+Content-Type: text/html;
+	charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+
+<body>This is the HTML body</body>
+------_=_NextPart_001_1718977850--"#
+                .to_owned()
+        );
+    }
 }
-- 
2.39.2





More information about the pve-devel mailing list