From: Lukas Wagner <l.wagner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox 1/3] notify: smtp: move building of forwarded mails to separate function
Date: Fri, 23 May 2025 14:26:13 +0200 [thread overview]
Message-ID: <20250523122615.251430-5-l.wagner@proxmox.com> (raw)
In-Reply-To: <20250523122615.251430-1-l.wagner@proxmox.com>
This allows us to write some tests for it.
No behavioral changes intended.
Signed-off-by: Lukas Wagner <l.wagner@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
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2025-05-23 12:27 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-23 12:26 [pve-devel] [PATCH debcargo-config/proxmox 0/6] package `mail-parser` for trixie; update proxmox-notify for newest mail-parser version Lukas Wagner
2025-05-23 12:26 ` [pve-devel] [PATCH debcargo-config 1/3] import mail-parser from bookworm Lukas Wagner
2025-05-23 12:26 ` [pve-devel] [PATCH debcargo-config 2/3] package hashify 0.2.6 Lukas Wagner
2025-05-23 12:26 ` [pve-devel] [PATCH debcargo-config 3/3] update mail-parser to 0.11.0 Lukas Wagner
2025-05-23 12:26 ` Lukas Wagner [this message]
2025-05-23 12:26 ` [pve-devel] [PATCH proxmox 2/3] notify: smtp: add test for building forwarded messages Lukas Wagner
2025-05-23 12:26 ` [pve-devel] [PATCH proxmox 3/3] notify: update mail-parser dependency to 0.11 Lukas Wagner
2025-05-26 14:01 ` [pve-devel] applied: [PATCH debcargo-config/proxmox 0/6] package `mail-parser` for trixie; update proxmox-notify for newest mail-parser version Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250523122615.251430-5-l.wagner@proxmox.com \
--to=l.wagner@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.