mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-09 20:12:13 +00:00
log-types: migrate mailparse -> mailparsing
This commit is contained in:
Generated
+1
-1
@@ -2323,7 +2323,7 @@ dependencies = [
|
||||
"bounce-classify",
|
||||
"chrono",
|
||||
"k9",
|
||||
"mailparse",
|
||||
"mailparsing",
|
||||
"rfc5321",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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--
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
",
|
||||
),
|
||||
)
|
||||
"#
|
||||
|
||||
Reference in New Issue
Block a user