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
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:
parent
59e35062e7
commit
d0f4716d83
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3163,6 +3163,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"css-inline",
|
||||
"flate2",
|
||||
"futures 0.3.31",
|
||||
"headers",
|
||||
"html-escape",
|
||||
|
||||
@ -24,6 +24,7 @@ cacher = { version = "0.2.0", registry = "xinu" }
|
||||
chrono = "0.4.40"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
css-inline = "0.17.0"
|
||||
flate2 = "1.1.2"
|
||||
futures = "0.3.31"
|
||||
headers = "0.4.0"
|
||||
html-escape = "0.2.13"
|
||||
|
||||
102
server/src/nm.rs
102
server/src/nm.rs
@ -451,42 +451,7 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Ser
|
||||
MULTIPART_MIXED => extract_mixed(m, part_addr),
|
||||
MULTIPART_ALTERNATIVE => extract_alternative(m, part_addr),
|
||||
MULTIPART_RELATED => extract_related(m, part_addr),
|
||||
APPLICATION_ZIP => {
|
||||
// Try to extract DMARC XML from ZIP
|
||||
if let Ok(zip_bytes) = m.get_body_raw() {
|
||||
if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) {
|
||||
for i in 0..archive.len() {
|
||||
if let Ok(mut file) = archive.by_index(i) {
|
||||
let name = file.name().to_lowercase();
|
||||
// Google DMARC reports are typically named like "google.com!example.com!...xml"
|
||||
// and may or may not contain "dmarc" in the filename.
|
||||
if name.ends_with(".xml") && (name.contains("dmarc") || name.starts_with("google.com!")) {
|
||||
let mut xml = String::new();
|
||||
use std::io::Read;
|
||||
if file.read_to_string(&mut xml).is_ok() {
|
||||
match parse_dmarc_report(&xml) {
|
||||
Ok(report) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report\">Google DMARC report summary:<br>{}</div>",
|
||||
report
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no DMARC report found, fall through to unhandled
|
||||
extract_unhandled(m)
|
||||
}
|
||||
APPLICATION_ZIP => extract_zip(m),
|
||||
_ => extract_unhandled(m),
|
||||
};
|
||||
if let Err(err) = ret {
|
||||
@ -496,6 +461,69 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Ser
|
||||
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(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) {
|
||||
for i in 0..archive.len() {
|
||||
if let Ok(mut file) = archive.by_index(i) {
|
||||
let name = file.name().to_lowercase();
|
||||
// Google DMARC reports are typically named like "google.com!example.com!...xml"
|
||||
// and may or may not contain "dmarc" in the filename.
|
||||
if name.ends_with(".xml") && (name.contains("dmarc") || name.starts_with("google.com!")) {
|
||||
let mut xml = String::new();
|
||||
use std::io::Read;
|
||||
if file.read_to_string(&mut xml).is_ok() {
|
||||
match parse_dmarc_report(&xml) {
|
||||
Ok(report) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report\">Google DMARC report summary:<br>{}</div>",
|
||||
report
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no DMARC report found, fall through to unhandled
|
||||
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
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
extract_unhandled(m)
|
||||
}
|
||||
|
||||
fn extract_unhandled(m: &ParsedMail) -> Result<Body, ServerError> {
|
||||
let msg = format!(
|
||||
"Unhandled body content type:\n{}\n{}",
|
||||
@ -565,6 +593,7 @@ fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Se
|
||||
MULTIPART_RELATED,
|
||||
TEXT_HTML,
|
||||
TEXT_PLAIN,
|
||||
APPLICATION_GZIP,
|
||||
];
|
||||
let mut unhandled_types: Vec<_> = m
|
||||
.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)),
|
||||
}
|
||||
part_addr.pop();
|
||||
|
||||
@ -16,10 +16,11 @@
|
||||
<link data-trunk rel="css" href="static/vars.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/email-specific.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="app"></section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@ -1025,7 +1025,7 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool)
|
||||
],
|
||||
IF!(open =>
|
||||
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 {
|
||||
ShowThreadQueryThreadOnEmailThreadMessagesBody::UnhandledContentType(
|
||||
ShowThreadQueryThreadOnEmailThreadMessagesBodyOnUnhandledContentType { contents ,content_tree},
|
||||
|
||||
@ -57,15 +57,6 @@ html {
|
||||
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 */
|
||||
.news-post figcaption.wp-caption-text {
|
||||
background-color: initial !important;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user