From 6e15e69254dc566d3e7f42c353acd0cfc31c66b0 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 27 Oct 2024 12:02:00 -0700 Subject: [PATCH] server: handle forwarded rfc822 messages --- server/src/graphql.rs | 14 ++++++++- server/src/nm.rs | 71 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/server/src/graphql.rs b/server/src/graphql.rs index 7d6dd64..9261112 100644 --- a/server/src/graphql.rs +++ b/server/src/graphql.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{fmt, str::FromStr}; use async_graphql::{ connection::{self, Connection, Edge, OpaqueCursor}, @@ -235,6 +235,18 @@ pub struct Email { pub addr: Option, } +impl fmt::Display for Email { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match (&self.name, &self.addr) { + (Some(name), Some(addr)) => write!(f, "{name} <{addr}>")?, + (Some(name), None) => write!(f, "{name}")?, + (None, Some(addr)) => write!(f, "{addr}")?, + (None, name) => write!(f, "")?, + } + Ok(()) + } +} + #[derive(SimpleObject)] pub struct Tag { pub name: String, diff --git a/server/src/nm.rs b/server/src/nm.rs index 1d2563e..dc444d0 100644 --- a/server/src/nm.rs +++ b/server/src/nm.rs @@ -20,14 +20,15 @@ use crate::{ linkify_html, InlineStyle, Query, SanitizeHtml, Transformer, }; -const TEXT_PLAIN: &'static str = "text/plain"; -const TEXT_HTML: &'static str = "text/html"; const IMAGE_JPEG: &'static str = "image/jpeg"; const IMAGE_PJPEG: &'static str = "image/pjpeg"; const IMAGE_PNG: &'static str = "image/png"; +const MESSAGE_RFC822: &'static str = "message/rfc822"; const MULTIPART_ALTERNATIVE: &'static str = "multipart/alternative"; const MULTIPART_MIXED: &'static str = "multipart/mixed"; const MULTIPART_RELATED: &'static str = "multipart/related"; +const TEXT_HTML: &'static str = "text/html"; +const TEXT_PLAIN: &'static str = "text/plain"; const MAX_RAW_MESSAGE_SIZE: usize = 100_000; @@ -174,7 +175,9 @@ pub async fn thread( .and_then(|d| mailparse::dateparse(&d).ok()); let cid_prefix = shared::urls::cid_prefix(None, &id); let base_url = None; - let body = match extract_body(&m, &id)? { + let mut part_addr = Vec::new(); + part_addr.push(id.to_string()); + let body = match extract_body(&m, &mut part_addr)? { Body::PlainText(PlainText { text, content_tree }) => { let text = if text.len() > MAX_RAW_MESSAGE_SIZE { format!( @@ -401,16 +404,14 @@ pub fn attachment_bytes(nm: &Notmuch, id: &str, idx: &[usize]) -> Result Result { - let mut part_addr = Vec::new(); - part_addr.push(id.to_string()); +fn extract_body(m: &ParsedMail, part_addr: &mut Vec) -> Result { let body = m.get_body()?; let ret = match m.ctype.mimetype.as_str() { TEXT_PLAIN => return Ok(Body::text(body)), TEXT_HTML => return Ok(Body::html(body)), - MULTIPART_MIXED => extract_mixed(m, &mut part_addr), - MULTIPART_ALTERNATIVE => extract_alternative(m, &mut part_addr), - MULTIPART_RELATED => extract_related(m, &mut part_addr), + MULTIPART_MIXED => extract_mixed(m, part_addr), + MULTIPART_ALTERNATIVE => extract_alternative(m, part_addr), + MULTIPART_RELATED => extract_related(m, part_addr), _ => extract_unhandled(m), }; if let Err(err) = ret { @@ -480,13 +481,14 @@ fn extract_alternative(m: &ParsedMail, part_addr: &mut Vec) -> Result) -> Result { let handled_types = vec![ + IMAGE_JPEG, + IMAGE_PJPEG, + IMAGE_PNG, + MESSAGE_RFC822, MULTIPART_ALTERNATIVE, MULTIPART_RELATED, TEXT_HTML, TEXT_PLAIN, - IMAGE_JPEG, - IMAGE_PJPEG, - IMAGE_PNG, ]; let mut unhandled_types: Vec<_> = m .subparts @@ -502,6 +504,7 @@ fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec) -> Result parts.push(extract_rfc822(&sp, part_addr)?), MULTIPART_RELATED => parts.push(extract_related(sp, part_addr)?), MULTIPART_ALTERNATIVE => parts.push(extract_alternative(sp, part_addr)?), TEXT_PLAIN => parts.push(Body::text(sp.get_body()?)), @@ -546,7 +549,7 @@ fn flatten_body_parts(parts: &[Body]) -> Body { // Trim newlines to prevent excessive white space at the beginning/end of // presenation. Leave tabs and spaces incase plain text attempts to center a // header on the first line. - linkify_html(&text.trim_matches('\n')) + linkify_html(&html_escape::encode_text(text).trim_matches('\n')) ) } Body::Html(Html { html, .. }) => html.clone(), @@ -557,14 +560,14 @@ fn flatten_body_parts(parts: &[Body]) -> Body { // Trim newlines to prevent excessive white space at the beginning/end of // presenation. Leave tabs and spaces incase plain text attempts to center a // header on the first line. - linkify_html(&text.trim_matches('\n')) + linkify_html(&html_escape::encode_text(text).trim_matches('\n')) ) } }) .collect::>() .join("\n"); - info!("flatten_body_parts {} {html}", parts.len()); + info!("flatten_body_parts {}", parts.len()); Body::html(html) } @@ -704,6 +707,44 @@ fn extract_attachment(m: &ParsedMail, id: &str, idx: &[usize]) -> Option Vec { + emails + .iter() + .map(|e| e.to_string()) + .inspect(|e| info!("e {e}")) + .collect() +} + +fn extract_rfc822(m: &ParsedMail, part_addr: &mut Vec) -> Result { + fn extract_headers(m: &ParsedMail) -> Result { + let path = ""; + let from = email_address_strings(&email_addresses(path, &m, "from")?).join(", "); + let to = email_address_strings(&email_addresses(path, &m, "to")?).join(", "); + let cc = email_address_strings(&email_addresses(path, &m, "cc")?).join(", "); + let date = m.headers.get_first_value("date").unwrap_or(String::new()); + let subject = m + .headers + .get_first_value("subject") + .unwrap_or(String::new()); + let text = format!( + r#" +---------- Forwarded message ---------- +From: {from} +To: {to} +CC: {cc} +Date: {date} +Subject: {subject} +"# + ); + Ok(Body::text(text)) + } + let inner_body = m.get_body()?; + let inner_m = parse_mail(inner_body.as_bytes())?; + let headers = extract_headers(&inner_m)?; + let body = extract_body(&inner_m, part_addr)?; + + Ok(flatten_body_parts(&[headers, body])) +} pub fn get_attachment_filename(header_value: &str) -> &str { info!("get_attachment_filename {header_value}");