server: add handling for multipart/report w/ ract_rfc822 subpart
This commit is contained in:
parent
285ff1d098
commit
1537333e76
@ -17,6 +17,7 @@ use crate::{
|
|||||||
const APPLICATION_GZIP: &'static str = "application/gzip";
|
const APPLICATION_GZIP: &'static str = "application/gzip";
|
||||||
|
|
||||||
const APPLICATION_ZIP: &'static str = "application/zip";
|
const APPLICATION_ZIP: &'static str = "application/zip";
|
||||||
|
const APPLICATION_TLSRPT_GZIP: &'static str = "application/tlsrpt+gzip";
|
||||||
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";
|
||||||
@ -641,115 +642,178 @@ pub fn extract_gzip(m: &ParsedMail) -> Result<(Body, Option<String>), ServerErro
|
|||||||
Ok((extract_unhandled(m)?, None))
|
Ok((extract_unhandled(m)?, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_report(m: &ParsedMail, _part_addr: &mut Vec<String>) -> Result<Body, ServerError> {
|
pub fn extract_report(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, ServerError> {
|
||||||
let mut html_part = None;
|
let mut parts = Vec::new();
|
||||||
let mut tlsrpt_part = None;
|
|
||||||
|
for (idx, sp) in m.subparts.iter().enumerate() {
|
||||||
|
part_addr.push(idx.to_string());
|
||||||
|
|
||||||
for sp in &m.subparts {
|
|
||||||
match sp.ctype.mimetype.as_str() {
|
match sp.ctype.mimetype.as_str() {
|
||||||
TEXT_HTML => html_part = Some(sp.get_body()?),
|
APPLICATION_TLSRPT_GZIP => {
|
||||||
"application/tlsrpt+gzip" => tlsrpt_part = Some(sp.get_body_raw()?),
|
let gz_bytes = sp.get_body_raw()?;
|
||||||
_ => {} // Ignore other parts for now
|
let mut decoder = flate2::read::GzDecoder::new(&gz_bytes[..]);
|
||||||
}
|
let mut buffer = Vec::new();
|
||||||
}
|
if decoder.read_to_end(&mut buffer).is_ok() {
|
||||||
|
if let Ok(json_str) = String::from_utf8(buffer) {
|
||||||
let tlsrpt_summary_html = if let Some(gz_bytes) = tlsrpt_part {
|
match serde_json::from_str::<TlsRpt>(&json_str) {
|
||||||
let mut decoder = flate2::read::GzDecoder::new(&gz_bytes[..]);
|
Ok(tlsrpt) => {
|
||||||
let mut buffer = Vec::new();
|
let formatted_tlsrpt = FormattedTlsRpt {
|
||||||
if decoder.read_to_end(&mut buffer).is_ok() {
|
organization_name: tlsrpt.organization_name,
|
||||||
if let Ok(json_str) = String::from_utf8(buffer) {
|
date_range: FormattedTlsRptDateRange {
|
||||||
match serde_json::from_str::<TlsRpt>(&json_str) {
|
start_datetime: tlsrpt.date_range.start_datetime,
|
||||||
Ok(tlsrpt) => {
|
end_datetime: tlsrpt.date_range.end_datetime,
|
||||||
let formatted_tlsrpt = FormattedTlsRpt {
|
|
||||||
organization_name: tlsrpt.organization_name,
|
|
||||||
date_range: FormattedTlsRptDateRange {
|
|
||||||
start_datetime: tlsrpt.date_range.start_datetime,
|
|
||||||
end_datetime: tlsrpt.date_range.end_datetime,
|
|
||||||
},
|
|
||||||
contact_info: tlsrpt.contact_info.unwrap_or_else(|| "".to_string()),
|
|
||||||
report_id: tlsrpt.report_id,
|
|
||||||
policies: tlsrpt
|
|
||||||
.policies
|
|
||||||
.into_iter()
|
|
||||||
.map(|policy| FormattedTlsRptPolicy {
|
|
||||||
policy: FormattedTlsRptPolicyDetails {
|
|
||||||
policy_type: policy.policy.policy_type,
|
|
||||||
policy_string: policy.policy.policy_string,
|
|
||||||
policy_domain: policy.policy.policy_domain,
|
|
||||||
mx_host: policy
|
|
||||||
.policy
|
|
||||||
.mx_host
|
|
||||||
.unwrap_or_else(|| Vec::new())
|
|
||||||
.into_iter()
|
|
||||||
.map(|mx| match mx {
|
|
||||||
MxHost::String(s) => FormattedTlsRptMxHost {
|
|
||||||
hostname: s,
|
|
||||||
failure_count: 0,
|
|
||||||
result_type: "".to_string(),
|
|
||||||
},
|
|
||||||
MxHost::Object(o) => FormattedTlsRptMxHost {
|
|
||||||
hostname: o.hostname,
|
|
||||||
failure_count: o.failure_count,
|
|
||||||
result_type: o.result_type,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
},
|
},
|
||||||
summary: policy.summary,
|
contact_info: tlsrpt
|
||||||
failure_details: policy
|
.contact_info
|
||||||
.failure_details
|
.unwrap_or_else(|| "".to_string()),
|
||||||
.unwrap_or_else(|| Vec::new())
|
report_id: tlsrpt.report_id,
|
||||||
|
policies: tlsrpt
|
||||||
|
.policies
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|detail| FormattedTlsRptFailureDetails {
|
.map(|policy| FormattedTlsRptPolicy {
|
||||||
result_type: detail.result_type,
|
policy: FormattedTlsRptPolicyDetails {
|
||||||
sending_mta_ip: detail
|
policy_type: policy.policy.policy_type,
|
||||||
.sending_mta_ip
|
policy_string: policy.policy.policy_string,
|
||||||
.unwrap_or_else(|| "".to_string()),
|
policy_domain: policy.policy.policy_domain,
|
||||||
receiving_ip: detail
|
mx_host: policy
|
||||||
.receiving_ip
|
.policy
|
||||||
.unwrap_or_else(|| "".to_string()),
|
.mx_host
|
||||||
receiving_mx_hostname: detail
|
.unwrap_or_else(|| Vec::new())
|
||||||
.receiving_mx_hostname
|
.into_iter()
|
||||||
.unwrap_or_else(|| "".to_string()),
|
.map(|mx| match mx {
|
||||||
failed_session_count: detail.failed_session_count,
|
MxHost::String(s) => {
|
||||||
additional_info: detail
|
FormattedTlsRptMxHost {
|
||||||
.additional_info
|
hostname: s,
|
||||||
.unwrap_or_else(|| "".to_string()),
|
failure_count: 0,
|
||||||
failure_reason_code: detail
|
result_type: "".to_string(),
|
||||||
.failure_reason_code
|
}
|
||||||
.unwrap_or_else(|| "".to_string()),
|
}
|
||||||
|
MxHost::Object(o) => {
|
||||||
|
FormattedTlsRptMxHost {
|
||||||
|
hostname: o.hostname,
|
||||||
|
failure_count: o.failure_count,
|
||||||
|
result_type: o.result_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
summary: policy.summary,
|
||||||
|
failure_details: policy
|
||||||
|
.failure_details
|
||||||
|
.unwrap_or_else(|| Vec::new())
|
||||||
|
.into_iter()
|
||||||
|
.map(|detail| FormattedTlsRptFailureDetails {
|
||||||
|
result_type: detail.result_type,
|
||||||
|
sending_mta_ip: detail
|
||||||
|
.sending_mta_ip
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
receiving_ip: detail
|
||||||
|
.receiving_ip
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
receiving_mx_hostname: detail
|
||||||
|
.receiving_mx_hostname
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
failed_session_count: detail
|
||||||
|
.failed_session_count,
|
||||||
|
additional_info: detail
|
||||||
|
.additional_info
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
failure_reason_code: detail
|
||||||
|
.failure_reason_code
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
};
|
||||||
.collect(),
|
let template = TlsReportTemplate {
|
||||||
};
|
report: &formatted_tlsrpt,
|
||||||
let template = TlsReportTemplate {
|
};
|
||||||
report: &formatted_tlsrpt,
|
let html = template.render().unwrap_or_else(|e| format!("<div class=\"tlsrpt-error\">Failed to render TLS report template: {}</div>", e));
|
||||||
};
|
parts.push(Body::html(html));
|
||||||
template.render().unwrap_or_else(|e| format!("<div class=\"tlsrpt-error\">Failed to render TLS report template: {}</div>", e))
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let html = format!(
|
||||||
|
"<div class=\"tlsrpt-error\">Failed to parse TLS report JSON: {}</div>",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
parts.push(Body::html(html));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let html = format!("<div class=\"tlsrpt-error\">Failed to convert decompressed data to UTF-8.</div>");
|
||||||
|
parts.push(Body::html(html));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let html =
|
||||||
|
format!("<div class=\"tlsrpt-error\">Failed to decompress data.</div>");
|
||||||
|
parts.push(Body::html(html));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MESSAGE_RFC822 => {
|
||||||
|
parts.push(extract_rfc822(&sp, part_addr)?);
|
||||||
|
}
|
||||||
|
TEXT_HTML => {
|
||||||
|
let body = sp.get_body()?;
|
||||||
|
parts.push(Body::html(body));
|
||||||
|
}
|
||||||
|
TEXT_PLAIN => {
|
||||||
|
let body = sp.get_body()?;
|
||||||
|
parts.push(Body::text(body));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// For any other content type, try to extract the body using the general extract_body function
|
||||||
|
match extract_body(sp, part_addr) {
|
||||||
|
Ok(body) => parts.push(body),
|
||||||
|
Err(_) => {
|
||||||
|
// If extraction fails, create an unhandled content type body
|
||||||
|
let msg = format!(
|
||||||
|
"Unhandled report subpart content type: {}\n{}",
|
||||||
|
sp.ctype.mimetype,
|
||||||
|
sp.get_body()
|
||||||
|
.unwrap_or_else(|_| "Failed to get body".to_string())
|
||||||
|
);
|
||||||
|
parts.push(Body::UnhandledContentType(UnhandledContentType {
|
||||||
|
text: msg,
|
||||||
|
content_tree: render_content_type_tree(sp),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
Err(e) => format!(
|
|
||||||
"<div class=\"tlsrpt-error\">Failed to parse TLS report JSON: {}</div>",
|
|
||||||
e
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
format!("<div class=\"tlsrpt-error\">Failed to convert decompressed data to UTF-8.</div>")
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
format!("<div class=\"tlsrpt-error\">Failed to decompressed data.</div>")
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let final_html = if let Some(html) = html_part {
|
part_addr.pop();
|
||||||
format!("{}<hr>{} ", html, tlsrpt_summary_html)
|
}
|
||||||
} else {
|
|
||||||
tlsrpt_summary_html
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Body::html(final_html))
|
if parts.is_empty() {
|
||||||
|
return Ok(Body::html(
|
||||||
|
"<div class=\"report-error\">No report content found</div>".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add <hr> tags between subparts for better visual separation
|
||||||
|
let html = parts
|
||||||
|
.iter()
|
||||||
|
.map(|p| match p {
|
||||||
|
Body::PlainText(PlainText { text, .. }) => {
|
||||||
|
format!(
|
||||||
|
r#"<p class="view-part-text-plain font-mono whitespace-pre-line">{}</p>"#,
|
||||||
|
linkify_html(&html_escape::encode_text(text).trim_matches('\n'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Body::Html(Html { html, .. }) => html.clone(),
|
||||||
|
Body::UnhandledContentType(UnhandledContentType { text, .. }) => {
|
||||||
|
format!(
|
||||||
|
r#"<p class="view-part-unhandled">{}</p>"#,
|
||||||
|
linkify_html(&html_escape::encode_text(text).trim_matches('\n'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("<hr>\n");
|
||||||
|
|
||||||
|
Ok(Body::html(html))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_unhandled(m: &ParsedMail) -> Result<Body, ServerError> {
|
pub fn extract_unhandled(m: &ParsedMail) -> Result<Body, ServerError> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user