diff --git a/server/src/nm.rs b/server/src/nm.rs index 4b82f9b..a44389c 100644 --- a/server/src/nm.rs +++ b/server/src/nm.rs @@ -27,6 +27,8 @@ use crate::{ linkify_html, InlineStyle, Query, SanitizeHtml, Transformer, }; +const APPLICATION_GZIP: &'static str = "application/gzip"; + const APPLICATION_ZIP: &'static str = "application/zip"; const IMAGE_JPEG: &'static str = "image/jpeg"; const IMAGE_PJPEG: &'static str = "image/pjpeg"; @@ -351,9 +353,7 @@ pub async fn thread( for i in 0..archive.len() { if let Ok(mut file) = archive.by_index(i) { let name = file.name().to_lowercase(); - if name.ends_with(".xml") - && (name.contains("dmarc") || name.starts_with("google.com!")) - { + if is_dmarc_report_filename(&name) { let mut xml = String::new(); use std::io::Read; if file.read_to_string(&mut xml).is_ok() { @@ -564,8 +564,6 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec) -> Result Result { if let Ok(zip_bytes) = m.get_body_raw() { if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) { @@ -574,16 +572,14 @@ fn extract_zip(m: &ParsedMail) -> Result { 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!")) - { + if is_dmarc_report_filename(&name) { 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!( - "
Google DMARC report summary:
{}
", + "
DMARC report summary:
{}
", report ))); } @@ -605,23 +601,30 @@ fn extract_zip(m: &ParsedMail) -> Result { } fn extract_gzip(m: &ParsedMail) -> Result { - 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!( - "
Microsoft DMARC report summary:
{}
", - report - ))); - } - Err(e) => { - return Ok(Body::html(format!( - "
Failed to parse DMARC report XML: {}
", - e - ))); + let pcd = m.get_content_disposition(); + let filename = pcd.params.get("filename").map(|s| s.to_lowercase()); + + let is_dmarc_xml_file = filename.map_or(false, |name| is_dmarc_report_filename(&name)); + + if is_dmarc_xml_file { + 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!( + "
DMARC report summary:
{}
", + report + ))); + } + Err(e) => { + return Ok(Body::html(format!( + "
Failed to parse DMARC report XML: {}
", + e + ))); + } } } } @@ -752,6 +755,10 @@ fn extract_unhandled(m: &ParsedMail) -> Result { })) } +fn is_dmarc_report_filename(name: &str) -> bool { + (name.ends_with(".xml.gz") || name.ends_with(".xml")) && name.contains("!") +} + // multipart/alternative defines multiple representations of the same message, and clients should // show the fanciest they can display. For this program, the priority is text/html, text/plain, // then give up.