server: handle forwarded rfc822 messages
This commit is contained in:
parent
2671a3b787
commit
6e15e69254
@ -1,4 +1,4 @@
|
|||||||
use std::str::FromStr;
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
use async_graphql::{
|
use async_graphql::{
|
||||||
connection::{self, Connection, Edge, OpaqueCursor},
|
connection::{self, Connection, Edge, OpaqueCursor},
|
||||||
@ -235,6 +235,18 @@ pub struct Email {
|
|||||||
pub addr: Option<String>,
|
pub addr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, "<UNKNOWN>")?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(SimpleObject)]
|
#[derive(SimpleObject)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@ -20,14 +20,15 @@ use crate::{
|
|||||||
linkify_html, InlineStyle, Query, SanitizeHtml, Transformer,
|
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_JPEG: &'static str = "image/jpeg";
|
||||||
const IMAGE_PJPEG: &'static str = "image/pjpeg";
|
const IMAGE_PJPEG: &'static str = "image/pjpeg";
|
||||||
const IMAGE_PNG: &'static str = "image/png";
|
const IMAGE_PNG: &'static str = "image/png";
|
||||||
|
const MESSAGE_RFC822: &'static str = "message/rfc822";
|
||||||
const MULTIPART_ALTERNATIVE: &'static str = "multipart/alternative";
|
const MULTIPART_ALTERNATIVE: &'static str = "multipart/alternative";
|
||||||
const MULTIPART_MIXED: &'static str = "multipart/mixed";
|
const MULTIPART_MIXED: &'static str = "multipart/mixed";
|
||||||
const MULTIPART_RELATED: &'static str = "multipart/related";
|
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;
|
const MAX_RAW_MESSAGE_SIZE: usize = 100_000;
|
||||||
|
|
||||||
@ -174,7 +175,9 @@ pub async fn thread(
|
|||||||
.and_then(|d| mailparse::dateparse(&d).ok());
|
.and_then(|d| mailparse::dateparse(&d).ok());
|
||||||
let cid_prefix = shared::urls::cid_prefix(None, &id);
|
let cid_prefix = shared::urls::cid_prefix(None, &id);
|
||||||
let base_url = None;
|
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 }) => {
|
Body::PlainText(PlainText { text, content_tree }) => {
|
||||||
let text = if text.len() > MAX_RAW_MESSAGE_SIZE {
|
let text = if text.len() > MAX_RAW_MESSAGE_SIZE {
|
||||||
format!(
|
format!(
|
||||||
@ -401,16 +404,14 @@ pub fn attachment_bytes(nm: &Notmuch, id: &str, idx: &[usize]) -> Result<Attachm
|
|||||||
Err(ServerError::PartNotFound)
|
Err(ServerError::PartNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_body(m: &ParsedMail, id: &str) -> Result<Body, ServerError> {
|
fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, ServerError> {
|
||||||
let mut part_addr = Vec::new();
|
|
||||||
part_addr.push(id.to_string());
|
|
||||||
let body = m.get_body()?;
|
let body = m.get_body()?;
|
||||||
let ret = match m.ctype.mimetype.as_str() {
|
let ret = match m.ctype.mimetype.as_str() {
|
||||||
TEXT_PLAIN => return Ok(Body::text(body)),
|
TEXT_PLAIN => return Ok(Body::text(body)),
|
||||||
TEXT_HTML => return Ok(Body::html(body)),
|
TEXT_HTML => return Ok(Body::html(body)),
|
||||||
MULTIPART_MIXED => extract_mixed(m, &mut part_addr),
|
MULTIPART_MIXED => extract_mixed(m, part_addr),
|
||||||
MULTIPART_ALTERNATIVE => extract_alternative(m, &mut part_addr),
|
MULTIPART_ALTERNATIVE => extract_alternative(m, part_addr),
|
||||||
MULTIPART_RELATED => extract_related(m, &mut part_addr),
|
MULTIPART_RELATED => extract_related(m, part_addr),
|
||||||
_ => extract_unhandled(m),
|
_ => extract_unhandled(m),
|
||||||
};
|
};
|
||||||
if let Err(err) = ret {
|
if let Err(err) = ret {
|
||||||
@ -480,13 +481,14 @@ fn extract_alternative(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Bo
|
|||||||
// 'serially'.
|
// 'serially'.
|
||||||
fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, ServerError> {
|
fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, ServerError> {
|
||||||
let handled_types = vec![
|
let handled_types = vec![
|
||||||
|
IMAGE_JPEG,
|
||||||
|
IMAGE_PJPEG,
|
||||||
|
IMAGE_PNG,
|
||||||
|
MESSAGE_RFC822,
|
||||||
MULTIPART_ALTERNATIVE,
|
MULTIPART_ALTERNATIVE,
|
||||||
MULTIPART_RELATED,
|
MULTIPART_RELATED,
|
||||||
TEXT_HTML,
|
TEXT_HTML,
|
||||||
TEXT_PLAIN,
|
TEXT_PLAIN,
|
||||||
IMAGE_JPEG,
|
|
||||||
IMAGE_PJPEG,
|
|
||||||
IMAGE_PNG,
|
|
||||||
];
|
];
|
||||||
let mut unhandled_types: Vec<_> = m
|
let mut unhandled_types: Vec<_> = m
|
||||||
.subparts
|
.subparts
|
||||||
@ -502,6 +504,7 @@ fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Se
|
|||||||
for (idx, sp) in m.subparts.iter().enumerate() {
|
for (idx, sp) in m.subparts.iter().enumerate() {
|
||||||
part_addr.push(idx.to_string());
|
part_addr.push(idx.to_string());
|
||||||
match sp.ctype.mimetype.as_str() {
|
match sp.ctype.mimetype.as_str() {
|
||||||
|
MESSAGE_RFC822 => parts.push(extract_rfc822(&sp, part_addr)?),
|
||||||
MULTIPART_RELATED => parts.push(extract_related(sp, part_addr)?),
|
MULTIPART_RELATED => parts.push(extract_related(sp, part_addr)?),
|
||||||
MULTIPART_ALTERNATIVE => parts.push(extract_alternative(sp, part_addr)?),
|
MULTIPART_ALTERNATIVE => parts.push(extract_alternative(sp, part_addr)?),
|
||||||
TEXT_PLAIN => parts.push(Body::text(sp.get_body()?)),
|
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
|
// Trim newlines to prevent excessive white space at the beginning/end of
|
||||||
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
||||||
// header on the first line.
|
// 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(),
|
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
|
// Trim newlines to prevent excessive white space at the beginning/end of
|
||||||
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
// presenation. Leave tabs and spaces incase plain text attempts to center a
|
||||||
// header on the first line.
|
// header on the first line.
|
||||||
linkify_html(&text.trim_matches('\n'))
|
linkify_html(&html_escape::encode_text(text).trim_matches('\n'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
info!("flatten_body_parts {} {html}", parts.len());
|
info!("flatten_body_parts {}", parts.len());
|
||||||
Body::html(html)
|
Body::html(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,6 +707,44 @@ fn extract_attachment(m: &ParsedMail, id: &str, idx: &[usize]) -> Option<Attachm
|
|||||||
bytes,
|
bytes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
fn email_address_strings(emails: &[Email]) -> Vec<String> {
|
||||||
|
emails
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.inspect(|e| info!("e {e}"))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_rfc822(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, ServerError> {
|
||||||
|
fn extract_headers(m: &ParsedMail) -> Result<Body, ServerError> {
|
||||||
|
let path = "<in-memory>";
|
||||||
|
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 {
|
pub fn get_attachment_filename(header_value: &str) -> &str {
|
||||||
info!("get_attachment_filename {header_value}");
|
info!("get_attachment_filename {header_value}");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user