simplify msg header decoding

This commit is contained in:
Clément DOUIN 2022-03-12 17:01:05 +01:00
parent f9bed5f3c2
commit 1d969a0d3a
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
2 changed files with 27 additions and 34 deletions

View file

@ -56,7 +56,7 @@ macro_rules! make_account_config {
/// Represents the text/plain format as defined in the /// Represents the text/plain format as defined in the
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt)
pub format: Option<Format>, pub format: Option<Format>,
/// Overrides the default headers displayed at the top of /// Represents the default headers displayed at the top of
/// the read message. /// the read message.
#[serde(default)] #[serde(default)]
pub read_headers: Vec<String>, pub read_headers: Vec<String>,

View file

@ -4,7 +4,7 @@ use chrono::{DateTime, FixedOffset};
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use html_escape; use html_escape;
use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart}; use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart};
use log::{debug, info, trace, warn}; use log::{info, trace, warn};
use regex::Regex; use regex::Regex;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@ -31,6 +31,8 @@ use crate::{
}, },
}; };
const DATE_TIME_FORMAT: &str = "%d-%b-%Y %H:%M:%S %z";
/// Representation of a message. /// Representation of a message.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Msg { pub struct Msg {
@ -73,8 +75,9 @@ impl Msg {
.collect() .collect()
} }
/// Folds string body from all plain text parts into a single string body. If no plain text /// Folds string body from all plain text parts into a single
/// parts are found, HTML parts are used instead. The result is sanitized (all HTML markup is /// string body. If no plain text parts are found, HTML parts are
/// used instead. The result is sanitized (all HTML markup is
/// removed). /// removed).
pub fn fold_text_plain_parts(&self) -> String { pub fn fold_text_plain_parts(&self) -> String {
let (plain, html) = self.parts.iter().fold( let (plain, html) = self.parts.iter().fold(
@ -142,7 +145,8 @@ impl Msg {
} }
} }
/// Fold string body from all HTML parts into a single string body. /// Fold string body from all HTML parts into a single string
/// body.
fn fold_text_html_parts(&self) -> String { fn fold_text_html_parts(&self) -> String {
let text_parts = self let text_parts = self
.parts .parts
@ -160,8 +164,9 @@ impl Msg {
text_parts text_parts
} }
/// Fold string body from all text parts into a single string body. The mime allows users to /// Fold string body from all text parts into a single string
/// choose between plain text parts and html text parts. /// body. The mime allows users to choose between plain text parts
/// and html text parts.
pub fn fold_text_parts(&self, text_mime: &str) -> String { pub fn fold_text_parts(&self, text_mime: &str) -> String {
if text_mime == "html" { if text_mime == "html" {
self.fold_text_html_parts() self.fold_text_html_parts()
@ -651,42 +656,32 @@ impl Msg {
parsed_mail: mailparse::ParsedMail<'_>, parsed_mail: mailparse::ParsedMail<'_>,
config: &AccountConfig, config: &AccountConfig,
) -> Result<Self> { ) -> Result<Self> {
info!("begin: building message from parsed mail"); trace!(">> build message from parsed mail");
trace!("parsed mail: {:?}", parsed_mail); trace!("parsed mail: {:?}", parsed_mail);
let mut msg = Msg::default(); let mut msg = Msg::default();
debug!("parsing headers");
for header in parsed_mail.get_headers() { for header in parsed_mail.get_headers() {
trace!(">> parse header {:?}", header);
let key = header.get_key(); let key = header.get_key();
debug!("header key: {:?}", key); trace!("header key: {:?}", key);
let val = header.get_value(); let val = header.get_value();
let val = String::from_utf8(header.get_value_raw().to_vec()) trace!("header value: {:?}", val);
.map(|val| val.trim().to_string())
.context(format!(
"cannot decode value {:?} from header {:?}",
key, val
))?;
debug!("header value: {:?}", val);
match key.to_lowercase().as_str() { match key.to_lowercase().as_str() {
"message-id" => msg.message_id = Some(val), "message-id" => msg.message_id = Some(val),
"in-reply-to" => msg.in_reply_to = Some(val), "in-reply-to" => msg.in_reply_to = Some(val),
"subject" => { "subject" => {
msg.subject = rfc2047_decoder::decode(val.as_bytes())?; msg.subject = val;
} }
"date" => { "date" => {
// TODO: use date format instead msg.date = DateTime::parse_from_str(&val, DATE_TIME_FORMAT)
// https://github.com/jonhoo/rust-imap/blob/afbc5118f251da4e3f6a1e560e749c0700020b54/src/types/fetch.rs#L16 .map_err(|err| {
msg.date = DateTime::parse_from_rfc2822( warn!("cannot parse message date {:?}, skipping it", val);
val.split_at(val.find(" (").unwrap_or_else(|| val.len())).0, err
) })
.map_err(|err| { .ok();
warn!("cannot parse message date {:?}, skipping it", val);
err
})
.ok();
} }
"from" => { "from" => {
msg.from = from_slice_to_addrs(val) msg.from = from_slice_to_addrs(val)
@ -709,19 +704,17 @@ impl Msg {
.context(format!("cannot parse header {:?}", key))? .context(format!("cannot parse header {:?}", key))?
} }
key => { key => {
msg.headers.insert( msg.headers.insert(key.to_lowercase(), val);
key.to_lowercase(),
rfc2047_decoder::decode(val.as_bytes()).unwrap_or(val),
);
} }
} }
trace!("<< parse header");
} }
msg.parts = Parts::from_parsed_mail(config, &parsed_mail) msg.parts = Parts::from_parsed_mail(config, &parsed_mail)
.context("cannot parsed message mime parts")?; .context("cannot parsed message mime parts")?;
trace!("message: {:?}", msg); trace!("message: {:?}", msg);
info!("end: building message from parsed mail"); info!("<< build message from parsed mail");
Ok(msg) Ok(msg)
} }