diff --git a/Cargo.lock b/Cargo.lock index 10599284..b66d66c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2875,8 +2875,8 @@ dependencies = [ "kumo-log-types", "lazy_static", "lruttl", - "mail-parser", "mailparse", + "mailparsing", "mlua", "prometheus", "rand", diff --git a/crates/mailparsing/src/error.rs b/crates/mailparsing/src/error.rs index 58f42776..d93ab7ad 100644 --- a/crates/mailparsing/src/error.rs +++ b/crates/mailparsing/src/error.rs @@ -12,4 +12,6 @@ pub enum MailParsingError { InvalidContentTransferEncoding(String), #[error("parsing body: {0}")] BodyParse(String), + #[error("Data is not ASCII or UTF-8")] + NotAscii, } diff --git a/crates/mailparsing/src/header.rs b/crates/mailparsing/src/header.rs index ffec34bb..2cb95d79 100644 --- a/crates/mailparsing/src/header.rs +++ b/crates/mailparsing/src/header.rs @@ -3,6 +3,7 @@ use crate::rfc5322_parser::Parser; use crate::{ AddressList, MailParsingError, Mailbox, MailboxList, MimeParameters, Result, SharedString, }; +use std::convert::TryInto; bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] @@ -188,10 +189,12 @@ impl<'a> Header<'a> { Parser::parse_unstructured_header(self.get_raw_value()) } - pub fn parse_headers>>( + pub fn parse_headers>>( header_block: S, ) -> Result> { - let header_block = header_block.into(); + let header_block = header_block + .try_into() + .map_err(|_| MailParsingError::NotAscii)?; let mut headers = vec![]; let mut idx = 0; let mut overall_conformance = HeaderConformance::default(); diff --git a/crates/mailparsing/src/lib.rs b/crates/mailparsing/src/lib.rs index f8ad81c8..42127e41 100644 --- a/crates/mailparsing/src/lib.rs +++ b/crates/mailparsing/src/lib.rs @@ -10,7 +10,7 @@ pub use error::MailParsingError; pub type Result = std::result::Result; pub use header::{Header, HeaderConformance, HeaderParseResult}; -pub use headermap::HeaderMap; +pub use headermap::*; pub use mimepart::MimePart; pub use rfc5322_parser::*; pub use strings::SharedString; diff --git a/crates/mailparsing/src/mimepart.rs b/crates/mailparsing/src/mimepart.rs index 90f48d29..f775f680 100644 --- a/crates/mailparsing/src/mimepart.rs +++ b/crates/mailparsing/src/mimepart.rs @@ -2,6 +2,7 @@ use crate::header::{HeaderConformance, HeaderParseResult}; use crate::headermap::HeaderMap; use crate::{Header, MailParsingError, MimeParameters, Result, SharedString}; use charset::Charset; +use std::convert::TryInto; use std::str::FromStr; /// Define our own because data_encoding::BASE64_MIME, despite its name, @@ -80,8 +81,8 @@ impl Rfc2045Info { impl<'a> MimePart<'a> { /// Parse some data into a tree of MimeParts - pub fn parse>>(bytes: S) -> Result { - let bytes = bytes.into(); + pub fn parse>>(bytes: S) -> Result { + let bytes = bytes.try_into().map_err(|_| MailParsingError::NotAscii)?; Self::parse_impl(bytes, true) } diff --git a/crates/mailparsing/src/rfc5322_parser.rs b/crates/mailparsing/src/rfc5322_parser.rs index 4abf78a8..e88d1264 100644 --- a/crates/mailparsing/src/rfc5322_parser.rs +++ b/crates/mailparsing/src/rfc5322_parser.rs @@ -1225,10 +1225,10 @@ pub enum Address { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct AddressList(Vec
); +pub struct AddressList(pub Vec
); #[derive(Debug, Clone, PartialEq, Eq)] -pub struct MailboxList(Vec); +pub struct MailboxList(pub Vec); #[derive(Debug, Clone, PartialEq, Eq)] pub struct Mailbox { diff --git a/crates/mailparsing/src/strings.rs b/crates/mailparsing/src/strings.rs index 406b2a45..a5decffa 100644 --- a/crates/mailparsing/src/strings.rs +++ b/crates/mailparsing/src/strings.rs @@ -120,3 +120,11 @@ impl<'a> From<&'a str> for SharedString<'a> { Self::Borrowed(s) } } + +impl<'a> TryFrom<&'a [u8]> for SharedString<'a> { + type Error = std::str::Utf8Error; + fn try_from(s: &'a [u8]) -> Result { + let s = std::str::from_utf8(s)?; + Ok(Self::Borrowed(s)) + } +} diff --git a/crates/message/Cargo.toml b/crates/message/Cargo.toml index 3830be81..2d436335 100644 --- a/crates/message/Cargo.toml +++ b/crates/message/Cargo.toml @@ -19,7 +19,7 @@ kumo-log-types = {path="../kumo-log-types"} lazy_static = "1.4" lruttl = {path="../lruttl"} mailparse = "0.14" -mail-parser = "0.8" +mailparsing = {path="../mailparsing"} mlua = {workspace=true, features=["vendored", "lua54", "async", "send", "serialize"]} prometheus = "0.13" rand = "0.8" diff --git a/crates/message/src/address.rs b/crates/message/src/address.rs index 4e2e8638..c902e399 100644 --- a/crates/message/src/address.rs +++ b/crates/message/src/address.rs @@ -1,5 +1,5 @@ use config::any_err; -use mail_parser::{Addr, Group, HeaderValue}; +use mailparsing::{Address, AddressList, EncodeHeaderValue, Mailbox}; use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods}; use rfc5321::{ForwardPath, ReversePath}; use serde::{Deserialize, Serialize}; @@ -146,6 +146,13 @@ impl HeaderAddressList { } } +impl From for HeaderAddressList { + fn from(input: AddressList) -> HeaderAddressList { + let addresses: Vec = input.0.iter().map(Into::into).collect(); + HeaderAddressList(addresses) + } +} + impl UserData for HeaderAddressList { fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("user", |_, this| { @@ -183,18 +190,22 @@ pub enum HeaderAddressEntry { Group(AddressGroup), } -impl From> for HeaderAddressEntry { - fn from(addr: Addr) -> Self { - Self::Address(addr.into()) - } -} - -impl From> for HeaderAddressEntry { - fn from(group: Group) -> Self { - Self::Group(AddressGroup { - name: group.name.map(|s| s.to_string()), - addresses: group.addresses.into_iter().map(|a| a.into()).collect(), - }) +impl From<&Address> for HeaderAddressEntry { + fn from(addr: &Address) -> HeaderAddressEntry { + match addr { + Address::Mailbox(mbox) => HeaderAddressEntry::Address(mbox.into()), + Address::Group { name, entries } => { + let addresses = entries.0.iter().map(Into::into).collect(); + HeaderAddressEntry::Group(AddressGroup { + name: if name.is_empty() { + None + } else { + Some(name.to_string()) + }, + addresses, + }) + } + } } } @@ -204,6 +215,15 @@ pub struct HeaderAddress { pub address: Option, } +impl From<&Mailbox> for HeaderAddress { + fn from(mbox: &Mailbox) -> HeaderAddress { + Self { + name: mbox.name.clone(), + address: Some(mbox.address.encode_value().to_string()), + } + } +} + impl HeaderAddress { fn user(&self) -> anyhow::Result<&str> { let (user, _domain) = self.crack_address().map_err(any_err)?; @@ -251,59 +271,8 @@ impl UserData for HeaderAddress { } } -impl From> for HeaderAddress { - fn from(addr: Addr) -> Self { - Self { - name: addr.name.map(|s| s.to_string()), - address: addr.address.map(|s| s.to_string()), - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AddressGroup { pub name: Option, pub addresses: Vec, } - -impl HeaderAddressList { - pub fn parse_header_value(data: &[u8]) -> Option { - // The parser needs a newline in the data otherwise it - // won't recognize the value - let mut fixed = data.to_vec(); - fixed.push(b'\n'); - let res = mail_parser::parsers::MessageStream::new(&fixed).parse_address(); - - let mut results = vec![]; - - match res { - HeaderValue::Address(addr) => { - results.push(addr.into()); - } - HeaderValue::AddressList(list) => { - for addr in list { - results.push(addr.into()); - } - } - HeaderValue::Group(group) => { - results.push(group.into()); - } - HeaderValue::GroupList(list) => { - for group in list { - results.push(group.into()); - } - } - HeaderValue::Text(_) - | HeaderValue::TextList(_) - | HeaderValue::DateTime(_) - | HeaderValue::ContentType(_) - | HeaderValue::Empty => {} - } - - if results.is_empty() { - None - } else { - Some(Self(results)) - } - } -} diff --git a/crates/message/src/message.rs b/crates/message/src/message.rs index 570e824c..7ccc77b8 100644 --- a/crates/message/src/message.rs +++ b/crates/message/src/message.rs @@ -9,6 +9,7 @@ use futures::FutureExt; use kumo_log_types::rfc3464::Report; use kumo_log_types::rfc5965::ARFReport; use mailparse::{MailHeader, MailHeaderMap}; +use mailparsing::HeaderParseResult; use mlua::{LuaSerdeExt, UserData, UserDataMethods}; use prometheus::IntGauge; use serde::{Deserialize, Serialize}; @@ -593,10 +594,15 @@ impl Message { header_name: &str, ) -> anyhow::Result> { let data = self.get_data(); - let (headers, _body_offset) = mailparse::parse_headers(&data)?; + let HeaderParseResult { headers, .. } = + mailparsing::Header::parse_headers(data.as_ref().as_ref())?; - match headers.get_first_header(header_name) { - Some(hdr) => Ok(HeaderAddressList::parse_header_value(hdr.get_value_raw())), + match headers.get_first(header_name) { + Some(hdr) => { + let list = hdr.as_address_list()?; + let result: HeaderAddressList = list.into(); + Ok(Some(result)) + } None => Ok(None), } }