server: add gzip dmarc email support
Some checks failed
Continuous integration / Check (push) Failing after 59s
Continuous integration / Test Suite (push) Failing after 1m32s
Continuous integration / Trunk (push) Failing after 47s
Continuous integration / Rustfmt (push) Failing after 35s
Continuous integration / build (push) Failing after 1m39s
Continuous integration / Disallow unused dependencies (push) Failing after 2m5s

This commit is contained in:
Bill Thiede 2025-08-11 12:41:25 -07:00
parent 59e35062e7
commit d0f4716d83
6 changed files with 71 additions and 47 deletions

1
Cargo.lock generated
View File

@ -3163,6 +3163,7 @@ dependencies = [
"chrono", "chrono",
"clap", "clap",
"css-inline", "css-inline",
"flate2",
"futures 0.3.31", "futures 0.3.31",
"headers", "headers",
"html-escape", "html-escape",

View File

@ -24,6 +24,7 @@ cacher = { version = "0.2.0", registry = "xinu" }
chrono = "0.4.40" chrono = "0.4.40"
clap = { version = "4.5.37", features = ["derive"] } clap = { version = "4.5.37", features = ["derive"] }
css-inline = "0.17.0" css-inline = "0.17.0"
flate2 = "1.1.2"
futures = "0.3.31" futures = "0.3.31"
headers = "0.4.0" headers = "0.4.0"
html-escape = "0.2.13" html-escape = "0.2.13"

View File

@ -451,8 +451,19 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Ser
MULTIPART_MIXED => extract_mixed(m, part_addr), MULTIPART_MIXED => extract_mixed(m, part_addr),
MULTIPART_ALTERNATIVE => extract_alternative(m, part_addr), MULTIPART_ALTERNATIVE => extract_alternative(m, part_addr),
MULTIPART_RELATED => extract_related(m, part_addr), MULTIPART_RELATED => extract_related(m, part_addr),
APPLICATION_ZIP => { APPLICATION_ZIP => extract_zip(m),
// Try to extract DMARC XML from ZIP _ => extract_unhandled(m),
};
if let Err(err) = ret {
error!("Failed to extract body: {err:?}");
return Ok(extract_unhandled(m)?);
}
ret
}
const APPLICATION_GZIP: &'static str = "application/gzip";
fn extract_zip(m: &ParsedMail) -> Result<Body, ServerError> {
if let Ok(zip_bytes) = m.get_body_raw() { if let Ok(zip_bytes) = m.get_body_raw() {
if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) { if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) {
for i in 0..archive.len() { for i in 0..archive.len() {
@ -486,14 +497,31 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Ser
} }
// If no DMARC report found, fall through to unhandled // If no DMARC report found, fall through to unhandled
extract_unhandled(m) extract_unhandled(m)
}
fn extract_gzip(m: &ParsedMail) -> Result<Body, ServerError> {
if let Ok(gz_bytes) = m.get_body_raw() {
let mut decoder = flate2::read::GzDecoder::new(&gz_bytes[..]);
let mut xml = String::new();
use std::io::Read;
if decoder.read_to_string(&mut xml).is_ok() {
match parse_dmarc_report(&xml) {
Ok(report) => {
return Ok(Body::html(format!(
"<div class=\"dmarc-report\">Microsoft DMARC report summary:<br>{}</div>",
report
)));
} }
_ => extract_unhandled(m), Err(e) => {
}; return Ok(Body::html(format!(
if let Err(err) = ret { "<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>",
error!("Failed to extract body: {err:?}"); e
return Ok(extract_unhandled(m)?); )));
} }
ret }
}
}
extract_unhandled(m)
} }
fn extract_unhandled(m: &ParsedMail) -> Result<Body, ServerError> { fn extract_unhandled(m: &ParsedMail) -> Result<Body, ServerError> {
@ -565,6 +593,7 @@ fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Se
MULTIPART_RELATED, MULTIPART_RELATED,
TEXT_HTML, TEXT_HTML,
TEXT_PLAIN, TEXT_PLAIN,
APPLICATION_GZIP,
]; ];
let mut unhandled_types: Vec<_> = m let mut unhandled_types: Vec<_> = m
.subparts .subparts
@ -608,6 +637,7 @@ fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Se
))); )));
} }
} }
APPLICATION_GZIP => parts.push(extract_gzip(sp)?),
mt => parts.push(unhandled_html(MULTIPART_MIXED, mt)), mt => parts.push(unhandled_html(MULTIPART_MIXED, mt)),
} }
part_addr.pop(); part_addr.pop();

View File

@ -16,6 +16,7 @@
<link data-trunk rel="css" href="static/vars.css" /> <link data-trunk rel="css" href="static/vars.css" />
<link data-trunk rel="tailwind-css" href="./src/tailwind.css" /> <link data-trunk rel="tailwind-css" href="./src/tailwind.css" />
<link data-trunk rel="css" href="static/overrides.css" /> <link data-trunk rel="css" href="static/overrides.css" />
<link data-trunk rel="css" href="static/email-specific.css" />
</head> </head>
<body> <body>

View File

@ -1025,7 +1025,7 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool)
], ],
IF!(open => IF!(open =>
div![ div![
C!["content", "bg-white", "text-black", "p-4", "min-w-full", "w-0","overflow-x-auto", from], C!["content", "bg-white", "text-black", "p-4", "min-w-full", "w-0","overflow-x-auto", from.map(|f|format!("from-{f}"))],
match &msg.body { match &msg.body {
ShowThreadQueryThreadOnEmailThreadMessagesBody::UnhandledContentType( ShowThreadQueryThreadOnEmailThreadMessagesBody::UnhandledContentType(
ShowThreadQueryThreadOnEmailThreadMessagesBodyOnUnhandledContentType { contents ,content_tree}, ShowThreadQueryThreadOnEmailThreadMessagesBodyOnUnhandledContentType { contents ,content_tree},

View File

@ -57,15 +57,6 @@ html {
margin-left: 2em; margin-left: 2em;
} }
.mail-thread .content .noreply-news-bloomberg-com a {
background-color: initial !important;
}
.mail-thread .content .noreply-news-bloomberg-com h2 {
margin: 0 !important;
padding: 0 !important;
}
/* Hackaday figures have unreadable black on dark grey */ /* Hackaday figures have unreadable black on dark grey */
.news-post figcaption.wp-caption-text { .news-post figcaption.wp-caption-text {
background-color: initial !important; background-color: initial !important;