From 9821a9888e70569b5d3b1dcd9220968d5ad76f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Mon, 27 Mar 2023 22:46:17 +0200 Subject: [PATCH] Replace the mailparse crate by a custom header pseudo-parser --- Cargo.toml | 1 - src/main.rs | 1 + src/message.rs | 48 +++++++++++++++---------------- src/parsed_message.rs | 66 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 src/parsed_message.rs diff --git a/Cargo.toml b/Cargo.toml index 7ea5d3d..13e60ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,4 @@ publish = false clap = { version = "4.1.13", default-features = false, features = ["std", "derive"] } env_logger = { version = "0.10.0", default-features = false } log = { version = "0.4.17", default-features = false } -mailparse = { version = "0.14.0", default-features = false } nom = { version = "7.1.3", default-features = false } diff --git a/src/main.rs b/src/main.rs index 0b2380a..0d61563 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod entry; mod handshake; mod logs; mod message; +mod parsed_message; mod stdin_reader; use algorithm::Algorithm; diff --git a/src/message.rs b/src/message.rs index 2276f94..b79a56f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,6 +1,6 @@ use crate::config::Config; use crate::entry::Entry; -use mailparse::parse_mail; +use crate::parsed_message::ParsedMessage; use std::io::{BufWriter, Write}; pub const RETURN_SEP: &[u8] = b"|"; @@ -52,35 +52,35 @@ impl Message { } pub fn sign_and_return(&self, cnf: &Config) { - log::trace!("content:\n{}", crate::display_bytes!(&self.content)); - match parse_mail(&self.content) { + log::trace!("content: {}", crate::display_bytes!(&self.content)); + match ParsedMessage::from_bytes(&self.content) { Ok(parsed_msg) => { log::trace!("mail parsed"); - for h in parsed_msg.get_headers() { - log::trace!("{:?}", h); - } - match self.get_body() { - Some(body) => { - log::trace!("MailBody:\n{}", crate::display_bytes!(body)); - // TODO: sign the message using DKIM - } - None => { - log::error!("{}: unable to find the body", self.session_id); - } + for h in &parsed_msg.headers { + log::trace!( + "ParsedMessage: header: raw: {}", + crate::display_bytes!(h.raw) + ); + log::trace!( + "ParsedMessage: header: name: {}", + crate::display_bytes!(h.name) + ); + log::trace!( + "ParsedMessage: header: value: {}", + crate::display_bytes!(h.value) + ); } + log::trace!( + "ParsedMessage: body: {}", + crate::display_bytes!(parsed_msg.body) + ); + // TODO: sign the message using DKIM } - Err(e) => { - log::error!("{}: unable to parse message: {e}", self.session_id); + Err(_) => { + log::error!("{}: unable to parse message", self.session_id); } - }; - self.print_msg(); - } - - fn get_body(&self) -> Option<&[u8]> { - match self.content.windows(4).position(|w| w == b"\r\n\r\n") { - Some(body_index) => Some(&self.content[body_index + 4..]), - None => None, } + self.print_msg(); } fn print_msg(&self) { diff --git a/src/parsed_message.rs b/src/parsed_message.rs new file mode 100644 index 0000000..336234a --- /dev/null +++ b/src/parsed_message.rs @@ -0,0 +1,66 @@ +pub struct ParsedMessage<'a> { + pub headers: Vec>, + pub body: &'a [u8], +} + +impl<'a> ParsedMessage<'a> { + pub fn from_bytes(data: &'a [u8]) -> Result { + let (mut raw_headers, body) = match data.windows(4).position(|w| w == b"\r\n\r\n") { + Some(body_index) => (&data[..body_index + 2], &data[body_index + 4..]), + None => return Err(()), + }; + let mut headers = Vec::with_capacity(128); + while !raw_headers.is_empty() { + let end_index = header_end_pos(raw_headers)?; + let h = ParsedHeader::from_bytes(&raw_headers[..end_index])?; + headers.push(h); + raw_headers = &raw_headers[end_index..]; + } + headers.shrink_to_fit(); + Ok(Self { headers, body }) + } +} + +fn is_wsp(c: u8) -> bool { + c == b' ' || c == b'\t' +} + +fn header_end_pos(data: &[u8]) -> Result { + let mut ret = 0; + let max_len = data.len(); + loop { + ret += data[ret..] + .windows(2) + .position(|w| w == b"\r\n") + .ok_or(())? + 2; + if ret == max_len { + return Ok(ret); + } + if !is_wsp(data[ret]) { + return Ok(ret); + } + } +} + +pub struct ParsedHeader<'a> { + pub name: &'a [u8], + pub name_lower: String, + pub value: &'a [u8], + pub raw: &'a [u8], +} + +impl<'a> ParsedHeader<'a> { + fn from_bytes(data: &'a [u8]) -> Result { + let colon_pos = data.iter().position(|&w| w == b':').ok_or(())?; + let name = &data[..colon_pos]; + let value = &data[colon_pos + 1..]; + Ok(Self { + name, + name_lower: String::from_utf8(name.to_vec()) + .map_err(|_| ())? + .to_lowercase(), + value, + raw: data, + }) + } +}