diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index ef7e9b140..b3322c1a8 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -13,9 +13,9 @@ use crate::dc_tools::*; use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer}; use crate::error::{bail, ensure, format_err, Result}; use crate::events::EventType; -use crate::headerdef::HeaderDef; +use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::job::{self, Action}; -use crate::message::{self, MessageState, MessengerMessage, MsgId}; +use crate::message::{self, rfc724_mid_exists, Message, MessageState, MessengerMessage, MsgId}; use crate::mimeparser::*; use crate::param::*; use crate::peerstate::*; @@ -370,10 +370,15 @@ async fn add_parts( return Ok(()); } + let parent = get_parent_message(context, mime_parser).await?; + let mut is_dc_message = if mime_parser.has_chat_version() { MessengerMessage::Yes - } else if is_reply_to_messenger_message(context, mime_parser).await { - MessengerMessage::Reply + } else if let Some(parent) = &parent { + match parent.is_dc_message { + MessengerMessage::No => MessengerMessage::No, + MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply, + } } else { MessengerMessage::No }; @@ -514,7 +519,7 @@ async fn add_parts( if Blocked::Not == create_blocked { chat_id.unblock(context).await; chat_id_blocked = Blocked::Not; - } else if is_reply_to_known_message(context, mime_parser).await { + } else if get_parent_message(context, mime_parser).await?.is_some() { // we do not want any chat to be created implicitly. Because of the origin-scale-up, // the contact requests will pop up and this should be just fine. Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo).await; @@ -1811,102 +1816,63 @@ fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef) { } } -async fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bool { - /* check if the message is a reply to a known message; the replies are identified by the Message-ID from - `In-Reply-To`/`References:` (to support non-Delta-Clients) */ +/// Given a list of Message-IDs, returns the latest message found in the database. +async fn get_rfc724_mid_in_list(context: &Context, mid_list: &str) -> Result> { + if mid_list.is_empty() { + return Ok(None); + } + + if let Ok(ids) = parse_message_ids(mid_list) { + for id in ids.iter().rev() { + if let Some((_, _, msg_id)) = rfc724_mid_exists(context, id).await? { + return Ok(Some(Message::load_from_db(context, msg_id).await?)); + } + } + } + + Ok(None) +} + +/// Returns the last message referenced from References: header found in the database. +/// +/// If none found, tries In-Reply-To: as a fallback for classic MUAs that don't set the +/// References: header. +async fn get_parent_message( + context: &Context, + mime_parser: &MimeMessage, +) -> Result> { + if let Some(field) = mime_parser.get(HeaderDef::References) { + if let Some(msg) = get_rfc724_mid_in_list(context, &field).await? { + return Ok(Some(msg)); + } + } if let Some(field) = mime_parser.get(HeaderDef::InReplyTo) { - if is_known_rfc724_mid_in_list(context, &field).await { - return true; + if let Some(msg) = get_rfc724_mid_in_list(context, &field).await? { + return Ok(Some(msg)); } } - if let Some(field) = mime_parser.get(HeaderDef::References) { - if is_known_rfc724_mid_in_list(context, &field).await { - return true; - } - } - - false + Ok(None) } -async fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { - if mid_list.is_empty() { - return false; - } - - if let Ok(ids) = parse_message_ids(mid_list) { - for id in ids.iter() { - if is_known_rfc724_mid(context, id).await { - return true; - } +pub(crate) async fn get_prefetch_parent_message( + context: &Context, + headers: &[mailparse::MailHeader<'_>], +) -> Result> { + if let Some(field) = headers.get_header_value(HeaderDef::References) { + if let Some(msg) = get_rfc724_mid_in_list(context, &field).await? { + return Ok(Some(msg)); } } - false -} - -/// Check if a message is a reply to a known message (messenger or non-messenger). -async fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { - let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>'); - - context - .sql - .exists( - "SELECT m.id FROM msgs m \ - LEFT JOIN chats c ON m.chat_id=c.id \ - WHERE m.rfc724_mid=? \ - AND m.chat_id>9 AND c.blocked=0;", - paramsv![rfc724_mid], - ) - .await - .unwrap_or_default() -} - -/// Checks if the message defined by mime_parser references a message send by us from Delta Chat. -/// This is similar to is_reply_to_known_message() but -/// - checks also if any of the referenced IDs are send by a messenger -/// - it is okay, if the referenced messages are moved to trash here -/// - no check for the Chat-* headers (function is only called if it is no messenger message itself) -async fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -> bool { - if let Some(value) = mime_parser.get(HeaderDef::InReplyTo) { - if is_msgrmsg_rfc724_mid_in_list(context, &value).await { - return true; + if let Some(field) = headers.get_header_value(HeaderDef::InReplyTo) { + if let Some(msg) = get_rfc724_mid_in_list(context, &field).await? { + return Ok(Some(msg)); } } - if let Some(value) = mime_parser.get(HeaderDef::References) { - if is_msgrmsg_rfc724_mid_in_list(context, &value).await { - return true; - } - } - - false -} - -pub(crate) async fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { - if let Ok(ids) = parse_message_ids(mid_list) { - for id in ids.iter() { - if is_msgrmsg_rfc724_mid(context, id).await { - return true; - } - } - } - false -} - -/// Check if a message is a reply to any messenger message. -async fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { - let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>'); - - context - .sql - .exists( - "SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;", - paramsv![rfc724_mid], - ) - .await - .unwrap_or_default() + Ok(None) } async fn dc_add_or_lookup_contacts_by_address_list( @@ -2032,38 +1998,6 @@ mod tests { ); } - #[async_std::test] - async fn test_is_known_rfc724_mid() { - let t = TestContext::new().await; - let mut msg = Message::new(Viewtype::Text); - msg.text = Some("first message".to_string()); - let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg)) - .await - .unwrap(); - let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap(); - - // Message-IDs may or may not be surrounded by angle brackets - assert!(is_known_rfc724_mid(&t.ctx, format!("<{}>", msg.rfc724_mid).as_str()).await); - assert!(is_known_rfc724_mid(&t.ctx, &msg.rfc724_mid).await); - assert!(!is_known_rfc724_mid(&t.ctx, "nonexistant@message.id").await); - } - - #[async_std::test] - async fn test_is_msgrmsg_rfc724_mid() { - let t = TestContext::new().await; - let mut msg = Message::new(Viewtype::Text); - msg.text = Some("first message".to_string()); - let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg)) - .await - .unwrap(); - let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap(); - - // Message-IDs may or may not be surrounded by angle brackets - assert!(is_msgrmsg_rfc724_mid(&t.ctx, format!("<{}>", msg.rfc724_mid).as_str()).await); - assert!(is_msgrmsg_rfc724_mid(&t.ctx, &msg.rfc724_mid).await); - assert!(!is_msgrmsg_rfc724_mid(&t.ctx, "nonexistant@message.id").await); - } - static MSGRMSG: &[u8] = b"From: Bob \n\ To: alice@example.com\n\ Chat-Version: 1.0\n\ diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 930f1362f..a8d55e59a 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -16,7 +16,7 @@ use num_traits::FromPrimitive; use crate::constants::*; use crate::context::Context; -use crate::dc_receive_imf::{from_field_to_contact_id, is_msgrmsg_rfc724_mid_in_list}; +use crate::dc_receive_imf::{from_field_to_contact_id, get_prefetch_parent_message}; use crate::error::{bail, format_err, Result}; use crate::events::EventType; use crate::headerdef::{HeaderDef, HeaderDefMap}; @@ -1556,25 +1556,6 @@ fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result } } -async fn prefetch_is_reply_to_chat_message( - context: &Context, - headers: &[mailparse::MailHeader<'_>], -) -> bool { - if let Some(value) = headers.get_header_value(HeaderDef::InReplyTo) { - if is_msgrmsg_rfc724_mid_in_list(context, &value).await { - return true; - } - } - - if let Some(value) = headers.get_header_value(HeaderDef::References) { - if is_msgrmsg_rfc724_mid_in_list(context, &value).await { - return true; - } - } - - false -} - pub(crate) async fn prefetch_should_download( context: &Context, headers: &[mailparse::MailHeader<'_>], @@ -1593,7 +1574,9 @@ pub(crate) async fn prefetch_should_download( } let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion).is_some(); - let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers).await; + let is_reply_to_chat_message = get_prefetch_parent_message(context, headers) + .await? + .is_some(); let maybe_ndn = if let Some(from) = headers.get_header_value(HeaderDef::From_) { let from = from.to_ascii_lowercase(); diff --git a/src/message.rs b/src/message.rs index 39232ad26..072737853 100644 --- a/src/message.rs +++ b/src/message.rs @@ -787,11 +787,8 @@ impl Message { pub async fn quoted_message(&self, context: &Context) -> Result, Error> { if self.param.get(Param::Quote).is_some() { if let Some(in_reply_to) = &self.in_reply_to { - let rfc724_mid = in_reply_to.trim_start_matches('<').trim_end_matches('>'); - if !rfc724_mid.is_empty() { - if let Some((_, _, msg_id)) = rfc724_mid_exists(context, rfc724_mid).await? { - return Ok(Some(Message::load_from_db(context, msg_id).await?)); - } + if let Some((_, _, msg_id)) = rfc724_mid_exists(context, in_reply_to).await? { + return Ok(Some(Message::load_from_db(context, msg_id).await?)); } } } @@ -1818,6 +1815,7 @@ pub(crate) async fn rfc724_mid_exists( context: &Context, rfc724_mid: &str, ) -> Result, Error> { + let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>'); if rfc724_mid.is_empty() { warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists"); return Ok(None);