Don't directly download messages from the Spam folder (#3015)

fix #3007

My approach is:

We don't download any messages from the spam folder anymore, and only download them if they were moved out. This means that is-it-spam logic only resides in spam_target_folder(). This has some implications, see the comments.
This commit is contained in:
Hocuri
2022-02-10 09:06:22 +01:00
committed by GitHub
parent 6c6d47c89c
commit 34f5510f1f
4 changed files with 69 additions and 107 deletions

View File

@@ -193,7 +193,6 @@ pub(crate) async fn dc_receive_imf_inner(
&mut mime_parser,
imf_raw,
incoming,
incoming_origin,
server_folder,
&to_ids,
&rfc724_mid,
@@ -415,7 +414,6 @@ async fn add_parts(
mime_parser: &mut MimeMessage,
imf_raw: &[u8],
incoming: bool,
incoming_origin: Origin,
server_folder: &str,
to_ids: &[u32],
rfc724_mid: &str,
@@ -432,7 +430,6 @@ async fn add_parts(
) -> Result<ReceivedMsg> {
let mut chat_id = None;
let mut chat_id_blocked = Blocked::Not;
let mut incoming_origin = incoming_origin;
let parent = get_parent_message(context, mime_parser).await?;
@@ -676,7 +673,6 @@ async fn add_parts(
if chat_id_blocked != Blocked::Not {
if chat_id_blocked != create_blocked {
chat_id.set_blocked(context, create_blocked).await?;
chat_id_blocked = create_blocked;
}
if create_blocked == Blocked::Request && parent.is_some() {
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
@@ -687,9 +683,6 @@ async fn add_parts(
context,
"Message is a reply to a known message, mark sender as known.",
);
if !incoming_origin.is_known() {
incoming_origin = Origin::IncomingReplyTo;
}
}
}
}
@@ -701,15 +694,6 @@ async fn add_parts(
} else {
MessageState::InFresh
};
let is_spam = (chat_id_blocked == Blocked::Request)
&& !incoming_origin.is_known()
&& (is_dc_message == MessengerMessage::No)
&& context.is_spam_folder(server_folder).await?;
if is_spam {
chat_id = Some(DC_CHAT_ID_TRASH);
info!(context, "Message is probably spam (TRASH)");
}
} else {
// Outgoing
@@ -2363,12 +2347,10 @@ fn dc_create_incoming_rfc724_mid(mime: &MimeMessage) -> String {
#[cfg(test)]
mod tests {
use chat::get_chat_contacts;
use mailparse::MailHeaderMap;
use super::*;
use crate::chat::get_chat_contacts;
use crate::chat::{get_chat_msgs, ChatItem, ChatVisibility};
use crate::chatlist::Chatlist;
use crate::constants::{DC_CONTACT_ID_INFO, DC_GCL_NO_SPECIALS};
@@ -2977,7 +2959,7 @@ mod tests {
&headers,
"some-other-message-id",
std::iter::empty(),
ShowEmails::Off
ShowEmails::Off,
)
.await
.unwrap());
@@ -4298,76 +4280,6 @@ YEAAAAAA!.
assert_eq!(msg.text.unwrap(), "Reply");
}
#[async_std::test]
async fn test_dont_show_spam() {
async fn is_shown(t: &TestContext, raw: &[u8], server_folder: &str) -> bool {
let mail = mailparse::parse_mail(raw).unwrap();
dc_receive_imf(t, raw, server_folder, false).await.unwrap();
t.get_last_msg().await.rfc724_mid
== mail.get_headers().get_first_value("Message-Id").unwrap()
}
let t = TestContext::new_alice().await;
t.set_config(Config::ConfiguredSpamFolder, Some("Spam"))
.await
.unwrap();
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
assert!(
is_shown(
&t,
b"Message-Id: abcd1@exmaple.com\n\
From: bob@example.org\n\
Chat-Version: 1.0\n",
"Inbox",
)
.await,
);
assert!(
is_shown(
&t,
b"Message-Id: abcd2@exmaple.com\n\
From: bob@example.org\n",
"Inbox",
)
.await,
);
assert!(
is_shown(
&t,
b"Message-Id: abcd3@exmaple.com\n\
From: bob@example.org\n\
Chat-Version: 1.0\n",
"Spam",
)
.await,
);
assert!(
// Note the `!`:
!is_shown(
&t,
b"Message-Id: abcd4@exmaple.com\n\
From: bob@example.org\n",
"Spam",
)
.await,
);
Contact::create(&t, "", "bob@example.org").await.unwrap();
assert!(
is_shown(
&t,
b"Message-Id: abcd5@exmaple.com\n\
From: bob@example.org\n",
"Spam",
)
.await,
);
}
#[async_std::test]
async fn test_dont_show_all_outgoing_msgs_in_self_chat() {
// Regression test for <https://github.com/deltachat/deltachat-android/issues/1940>:

View File

@@ -748,6 +748,11 @@ impl Imap {
// message, move it to the movebox and then download the second message before
// downloading the first one, if downloading from inbox before moving is allowed.
if folder == target
// Never download messages directly from the spam folder.
// If the sender is known, the message will be moved to the Inbox or Mvbox
// and then we download the message from there.
// Also see `spam_target_folder()`.
&& !context.is_spam_folder(folder).await?
&& prefetch_should_download(
context,
&headers,
@@ -1598,16 +1603,28 @@ impl Imap {
}
}
/// Returns target folder for a message found in the Spam folder.
async fn spam_target_folder(
async fn should_move_out_of_spam(
context: &Context,
folder: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Result<Option<Config>> {
if let Some(chat) = prefetch_get_chat(context, headers).await? {
if chat.blocked != Blocked::Not {
) -> Result<bool> {
if headers.get_header_value(HeaderDef::ChatVersion).is_some() {
// If this is a chat message (i.e. has a ChatVersion header), then this might be
// a securejoin message. We can't find out at this point as we didn't prefetch
// the SecureJoin header. So, we always move chat messages out of Spam.
// Two possibilities to change this would be:
// 1. Remove the `&& !context.is_spam_folder(folder).await?` check from
// `fetch_new_messages()`, and then let `dc_receive_imf()` check
// if it's a spam message and should be hidden.
// 2. Or add a flag to the ChatVersion header that this is a securejoin
// request, and return `true` here only if the message has this flag.
// `dc_receive_imf()` can then check if the securejoin request is valid.
return Ok(true);
}
if let Some(msg) = get_prefetch_parent_message(context, headers).await? {
if msg.chat_blocked != Blocked::Not {
// Blocked or contact request message in the spam folder, leave it there.
return Ok(None);
return Ok(false);
}
} else {
// No chat found.
@@ -1615,22 +1632,38 @@ async fn spam_target_folder(
from_field_to_contact_id(context, &mimeparser::get_from(headers), true).await?;
if blocked_contact {
// Contact is blocked, leave the message in spam.
return Ok(None);
return Ok(false);
}
if let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, from_id).await? {
if chat_id_blocked.blocked != Blocked::Not {
return Ok(None);
return Ok(false);
}
} else if from_id != DC_CONTACT_ID_SELF {
// No chat with this contact found.
return Ok(None);
return Ok(false);
}
}
Ok(true)
}
/// Returns target folder for a message found in the Spam folder.
/// If this returns None, the message will not be moved out of the
/// Spam folder, and as `fetch_new_messages()` doesn't download
/// messages from the Spam folder, the message will be ignored.
async fn spam_target_folder(
context: &Context,
folder: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Result<Option<Config>> {
if !should_move_out_of_spam(context, headers).await? {
return Ok(None);
}
if needs_move_to_mvbox(context, headers).await?
// We don't want to move the message to the inbox or sentbox where we wouldn't
// fetch it again:
// If OnlyFetchMvbox is set, we don't want to move the message to
// the inbox or sentbox where we wouldn't fetch it again:
|| context.get_config_bool(Config::OnlyFetchMvbox).await?
{
Ok(Some(Config::ConfiguredMvboxFolder))
@@ -2383,7 +2416,7 @@ mod tests {
("Spam", true, true, "DeltaChat"),
];
// These are the same as above, but all messages in Spam stay in Spam
// These are the same as above, but non-chat messages in Spam stay in Spam
const COMBINATIONS_REQUEST: &[(&str, bool, bool, &str)] = &[
("INBOX", false, false, "INBOX"),
("INBOX", false, true, "INBOX"),
@@ -2394,9 +2427,9 @@ mod tests {
("Sent", true, false, "Sent"),
("Sent", true, true, "DeltaChat"),
("Spam", false, false, "Spam"),
("Spam", false, true, "Spam"),
("Spam", false, true, "INBOX"),
("Spam", true, false, "Spam"),
("Spam", true, true, "Spam"),
("Spam", true, true, "DeltaChat"),
];
#[async_std::test]