[pve-devel] [PATCH proxmox 1/3] notify: smtp: move building of forwarded mails to separate function
Lukas Wagner
l.wagner at proxmox.com
Fri May 23 14:26:13 CEST 2025
This allows us to write some tests for it.
No behavioral changes intended.
Signed-off-by: Lukas Wagner <l.wagner at proxmox.com>
---
proxmox-notify/src/endpoints/smtp.rs | 189 ++++++++++++++-------------
1 file changed, 97 insertions(+), 92 deletions(-)
diff --git a/proxmox-notify/src/endpoints/smtp.rs b/proxmox-notify/src/endpoints/smtp.rs
index b88e6c95..2f4bad3c 100644
--- a/proxmox-notify/src/endpoints/smtp.rs
+++ b/proxmox-notify/src/endpoints/smtp.rs
@@ -251,98 +251,8 @@ impl Endpoint for SmtpEndpoint {
.map_err(|err| Error::NotifyFailed(self.name().into(), Box::new(err)))?
}
#[cfg(feature = "mail-forwarder")]
- Content::ForwardedMail { ref raw, title, .. } => {
- use lettre::message::header::ContentTransferEncoding;
- use lettre::message::Body;
- use tracing::error;
-
- let parsed_message = mail_parser::Message::parse(raw)
- .ok_or_else(|| Error::Generic("could not parse forwarded email".to_string()))?;
-
- let root_part = parsed_message
- .part(0)
- .ok_or_else(|| Error::Generic("root message part not present".to_string()))?;
-
- let raw_body = parsed_message
- .raw_message()
- .get(root_part.offset_body..root_part.offset_end)
- .ok_or_else(|| Error::Generic("could not get raw body content".to_string()))?;
-
- // We assume that the original message content is already properly
- // encoded, thus we add the original message body in 'Binary' encoding.
- // This prohibits lettre from trying to re-encode our raw body data.
- // lettre will automatically set the `Content-Transfer-Encoding: binary` header,
- // which we need to remove. The actual transfer encoding is later
- // copied from the original message headers.
- let body =
- Body::new_with_encoding(raw_body.to_vec(), ContentTransferEncoding::Binary)
- .map_err(|_| Error::Generic("could not create body".into()))?;
- let mut message = email_builder
- .subject(title)
- .body(body)
- .map_err(|err| Error::NotifyFailed(self.name().into(), Box::new(err)))?;
- message
- .headers_mut()
- .remove_raw("Content-Transfer-Encoding");
-
- // Copy over all headers that are relevant to display the original body correctly.
- // Unfortunately this is a bit cumbersome, as we use separate crates for mail parsing (mail-parser)
- // and creating/sending mails (lettre).
- // Note: Other MIME-Headers, such as Content-{ID,Description,Disposition} are only used
- // for body-parts in multipart messages, so we can ignore them for the messages headers.
- // Since we send the original raw body, the part-headers will be included any way.
- for header in parsed_message.headers() {
- let header_name = header.name.as_str();
- // Email headers are case-insensitive, so convert to lowercase...
- let value = match header_name.to_lowercase().as_str() {
- "content-type" => {
- if let mail_parser::HeaderValue::ContentType(ct) = header.value() {
- // mail_parser does not give us access to the full decoded and unfolded
- // header value, so we unfortunately need to reassemble it ourselves.
- // Meh.
- let mut value = ct.ctype().to_string();
- if let Some(subtype) = ct.subtype() {
- value.push('/');
- value.push_str(subtype);
- }
- if let Some(attributes) = ct.attributes() {
- use std::fmt::Write;
-
- for attribute in attributes {
- let _ = write!(
- &mut value,
- "; {}=\"{}\"",
- attribute.0, attribute.1
- );
- }
- }
- Some(value)
- } else {
- None
- }
- }
- "content-transfer-encoding" | "mime-version" => {
- if let mail_parser::HeaderValue::Text(text) = header.value() {
- Some(text.to_string())
- } else {
- None
- }
- }
- _ => None,
- };
-
- if let Some(value) = value {
- match HeaderName::new_from_ascii(header_name.into()) {
- Ok(name) => {
- let header = HeaderValue::new(name, value);
- message.headers_mut().insert_raw(header);
- }
- Err(e) => error!("could not set header: {e}"),
- }
- }
- }
-
- message
+ Content::ForwardedMail { ref raw, .. } => {
+ build_forwarded_message(email_builder, self.name(), raw)?
}
};
@@ -371,3 +281,98 @@ impl Endpoint for SmtpEndpoint {
self.config.disable.unwrap_or_default()
}
}
+
+/// Construct a lettre `Message` from a raw email message.
+#[cfg(feature = "mail-forwarder")]
+fn build_forwarded_message(
+ email_builder: lettre::message::MessageBuilder,
+ endpoint_name: &str,
+ raw: &[u8],
+) -> Result<Message, Error> {
+ use lettre::message::header::ContentTransferEncoding;
+ use lettre::message::Body;
+ use tracing::error;
+
+ let parsed_message = mail_parser::Message::parse(raw)
+ .ok_or_else(|| Error::Generic("could not parse forwarded email".to_string()))?;
+
+ let root_part = parsed_message
+ .part(0)
+ .ok_or_else(|| Error::Generic("root message part not present".to_string()))?;
+
+ let raw_body = parsed_message
+ .raw_message()
+ .get(root_part.offset_body..root_part.offset_end)
+ .ok_or_else(|| Error::Generic("could not get raw body content".to_string()))?;
+
+ // We assume that the original message content is already properly
+ // encoded, thus we add the original message body in 'Binary' encoding.
+ // This prohibits lettre from trying to re-encode our raw body data.
+ // lettre will automatically set the `Content-Transfer-Encoding: binary` header,
+ // which we need to remove. The actual transfer encoding is later
+ // copied from the original message headers.
+ let body = Body::new_with_encoding(raw_body.to_vec(), ContentTransferEncoding::Binary)
+ .map_err(|_| Error::Generic("could not create body".into()))?;
+ let mut message = email_builder
+ .subject(parsed_message.subject().unwrap_or_default())
+ .body(body)
+ .map_err(|err| Error::NotifyFailed(endpoint_name.into(), Box::new(err)))?;
+ message
+ .headers_mut()
+ .remove_raw("Content-Transfer-Encoding");
+
+ // Copy over all headers that are relevant to display the original body correctly.
+ // Unfortunately this is a bit cumbersome, as we use separate crates for mail parsing (mail-parser)
+ // and creating/sending mails (lettre).
+ // Note: Other MIME-Headers, such as Content-{ID,Description,Disposition} are only used
+ // for body-parts in multipart messages, so we can ignore them for the messages headers.
+ // Since we send the original raw body, the part-headers will be included any way.
+ for header in parsed_message.headers() {
+ let header_name = header.name.as_str();
+ // Email headers are case-insensitive, so convert to lowercase...
+ let value = match header_name.to_lowercase().as_str() {
+ "content-type" => {
+ if let mail_parser::HeaderValue::ContentType(ct) = header.value() {
+ // mail_parser does not give us access to the full decoded and unfolded
+ // header value, so we unfortunately need to reassemble it ourselves.
+ // Meh.
+ let mut value = ct.ctype().to_string();
+ if let Some(subtype) = ct.subtype() {
+ value.push('/');
+ value.push_str(subtype);
+ }
+ if let Some(attributes) = ct.attributes() {
+ use std::fmt::Write;
+
+ for attribute in attributes {
+ let _ = write!(&mut value, "; {}=\"{}\"", attribute.0, attribute.1);
+ }
+ }
+ Some(value)
+ } else {
+ None
+ }
+ }
+ "content-transfer-encoding" | "mime-version" => {
+ if let mail_parser::HeaderValue::Text(text) = header.value() {
+ Some(text.to_string())
+ } else {
+ None
+ }
+ }
+ _ => None,
+ };
+
+ if let Some(value) = value {
+ match HeaderName::new_from_ascii(header_name.into()) {
+ Ok(name) => {
+ let header = HeaderValue::new(name, value);
+ message.headers_mut().insert_raw(header);
+ }
+ Err(e) => error!("could not set header: {e}"),
+ }
+ }
+ }
+
+ Ok(message)
+}
--
2.39.5
More information about the pve-devel
mailing list