log-types: migrate mailparse -> mailparsing

This commit is contained in:
Wez Furlong
2023-08-25 08:55:29 -07:00
parent 81d5dd77da
commit 2ec6386b97
7 changed files with 119 additions and 95 deletions
Generated
+1 -1
View File
@@ -2323,7 +2323,7 @@ dependencies = [
"bounce-classify",
"chrono",
"k9",
"mailparse",
"mailparsing",
"rfc5321",
"serde",
"serde_json",
+1 -1
View File
@@ -10,8 +10,8 @@ anyhow = "1.0"
base64 = "0.13"
bounce-classify = {path="../bounce-classify"}
chrono = {version="0.4", default-features=false, features=["serde", "std"]}
mailparse = "0.14"
#message = {path="../message"}
mailparsing = {path="../mailparsing"}
rfc5321 = {path="../rfc5321"}
serde = {version="1.0", features=["derive"]}
serde_json = "1.0"
+41 -41
View File
@@ -1,41 +1,41 @@
Date: Thu, 7 Jul 1994 17:16:05 -0400
From: Mail Delivery Subsystem <MAILER-DAEMON@CS.UTK.EDU>
Message-Id: <199407072116.RAA14128@CS.UTK.EDU>
Subject: Returned mail: Cannot send message for 5 days
To: <owner-info-mime@cs.utk.edu>
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery- status;
boundary="RAA14128.773615765/CS.UTK.EDU"
--RAA14128.773615765/CS.UTK.EDU
The original message was received at Sat, 2 Jul 1994 17:10:28 -0400
from root@localhost
----- The following addresses had delivery problems -----
<louisl@larry.slip.umd.edu> (unrecoverable error)
----- Transcript of session follows -----
<louisl@larry.slip.umd.edu>... Deferred: Connection timed out
with larry.slip.umd.edu.
Message could not be delivered for 5 days
Message will be deleted from queue
--RAA14128.773615765/CS.UTK.EDU
content-type: message/delivery-status
Reporting-MTA: dns; cs.utk.edu
Original-Recipient: rfc822;louisl@larry.slip.umd.edu
Final-Recipient: rfc822;louisl@larry.slip.umd.edu
Action: failed
Status: 4.0.0
Diagnostic-Code: smtp; 426 connection timed out
Last-Attempt-Date: Thu, 7 Jul 1994 17:15:49 -0400
--RAA14128.773615765/CS.UTK.EDU
content-type: message/rfc822
[original message goes here]
--RAA14128.773615765/CS.UTK.EDU--
Date: Thu, 7 Jul 1994 17:16:05 -0400
From: Mail Delivery Subsystem <MAILER-DAEMON@CS.UTK.EDU>
Message-Id: <199407072116.RAA14128@CS.UTK.EDU>
Subject: Returned mail: Cannot send message for 5 days
To: <owner-info-mime@cs.utk.edu>
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery-status;
boundary="RAA14128.773615765/CS.UTK.EDU"
--RAA14128.773615765/CS.UTK.EDU
The original message was received at Sat, 2 Jul 1994 17:10:28 -0400
from root@localhost
----- The following addresses had delivery problems -----
<louisl@larry.slip.umd.edu> (unrecoverable error)
----- Transcript of session follows -----
<louisl@larry.slip.umd.edu>... Deferred: Connection timed out
with larry.slip.umd.edu.
Message could not be delivered for 5 days
Message will be deleted from queue
--RAA14128.773615765/CS.UTK.EDU
content-type: message/delivery-status
Reporting-MTA: dns; cs.utk.edu
Original-Recipient: rfc822;louisl@larry.slip.umd.edu
Final-Recipient: rfc822;louisl@larry.slip.umd.edu
Action: failed
Status: 4.0.0
Diagnostic-Code: smtp; 426 connection timed out
Last-Attempt-Date: Thu, 7 Jul 1994 17:15:49 -0400
--RAA14128.773615765/CS.UTK.EDU
content-type: message/rfc822
[original message goes here]
--RAA14128.773615765/CS.UTK.EDU--
+24 -15
View File
@@ -5,7 +5,7 @@ use crate::rfc5965::{
};
use anyhow::{anyhow, Context};
use chrono::{DateTime, Utc};
use mailparse::{parse_headers, parse_mail, ParsedMail};
use mailparsing::MimePart;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
@@ -226,29 +226,38 @@ pub struct Report {
pub original_message: Option<String>,
}
pub(crate) fn content_type(part: &MimePart) -> Option<String> {
let ct = part.headers().content_type().ok()??;
Some(ct.value)
}
impl Report {
pub fn parse(input: &[u8]) -> anyhow::Result<Option<Self>> {
let mail = parse_mail(input)?;
let mail = MimePart::parse(input).with_context(|| {
format!(
"Report::parse top; input is {:?}",
String::from_utf8_lossy(input)
)
})?;
if mail.ctype.mimetype != "multipart/report" {
if content_type(&mail).as_deref() != Some("multipart/report") {
return Ok(None);
}
let mut original_message = None;
for part in &mail.subparts {
if part.ctype.mimetype == "message/rfc822"
|| part.ctype.mimetype == "text/rfc822-headers"
{
let (_headers, offset) = parse_headers(part.raw_bytes)?;
original_message =
Some(String::from_utf8_lossy(&part.raw_bytes[offset..]).replace("\r\n", "\n"));
for part in mail.child_parts() {
let ct = content_type(part);
let ct = ct.as_deref();
if ct == Some("message/rfc822") || ct == Some("text/rfc822-headers") {
original_message = Some(part.raw_body().replace("\r\n", "\n"));
}
}
for part in &mail.subparts {
if part.ctype.mimetype == "message/delivery-status"
|| part.ctype.mimetype == "message/global-delivery-status"
for part in mail.child_parts() {
let ct = content_type(part);
let ct = ct.as_deref();
if ct == Some("message/delivery-status") || ct == Some("message/global-delivery-status")
{
return Ok(Some(Self::parse_inner(part, original_message)?));
}
@@ -257,8 +266,8 @@ impl Report {
anyhow::bail!("delivery-status part missing");
}
fn parse_inner(part: &ParsedMail, original_message: Option<String>) -> anyhow::Result<Self> {
let body = part.get_body()?;
fn parse_inner(part: &MimePart, original_message: Option<String>) -> anyhow::Result<Self> {
let body = part.raw_body();
let body = body.replace("\r\n", "\n");
let mut parts = body.trim().split("\n\n");
+32 -27
View File
@@ -1,8 +1,8 @@
//! ARF reports
use crate::rfc3464::RemoteMta;
use crate::rfc3464::{content_type, RemoteMta};
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use mailparse::{parse_headers, parse_mail, ParsedMail};
use mailparsing::{Header, HeaderParseResult, MimePart};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
@@ -43,34 +43,37 @@ pub struct ARFReport {
impl ARFReport {
pub fn parse(input: &[u8]) -> anyhow::Result<Option<Self>> {
let mail = parse_mail(input)?;
let mail = MimePart::parse(input)?;
let ct = mail.headers().content_type()?;
let ct = match ct {
None => return Ok(None),
Some(ct) => ct,
};
if mail.ctype.mimetype != "multipart/report" {
if ct.value != "multipart/report" {
return Ok(None);
}
if mail.ctype.params.get("report-type").map(|s| s.as_str()) != Some("feedback-report") {
if ct.get("report-type").as_deref() != Some("feedback-report") {
return Ok(None);
}
let mut original_message = None;
let mut supplemental_trace = None;
for part in &mail.subparts {
if part.ctype.mimetype == "message/rfc822"
|| part.ctype.mimetype == "text/rfc822-headers"
{
let (_headers, offset) = parse_headers(part.raw_bytes)?;
let bytes = &part.raw_bytes[offset..];
if let Ok((headers, _)) = parse_headers(bytes) {
for part in mail.child_parts() {
let ct = content_type(part);
let ct = ct.as_deref();
if ct == Some("message/rfc822") || ct == Some("text/rfc822-headers") {
if let Ok(HeaderParseResult { headers, .. }) =
Header::parse_headers(part.raw_body())
{
// Look for x-headers that might be our supplemental trace headers
for hdr in headers {
if !(hdr.get_key_ref().starts_with("X-")
|| hdr.get_key_ref().starts_with("x-"))
{
for hdr in headers.iter() {
if !(hdr.get_name().starts_with("X-") || hdr.get_name().starts_with("x-")) {
continue;
}
if let Ok(decoded) = base64::decode(hdr.get_value_raw()) {
if let Ok(decoded) = base64::decode(hdr.get_raw_value()) {
#[derive(Deserialize)]
struct Wrap {
#[serde(rename = "_@_")]
@@ -91,12 +94,14 @@ impl ARFReport {
}
}
original_message = Some(String::from_utf8_lossy(bytes).replace("\r\n", "\n"));
original_message = Some(part.raw_body().replace("\r\n", "\n"));
}
}
for part in &mail.subparts {
if part.ctype.mimetype == "message/feedback-report" {
for part in mail.child_parts() {
let ct = content_type(part);
let ct = ct.as_deref();
if ct == Some("message/feedback-report") {
return Ok(Some(Self::parse_inner(
part,
original_message,
@@ -109,11 +114,11 @@ impl ARFReport {
}
fn parse_inner(
part: &ParsedMail,
part: &MimePart,
original_message: Option<String>,
supplemental_trace: Option<serde_json::Value>,
) -> anyhow::Result<Self> {
let body = part.get_body()?;
let body = part.raw_body();
let mut extensions = extract_headers(body.as_bytes())?;
let feedback_type = extract_single_req("feedback-type", &mut extensions)?;
@@ -153,16 +158,16 @@ impl ARFReport {
}
pub(crate) fn extract_headers(part: &[u8]) -> anyhow::Result<HashMap<String, Vec<String>>> {
let (headers, _) = parse_headers(part)?;
let HeaderParseResult { headers, .. } = Header::parse_headers(part)?;
let mut extensions = HashMap::new();
for hdr in headers {
let name = hdr.get_key_ref().to_ascii_lowercase();
for hdr in headers.iter() {
let name = hdr.get_name().to_ascii_lowercase();
extensions
.entry(name)
.or_insert_with(|| vec![])
.push(hdr.get_value_utf8()?);
.push(hdr.as_unstructured()?);
}
Ok(extensions)
}
+7 -7
View File
@@ -202,13 +202,6 @@ impl<'a> Header<'a> {
while idx < header_block.len() {
let b = header_block[idx];
if headers.is_empty() {
if b.is_ascii_whitespace() {
return Err(MailParsingError::HeaderParse(
"header block must not start with spaces".to_string(),
));
}
}
if b == b'\n' {
// LF: End of header block
idx += 1;
@@ -225,6 +218,13 @@ impl<'a> Header<'a> {
"lone CR in header".to_string(),
));
}
if headers.is_empty() {
if b.is_ascii_whitespace() {
return Err(MailParsingError::HeaderParse(
"header block must not start with spaces".to_string(),
));
}
}
let (header, next) = Self::parse(header_block.slice(idx..header_block.len()))?;
overall_conformance |= header.conformance;
headers.push(header);
+13 -3
View File
@@ -131,12 +131,21 @@ impl<'a> MimePart<'a> {
memchr::memchr(b'\n', &raw_body.as_bytes()[boundary_end..])
.map(|p| p + boundary_end + 1)
{
let part_end = iter.next().unwrap_or(raw_body.len());
let part_end = iter
.next()
.map(|p| {
// P is the newline; we want to include it in the raw
// bytes for this part, so look beyond it
p + 1
})
.unwrap_or(raw_body.len());
let child = Self::parse_impl(raw_body.slice(part_start..part_end), false)?;
self.parts.push(child);
boundary_end = part_end + boundary.len();
boundary_end = part_end -
1 /* newline we adjusted for when assigning part_end */
+ boundary.len();
if boundary_end + 2 > raw_body.len()
|| &raw_body.as_bytes()[boundary_end..boundary_end + 2] == b"--"
{
@@ -352,7 +361,8 @@ Ok(
r#"
Ok(
Text(
"This is the plaintext version, in utf-8. Proof by Euro: €",
"This is the plaintext version, in utf-8. Proof by Euro: €\r
",
),
)
"#