deltachat/
receive_imf.rs

1//! Internet Message Format reception pipeline.
2
3use std::collections::{BTreeMap, BTreeSet, HashSet};
4use std::iter;
5use std::sync::LazyLock;
6
7use anyhow::{Context as _, Result, ensure};
8use data_encoding::BASE32_NOPAD;
9use deltachat_contact_tools::{
10    ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_single_line,
11};
12use iroh_gossip::proto::TopicId;
13use mailparse::SingleInfo;
14use num_traits::FromPrimitive;
15use regex::Regex;
16
17use crate::chat::{self, Chat, ChatId, ChatIdBlocked, save_broadcast_secret};
18use crate::config::Config;
19use crate::constants::{self, Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
20use crate::contact::{self, Contact, ContactId, Origin, mark_contact_id_as_verified};
21use crate::context::Context;
22use crate::debug_logging::maybe_set_logging_xdc_inner;
23use crate::download::{DownloadState, msg_is_downloaded_for};
24use crate::ephemeral::{Timer as EphemeralTimer, stock_ephemeral_timer_changed};
25use crate::events::EventType;
26use crate::headerdef::{HeaderDef, HeaderDefMap};
27use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table};
28use crate::key::{DcKey, Fingerprint};
29use crate::key::{self_fingerprint, self_fingerprint_opt};
30use crate::log::{LogExt as _, warn};
31use crate::message::{
32    self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
33};
34use crate::mimeparser::{
35    AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids,
36};
37use crate::param::{Param, Params};
38use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub};
39use crate::reaction::{Reaction, set_msg_reaction};
40use crate::rusqlite::OptionalExtension;
41use crate::securejoin::{
42    self, get_secure_join_step, handle_securejoin_handshake, observe_securejoin_on_other_device,
43};
44use crate::simplify;
45use crate::stats::STATISTICS_BOT_EMAIL;
46use crate::stock_str;
47use crate::sync::Sync::*;
48use crate::tools::{
49    self, buf_compress, normalize_text, remove_subject_prefix, validate_broadcast_secret,
50};
51use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
52use crate::{logged_debug_assert, mimeparser};
53
54/// This is the struct that is returned after receiving one email (aka MIME message).
55///
56/// One email with multiple attachments can end up as multiple chat messages, but they
57/// all have the same chat_id, state and sort_timestamp.
58#[derive(Debug)]
59pub struct ReceivedMsg {
60    /// Chat the message is assigned to.
61    pub chat_id: ChatId,
62
63    /// Received message state.
64    pub state: MessageState,
65
66    /// Whether the message is hidden.
67    pub hidden: bool,
68
69    /// Message timestamp for sorting.
70    pub sort_timestamp: i64,
71
72    /// IDs of inserted rows in messages table.
73    pub msg_ids: Vec<MsgId>,
74
75    /// Whether IMAP messages should be immediately deleted.
76    pub needs_delete_job: bool,
77}
78
79/// Decision on which kind of chat the message
80/// should be assigned in.
81///
82/// This is done before looking up contact IDs
83/// so we know in advance whether to lookup
84/// key-contacts or email address contacts.
85///
86/// Once this decision is made,
87/// it should not be changed so we
88/// don't assign the message to an encrypted
89/// group after looking up key-contacts
90/// or vice versa.
91#[derive(Debug)]
92enum ChatAssignment {
93    /// Trash the message.
94    Trash,
95
96    /// Group chat with a Group ID.
97    ///
98    /// Lookup key-contacts and
99    /// assign to encrypted group.
100    GroupChat { grpid: String },
101
102    /// Mailing list or broadcast channel.
103    ///
104    /// Mailing lists don't have members.
105    /// Broadcast channels have members
106    /// on the sender side,
107    /// but their addresses don't go into
108    /// the `To` field.
109    ///
110    /// In any case, the `To`
111    /// field should be ignored
112    /// and no contact IDs should be looked
113    /// up except the `from_id`
114    /// which may be an email address contact
115    /// or a key-contact.
116    MailingListOrBroadcast,
117
118    /// Group chat without a Group ID.
119    ///
120    /// This is not encrypted.
121    AdHocGroup,
122
123    /// Assign the message to existing chat
124    /// with a known `chat_id`.
125    ExistingChat {
126        /// ID of existing chat
127        /// which the message should be assigned to.
128        chat_id: ChatId,
129
130        /// Whether existing chat is blocked.
131        /// This is loaded together with a chat ID
132        /// reduce the number of database calls.
133        ///
134        /// We may want to unblock the chat
135        /// after adding the message there
136        /// if the chat is currently blocked.
137        chat_id_blocked: Blocked,
138    },
139
140    /// 1:1 chat with a single contact.
141    ///
142    /// The chat may be encrypted or not,
143    /// it does not matter.
144    /// It is not possible to mix
145    /// email address contacts
146    /// with key-contacts in a single 1:1 chat anyway.
147    OneOneChat,
148}
149
150/// Emulates reception of a message from the network.
151///
152/// This method returns errors on a failure to parse the mail or extract Message-ID. It's only used
153/// for tests and REPL tool, not actual message reception pipeline.
154#[cfg(any(test, feature = "internals"))]
155pub async fn receive_imf(
156    context: &Context,
157    imf_raw: &[u8],
158    seen: bool,
159) -> Result<Option<ReceivedMsg>> {
160    let mail = mailparse::parse_mail(imf_raw).context("can't parse mail")?;
161    let rfc724_mid = crate::imap::prefetch_get_message_id(&mail.headers)
162        .unwrap_or_else(crate::imap::create_message_id);
163    receive_imf_from_inbox(context, &rfc724_mid, imf_raw, seen).await
164}
165
166/// Emulates reception of a message from "INBOX".
167///
168/// Only used for tests and REPL tool, not actual message reception pipeline.
169#[cfg(any(test, feature = "internals"))]
170pub(crate) async fn receive_imf_from_inbox(
171    context: &Context,
172    rfc724_mid: &str,
173    imf_raw: &[u8],
174    seen: bool,
175) -> Result<Option<ReceivedMsg>> {
176    receive_imf_inner(context, rfc724_mid, imf_raw, seen).await
177}
178
179/// Inserts a tombstone into `msgs` table
180/// to prevent downloading the same message in the future.
181///
182/// Returns tombstone database row ID.
183async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
184    let row_id = context
185        .sql
186        .insert(
187            "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
188            (rfc724_mid, DC_CHAT_ID_TRASH),
189        )
190        .await?;
191    let msg_id = MsgId::new(u32::try_from(row_id)?);
192    Ok(msg_id)
193}
194
195async fn get_to_and_past_contact_ids(
196    context: &Context,
197    mime_parser: &MimeMessage,
198    chat_assignment: &ChatAssignment,
199    parent_message: &Option<Message>,
200    incoming_origin: Origin,
201) -> Result<(Vec<Option<ContactId>>, Vec<Option<ContactId>>)> {
202    // `None` means that the chat is encrypted,
203    // but we were not able to convert the address
204    // to key-contact, e.g.
205    // because there was no corresponding
206    // Autocrypt-Gossip header.
207    //
208    // This way we still preserve remaining
209    // number of contacts and their positions
210    // so we can match the contacts to
211    // e.g. Chat-Group-Member-Timestamps
212    // header.
213    let to_ids: Vec<Option<ContactId>>;
214    let past_ids: Vec<Option<ContactId>>;
215
216    // ID of the chat to look up the addresses in.
217    //
218    // Note that this is not necessarily the chat we want to assign the message to.
219    // In case of an outgoing private reply to a group message we may
220    // lookup the address of receipient in the list of addresses used in the group,
221    // but want to assign the message to 1:1 chat.
222    let chat_id = match chat_assignment {
223        ChatAssignment::Trash => None,
224        ChatAssignment::GroupChat { grpid } => {
225            if let Some((chat_id, _blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
226                Some(chat_id)
227            } else {
228                None
229            }
230        }
231        ChatAssignment::AdHocGroup => {
232            // If we are going to assign a message to ad hoc group,
233            // we can just convert the email addresses
234            // to e-mail address contacts and don't need a `ChatId`
235            // to lookup key-contacts.
236            None
237        }
238        ChatAssignment::ExistingChat { chat_id, .. } => Some(*chat_id),
239        ChatAssignment::MailingListOrBroadcast => None,
240        ChatAssignment::OneOneChat => {
241            if !mime_parser.incoming {
242                parent_message.as_ref().map(|m| m.chat_id)
243            } else {
244                None
245            }
246        }
247    };
248
249    let member_fingerprints = mime_parser.chat_group_member_fingerprints();
250    let to_member_fingerprints;
251    let past_member_fingerprints;
252
253    if !member_fingerprints.is_empty() {
254        if member_fingerprints.len() >= mime_parser.recipients.len() {
255            (to_member_fingerprints, past_member_fingerprints) =
256                member_fingerprints.split_at(mime_parser.recipients.len());
257        } else {
258            warn!(
259                context,
260                "Unexpected length of the fingerprint header, expected at least {}, got {}.",
261                mime_parser.recipients.len(),
262                member_fingerprints.len()
263            );
264            to_member_fingerprints = &[];
265            past_member_fingerprints = &[];
266        }
267    } else {
268        to_member_fingerprints = &[];
269        past_member_fingerprints = &[];
270    }
271
272    match chat_assignment {
273        ChatAssignment::GroupChat { .. } => {
274            to_ids = add_or_lookup_key_contacts(
275                context,
276                &mime_parser.recipients,
277                &mime_parser.gossiped_keys,
278                to_member_fingerprints,
279                Origin::Hidden,
280            )
281            .await?;
282
283            if let Some(chat_id) = chat_id {
284                past_ids = lookup_key_contacts_by_address_list(
285                    context,
286                    &mime_parser.past_members,
287                    past_member_fingerprints,
288                    Some(chat_id),
289                )
290                .await?;
291            } else {
292                past_ids = add_or_lookup_key_contacts(
293                    context,
294                    &mime_parser.past_members,
295                    &mime_parser.gossiped_keys,
296                    past_member_fingerprints,
297                    Origin::Hidden,
298                )
299                .await?;
300            }
301        }
302        ChatAssignment::Trash => {
303            to_ids = Vec::new();
304            past_ids = Vec::new();
305        }
306        ChatAssignment::ExistingChat { chat_id, .. } => {
307            let chat = Chat::load_from_db(context, *chat_id).await?;
308            if chat.is_encrypted(context).await? {
309                to_ids = add_or_lookup_key_contacts(
310                    context,
311                    &mime_parser.recipients,
312                    &mime_parser.gossiped_keys,
313                    to_member_fingerprints,
314                    Origin::Hidden,
315                )
316                .await?;
317                past_ids = lookup_key_contacts_by_address_list(
318                    context,
319                    &mime_parser.past_members,
320                    past_member_fingerprints,
321                    Some(*chat_id),
322                )
323                .await?;
324            } else {
325                to_ids = add_or_lookup_contacts_by_address_list(
326                    context,
327                    &mime_parser.recipients,
328                    if !mime_parser.incoming {
329                        Origin::OutgoingTo
330                    } else if incoming_origin.is_known() {
331                        Origin::IncomingTo
332                    } else {
333                        Origin::IncomingUnknownTo
334                    },
335                )
336                .await?;
337
338                past_ids = add_or_lookup_contacts_by_address_list(
339                    context,
340                    &mime_parser.past_members,
341                    Origin::Hidden,
342                )
343                .await?;
344            }
345        }
346        ChatAssignment::AdHocGroup => {
347            to_ids = add_or_lookup_contacts_by_address_list(
348                context,
349                &mime_parser.recipients,
350                if !mime_parser.incoming {
351                    Origin::OutgoingTo
352                } else if incoming_origin.is_known() {
353                    Origin::IncomingTo
354                } else {
355                    Origin::IncomingUnknownTo
356                },
357            )
358            .await?;
359
360            past_ids = add_or_lookup_contacts_by_address_list(
361                context,
362                &mime_parser.past_members,
363                Origin::Hidden,
364            )
365            .await?;
366        }
367        // Sometimes, messages are sent just to a single recipient
368        // in a broadcast (e.g. securejoin messages).
369        // In this case, we need to look them up like in a 1:1 chat:
370        ChatAssignment::OneOneChat | ChatAssignment::MailingListOrBroadcast => {
371            let pgp_to_ids = add_or_lookup_key_contacts(
372                context,
373                &mime_parser.recipients,
374                &mime_parser.gossiped_keys,
375                to_member_fingerprints,
376                Origin::Hidden,
377            )
378            .await?;
379            if pgp_to_ids
380                .first()
381                .is_some_and(|contact_id| contact_id.is_some())
382            {
383                // There is a single recipient and we have
384                // mapped it to a key contact.
385                // This is an encrypted 1:1 chat.
386                to_ids = pgp_to_ids
387            } else if let Some(chat_id) = chat_id {
388                to_ids = match mime_parser.was_encrypted() {
389                    true => {
390                        lookup_key_contacts_by_address_list(
391                            context,
392                            &mime_parser.recipients,
393                            to_member_fingerprints,
394                            Some(chat_id),
395                        )
396                        .await?
397                    }
398                    false => {
399                        add_or_lookup_contacts_by_address_list(
400                            context,
401                            &mime_parser.recipients,
402                            if !mime_parser.incoming {
403                                Origin::OutgoingTo
404                            } else if incoming_origin.is_known() {
405                                Origin::IncomingTo
406                            } else {
407                                Origin::IncomingUnknownTo
408                            },
409                        )
410                        .await?
411                    }
412                }
413            } else {
414                let ids = match mime_parser.was_encrypted() {
415                    true => {
416                        lookup_key_contacts_by_address_list(
417                            context,
418                            &mime_parser.recipients,
419                            to_member_fingerprints,
420                            None,
421                        )
422                        .await?
423                    }
424                    false => vec![],
425                };
426                if mime_parser.was_encrypted() && !ids.contains(&None)
427                // Prefer creating PGP chats if there are any key-contacts. At least this prevents
428                // from replying unencrypted.
429                || ids
430                    .iter()
431                    .any(|&c| c.is_some() && c != Some(ContactId::SELF))
432                {
433                    to_ids = ids;
434                } else {
435                    to_ids = add_or_lookup_contacts_by_address_list(
436                        context,
437                        &mime_parser.recipients,
438                        if !mime_parser.incoming {
439                            Origin::OutgoingTo
440                        } else if incoming_origin.is_known() {
441                            Origin::IncomingTo
442                        } else {
443                            Origin::IncomingUnknownTo
444                        },
445                    )
446                    .await?;
447                }
448            }
449
450            past_ids = add_or_lookup_contacts_by_address_list(
451                context,
452                &mime_parser.past_members,
453                Origin::Hidden,
454            )
455            .await?;
456        }
457    };
458
459    Ok((to_ids, past_ids))
460}
461
462/// Receive a message and add it to the database.
463///
464/// Returns an error on database failure or if the message is broken,
465/// e.g. has nonstandard MIME structure.
466///
467/// If possible, creates a database entry to prevent the message from being
468/// downloaded again, sets `chat_id=DC_CHAT_ID_TRASH` and returns `Ok(Some(…))`.
469/// If the message is so wrong that we didn't even create a database entry,
470/// returns `Ok(None)`.
471pub(crate) async fn receive_imf_inner(
472    context: &Context,
473    rfc724_mid: &str,
474    imf_raw: &[u8],
475    seen: bool,
476) -> Result<Option<ReceivedMsg>> {
477    ensure!(
478        !context
479            .get_config_bool(Config::SimulateReceiveImfError)
480            .await?
481    );
482    if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
483        info!(
484            context,
485            "receive_imf: incoming message mime-body:\n{}",
486            String::from_utf8_lossy(imf_raw),
487        );
488    }
489
490    let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw).await {
491        Err(err) => {
492            warn!(context, "receive_imf: can't parse MIME: {err:#}.");
493            if rfc724_mid.starts_with(GENERATED_PREFIX) {
494                // We don't have an rfc724_mid, there's no point in adding a trash entry
495                return Ok(None);
496            }
497
498            let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
499
500            return Ok(Some(ReceivedMsg {
501                chat_id: DC_CHAT_ID_TRASH,
502                state: MessageState::Undefined,
503                hidden: false,
504                sort_timestamp: 0,
505                msg_ids,
506                needs_delete_job: false,
507            }));
508        }
509        Ok(mime_parser) => mime_parser,
510    };
511
512    let rfc724_mid_orig = &mime_parser
513        .get_rfc724_mid()
514        .unwrap_or(rfc724_mid.to_string());
515    info!(
516        context,
517        "Receiving message {rfc724_mid_orig:?}, seen={seen}...",
518    );
519
520    // check, if the mail is already in our database.
521    // make sure, this check is done eg. before securejoin-processing.
522    let (replace_msg_id, replace_chat_id);
523    if mime_parser.pre_message == mimeparser::PreMessageMode::Post {
524        // Post-Message just replaces the attachment and modifies Params, not the whole message.
525        // This is done in the `handle_post_message` method.
526        replace_msg_id = None;
527        replace_chat_id = None;
528    } else if let Some(old_msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
529        // This code handles the download of old partial download stub messages
530        // It will be removed after a transitioning period,
531        // after we have released a few versions with pre-messages
532        replace_msg_id = Some(old_msg_id);
533        replace_chat_id = if let Some(msg) = Message::load_from_db_optional(context, old_msg_id)
534            .await?
535            .filter(|msg| msg.download_state() != DownloadState::Done)
536        {
537            // the message was partially downloaded before and is fully downloaded now.
538            info!(context, "Message already partly in DB, replacing.");
539            Some(msg.chat_id)
540        } else {
541            // The message was already fully downloaded
542            // or cannot be loaded because it is deleted.
543            None
544        };
545    } else {
546        replace_msg_id = if rfc724_mid_orig == rfc724_mid {
547            None
548        } else {
549            message::rfc724_mid_exists(context, rfc724_mid_orig).await?
550        };
551        replace_chat_id = None;
552    }
553
554    if replace_chat_id.is_some() {
555        // Need to update chat id in the db.
556    } else if let Some(msg_id) = replace_msg_id {
557        info!(context, "Message is already downloaded.");
558        if mime_parser.incoming {
559            return Ok(None);
560        }
561        // For the case if we missed a successful SMTP response. Be optimistic that the message is
562        // delivered also.
563        let self_addr = context.get_primary_self_addr().await?;
564        context
565            .sql
566            .execute(
567                "DELETE FROM smtp \
568                WHERE rfc724_mid=?1 AND (recipients LIKE ?2 OR recipients LIKE ('% ' || ?2))",
569                (rfc724_mid_orig, &self_addr),
570            )
571            .await?;
572        if !context
573            .sql
574            .exists(
575                "SELECT COUNT(*) FROM smtp WHERE rfc724_mid=?",
576                (rfc724_mid_orig,),
577            )
578            .await?
579        {
580            msg_id.set_delivered(context).await?;
581        }
582        return Ok(None);
583    };
584
585    let prevent_rename = should_prevent_rename(&mime_parser);
586
587    // get From: (it can be an address list!) and check if it is known (for known From:'s we add
588    // the other To:/Cc: in the 3rd pass)
589    // or if From: is equal to SELF (in this case, it is any outgoing messages,
590    // we do not check Return-Path any more as this is unreliable, see
591    // <https://github.com/deltachat/deltachat-core/issues/150>)
592    //
593    // If this is a mailing list email (i.e. list_id_header is some), don't change the displayname because in
594    // a mailing list the sender displayname sometimes does not belong to the sender email address.
595    // For example, GitHub sends messages from `notifications@github.com`,
596    // but uses display name of the user whose action generated the notification
597    // as the display name.
598    let fingerprint = mime_parser.signature.as_ref();
599    let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
600        context,
601        &mime_parser.from,
602        fingerprint,
603        prevent_rename,
604        false,
605    )
606    .await?
607    {
608        Some(contact_id_res) => contact_id_res,
609        None => {
610            warn!(
611                context,
612                "receive_imf: From field does not contain an acceptable address."
613            );
614            return Ok(None);
615        }
616    };
617
618    // Lookup parent message.
619    //
620    // This may be useful to assign the message to
621    // group chats without Chat-Group-ID
622    // when a message is sent by Thunderbird.
623    //
624    // This can be also used to lookup
625    // key-contact by email address
626    // when receiving a private 1:1 reply
627    // to a group chat message.
628    let parent_message = get_parent_message(
629        context,
630        mime_parser.get_header(HeaderDef::References),
631        mime_parser.get_header(HeaderDef::InReplyTo),
632    )
633    .await?
634    .filter(|p| Some(p.id) != replace_msg_id);
635
636    let chat_assignment =
637        decide_chat_assignment(context, &mime_parser, &parent_message, rfc724_mid, from_id).await?;
638    info!(context, "Chat assignment is {chat_assignment:?}.");
639
640    let (to_ids, past_ids) = get_to_and_past_contact_ids(
641        context,
642        &mime_parser,
643        &chat_assignment,
644        &parent_message,
645        incoming_origin,
646    )
647    .await?;
648
649    let received_msg;
650    if let Some(_step) = get_secure_join_step(&mime_parser) {
651        let res = if mime_parser.incoming {
652            handle_securejoin_handshake(context, &mut mime_parser, from_id)
653                .await
654                .with_context(|| {
655                    format!(
656                        "Error in Secure-Join '{}' message handling",
657                        mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
658                    )
659                })?
660        } else if let Some(to_id) = to_ids.first().copied().flatten() {
661            // handshake may mark contacts as verified and must be processed before chats are created
662            observe_securejoin_on_other_device(context, &mime_parser, to_id)
663                .await
664                .with_context(|| {
665                    format!(
666                        "Error in Secure-Join '{}' watching",
667                        mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
668                    )
669                })?
670        } else {
671            securejoin::HandshakeMessage::Propagate
672        };
673
674        match res {
675            securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
676                let msg_id = insert_tombstone(context, rfc724_mid).await?;
677                received_msg = Some(ReceivedMsg {
678                    chat_id: DC_CHAT_ID_TRASH,
679                    state: MessageState::InSeen,
680                    hidden: false,
681                    sort_timestamp: mime_parser.timestamp_sent,
682                    msg_ids: vec![msg_id],
683                    needs_delete_job: res == securejoin::HandshakeMessage::Done,
684                });
685            }
686            securejoin::HandshakeMessage::Propagate => {
687                received_msg = None;
688            }
689        }
690    } else {
691        received_msg = None;
692    }
693
694    let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?;
695
696    if verified_encryption == VerifiedEncryption::Verified {
697        mark_recipients_as_verified(context, from_id, &mime_parser).await?;
698    }
699
700    let is_old_contact_request;
701    let received_msg = if let Some(received_msg) = received_msg {
702        is_old_contact_request = false;
703        received_msg
704    } else {
705        let is_dc_message = if mime_parser.has_chat_version() {
706            MessengerMessage::Yes
707        } else if let Some(parent_message) = &parent_message {
708            match parent_message.is_dc_message {
709                MessengerMessage::No => MessengerMessage::No,
710                MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply,
711            }
712        } else {
713            MessengerMessage::No
714        };
715
716        let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
717            .unwrap_or_default();
718
719        let allow_creation = if mime_parser.decrypting_failed {
720            false
721        } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
722            && is_dc_message == MessengerMessage::No
723            && !context.get_config_bool(Config::IsChatmail).await?
724        {
725            // the message is a classic email in a classic profile
726            // (in chatmail profiles, we always show all messages, because shared dc-mua usage is not supported)
727            match show_emails {
728                ShowEmails::Off | ShowEmails::AcceptedContacts => false,
729                ShowEmails::All => true,
730            }
731        } else {
732            !mime_parser.parts.iter().all(|part| part.is_reaction)
733        };
734
735        let to_id = if mime_parser.incoming {
736            ContactId::SELF
737        } else {
738            to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
739        };
740
741        let (chat_id, chat_id_blocked, is_created) = do_chat_assignment(
742            context,
743            &chat_assignment,
744            from_id,
745            &to_ids,
746            &past_ids,
747            to_id,
748            allow_creation,
749            &mut mime_parser,
750            parent_message,
751        )
752        .await?;
753        is_old_contact_request = chat_id_blocked == Blocked::Request && !is_created;
754
755        // Add parts
756        add_parts(
757            context,
758            &mut mime_parser,
759            imf_raw,
760            &to_ids,
761            &past_ids,
762            rfc724_mid_orig,
763            from_id,
764            seen,
765            replace_msg_id,
766            prevent_rename,
767            chat_id,
768            chat_id_blocked,
769            is_dc_message,
770        )
771        .await
772        .context("add_parts error")?
773    };
774
775    if !from_id.is_special() {
776        contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
777    }
778
779    // Update gossiped timestamp for the chat if someone else or our other device sent
780    // Autocrypt-Gossip header to avoid sending Autocrypt-Gossip ourselves
781    // and waste traffic.
782    let chat_id = received_msg.chat_id;
783    if !chat_id.is_special() {
784        for gossiped_key in mime_parser.gossiped_keys.values() {
785            context
786                .sql
787                .transaction(move |transaction| {
788                    let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
789                    transaction.execute(
790                        "INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
791                         VALUES                       (?, ?, ?)
792                         ON CONFLICT                  (chat_id, fingerprint)
793                         DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
794                        (chat_id, &fingerprint, mime_parser.timestamp_sent),
795                    )?;
796
797                    Ok(())
798                })
799                .await?;
800        }
801    }
802
803    let insert_msg_id = if let Some(msg_id) = received_msg.msg_ids.last() {
804        *msg_id
805    } else {
806        MsgId::new_unset()
807    };
808
809    save_locations(context, &mime_parser, chat_id, from_id, insert_msg_id).await?;
810
811    if let Some(ref sync_items) = mime_parser.sync_items {
812        if from_id == ContactId::SELF {
813            if mime_parser.was_encrypted() {
814                // Receiving encrypted message from self updates primary transport.
815                let from_addr = &mime_parser.from.addr;
816
817                let transport_changed = context
818                    .sql
819                    .transaction(|transaction| {
820                        let transport_exists = transaction.query_row(
821                            "SELECT COUNT(*) FROM transports WHERE addr=?",
822                            (from_addr,),
823                            |row| {
824                                let count: i64 = row.get(0)?;
825                                Ok(count > 0)
826                            },
827                        )?;
828
829                        let transport_changed = if transport_exists {
830                            transaction.execute(
831                                "UPDATE config SET value=? WHERE keyname='configured_addr'",
832                                (from_addr,),
833                            )? > 0
834                        } else {
835                            warn!(
836                                context,
837                                "Received sync message from unknown address {from_addr:?}."
838                            );
839                            false
840                        };
841                        Ok(transport_changed)
842                    })
843                    .await?;
844                if transport_changed {
845                    info!(context, "Primary transport changed to {from_addr:?}.");
846                    context.sql.uncache_raw_config("configured_addr").await;
847                }
848
849                context
850                    .execute_sync_items(sync_items, mime_parser.timestamp_sent)
851                    .await;
852            } else {
853                warn!(context, "Sync items are not encrypted.");
854            }
855        } else {
856            warn!(context, "Sync items not sent by self.");
857        }
858    }
859
860    if let Some(ref status_update) = mime_parser.webxdc_status_update {
861        let can_info_msg;
862        let instance = if mime_parser
863            .parts
864            .first()
865            .filter(|part| part.typ == Viewtype::Webxdc)
866            .is_some()
867        {
868            can_info_msg = false;
869            Some(Message::load_from_db(context, insert_msg_id).await?)
870        } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
871            if let Some(instance) =
872                message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
873            {
874                can_info_msg = instance.download_state() == DownloadState::Done;
875                Some(instance)
876            } else {
877                can_info_msg = false;
878                None
879            }
880        } else {
881            can_info_msg = false;
882            None
883        };
884
885        if let Some(instance) = instance {
886            if let Err(err) = context
887                .receive_status_update(
888                    from_id,
889                    &instance,
890                    received_msg.sort_timestamp,
891                    can_info_msg,
892                    status_update,
893                )
894                .await
895            {
896                warn!(context, "receive_imf cannot update status: {err:#}.");
897            }
898        } else {
899            warn!(
900                context,
901                "Received webxdc update, but cannot assign it to message."
902            );
903        }
904    }
905
906    if let Some(avatar_action) = &mime_parser.user_avatar
907        && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
908        && context
909            .update_contacts_timestamp(from_id, Param::AvatarTimestamp, mime_parser.timestamp_sent)
910            .await?
911        && let Err(err) = contact::set_profile_image(context, from_id, avatar_action).await
912    {
913        warn!(context, "receive_imf cannot update profile image: {err:#}.");
914    };
915
916    // Ignore footers from mailinglists as they are often created or modified by the mailinglist software.
917    if let Some(footer) = &mime_parser.footer
918        && !mime_parser.is_mailinglist_message()
919        && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
920        && context
921            .update_contacts_timestamp(from_id, Param::StatusTimestamp, mime_parser.timestamp_sent)
922            .await?
923        && let Err(err) = contact::set_status(context, from_id, footer.to_string()).await
924    {
925        warn!(context, "Cannot update contact status: {err:#}.");
926    }
927
928    // Get user-configured server deletion
929    let delete_server_after = context.get_config_delete_server_after().await?;
930
931    if !received_msg.msg_ids.is_empty() {
932        let target = if received_msg.needs_delete_job || delete_server_after == Some(0) {
933            Some(context.get_delete_msgs_target().await?)
934        } else {
935            None
936        };
937        if target.is_some() || rfc724_mid_orig != rfc724_mid {
938            let target_subst = match &target {
939                Some(_) => "target=?1,",
940                None => "",
941            };
942            context
943                .sql
944                .execute(
945                    &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
946                    (
947                        target.as_deref().unwrap_or_default(),
948                        rfc724_mid_orig,
949                        rfc724_mid,
950                    ),
951                )
952                .await?;
953            context.scheduler.interrupt_inbox().await;
954        }
955        if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
956        {
957            // This is a Delta Chat MDN. Mark as read.
958            markseen_on_imap_table(context, rfc724_mid_orig).await?;
959        }
960    }
961
962    if mime_parser.is_call() {
963        context
964            .handle_call_msg(insert_msg_id, &mime_parser, from_id)
965            .await?;
966    } else if received_msg.hidden {
967        // No need to emit an event about the changed message
968    } else if let Some(replace_chat_id) = replace_chat_id {
969        match replace_chat_id == chat_id {
970            false => context.emit_msgs_changed_without_msg_id(replace_chat_id),
971            true => context.emit_msgs_changed(chat_id, replace_msg_id.unwrap_or_default()),
972        }
973    } else if !chat_id.is_trash() {
974        let fresh = received_msg.state == MessageState::InFresh
975            && mime_parser.is_system_message != SystemMessage::CallAccepted
976            && mime_parser.is_system_message != SystemMessage::CallEnded;
977        let important = mime_parser.incoming && fresh && !is_old_contact_request;
978        for msg_id in &received_msg.msg_ids {
979            chat_id.emit_msg_event(context, *msg_id, important);
980        }
981    }
982    context.new_msgs_notify.notify_one();
983
984    mime_parser
985        .handle_reports(context, from_id, &mime_parser.parts)
986        .await;
987
988    if let Some(is_bot) = mime_parser.is_bot {
989        // If the message is auto-generated and was generated by Delta Chat,
990        // mark the contact as a bot.
991        if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
992            from_id.mark_bot(context, is_bot).await?;
993        }
994    }
995
996    Ok(Some(received_msg))
997}
998
999/// Converts "From" field to contact id.
1000///
1001/// Also returns whether it is blocked or not and its origin.
1002///
1003/// * `prevent_rename`: if true, the display_name of this contact will not be changed. Useful for
1004///   mailing lists: In some mailing lists, many users write from the same address but with different
1005///   display names. We don't want the display name to change every time the user gets a new email from
1006///   a mailing list.
1007///
1008/// * `find_key_contact_by_addr`: if true, we only know the e-mail address
1009///   of the contact, but not the fingerprint,
1010///   yet want to assign the message to some key-contact.
1011///   This can happen during prefetch.
1012///   If we get it wrong, the message will be placed into the correct
1013///   chat after downloading.
1014///
1015/// Returns `None` if From field does not contain a valid contact address.
1016pub async fn from_field_to_contact_id(
1017    context: &Context,
1018    from: &SingleInfo,
1019    fingerprint: Option<&Fingerprint>,
1020    prevent_rename: bool,
1021    find_key_contact_by_addr: bool,
1022) -> Result<Option<(ContactId, bool, Origin)>> {
1023    let fingerprint = fingerprint.as_ref().map(|fp| fp.hex()).unwrap_or_default();
1024    let display_name = if prevent_rename {
1025        Some("")
1026    } else {
1027        from.display_name.as_deref()
1028    };
1029    let from_addr = match ContactAddress::new(&from.addr) {
1030        Ok(from_addr) => from_addr,
1031        Err(err) => {
1032            warn!(
1033                context,
1034                "Cannot create a contact for the given From field: {err:#}."
1035            );
1036            return Ok(None);
1037        }
1038    };
1039
1040    if fingerprint.is_empty() && find_key_contact_by_addr {
1041        let addr_normalized = addr_normalize(&from_addr);
1042
1043        // Try to assign to some key-contact.
1044        if let Some((from_id, origin)) = context
1045            .sql
1046            .query_row_optional(
1047                "SELECT id, origin FROM contacts
1048                 WHERE addr=?1 COLLATE NOCASE
1049                 AND fingerprint<>'' -- Only key-contacts
1050                 AND id>?2 AND origin>=?3 AND blocked=?4
1051                 ORDER BY last_seen DESC
1052                 LIMIT 1",
1053                (
1054                    &addr_normalized,
1055                    ContactId::LAST_SPECIAL,
1056                    Origin::IncomingUnknownFrom,
1057                    Blocked::Not,
1058                ),
1059                |row| {
1060                    let id: ContactId = row.get(0)?;
1061                    let origin: Origin = row.get(1)?;
1062                    Ok((id, origin))
1063                },
1064            )
1065            .await?
1066        {
1067            return Ok(Some((from_id, false, origin)));
1068        }
1069    }
1070
1071    let (from_id, _) = Contact::add_or_lookup_ex(
1072        context,
1073        display_name.unwrap_or_default(),
1074        &from_addr,
1075        &fingerprint,
1076        Origin::IncomingUnknownFrom,
1077    )
1078    .await?;
1079
1080    if from_id == ContactId::SELF {
1081        Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
1082    } else {
1083        let contact = Contact::get_by_id(context, from_id).await?;
1084        let from_id_blocked = contact.blocked;
1085        let incoming_origin = contact.origin;
1086
1087        context
1088            .sql
1089            .execute(
1090                "UPDATE contacts SET addr=? WHERE id=?",
1091                (from_addr, from_id),
1092            )
1093            .await?;
1094
1095        Ok(Some((from_id, from_id_blocked, incoming_origin)))
1096    }
1097}
1098
1099async fn decide_chat_assignment(
1100    context: &Context,
1101    mime_parser: &MimeMessage,
1102    parent_message: &Option<Message>,
1103    rfc724_mid: &str,
1104    from_id: ContactId,
1105) -> Result<ChatAssignment> {
1106    let mut should_trash = if !mime_parser.mdn_reports.is_empty() {
1107        info!(context, "Message is an MDN (TRASH).");
1108        true
1109    } else if mime_parser.delivery_report.is_some() {
1110        info!(context, "Message is a DSN (TRASH).");
1111        markseen_on_imap_table(context, rfc724_mid).await.ok();
1112        true
1113    } else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
1114        || mime_parser.get_header(HeaderDef::ChatDelete).is_some()
1115        || mime_parser.get_header(HeaderDef::IrohNodeAddr).is_some()
1116        || mime_parser.sync_items.is_some()
1117    {
1118        info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
1119        true
1120    } else if mime_parser.is_system_message == SystemMessage::CallAccepted
1121        || mime_parser.is_system_message == SystemMessage::CallEnded
1122    {
1123        info!(context, "Call state changed (TRASH).");
1124        true
1125    } else if mime_parser.decrypting_failed && !mime_parser.incoming {
1126        // Outgoing undecryptable message.
1127        let last_time = context
1128            .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1129            .await?;
1130        let now = tools::time();
1131        let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1132            let txt = "⚠️ It seems you are using Delta Chat on multiple devices that cannot decrypt each other's outgoing messages. To fix this, on the older device use \"Settings / Add Second Device\" and follow the instructions.";
1133            let mut msg = Message::new_text(txt.to_string());
1134            chat::add_device_msg(context, None, Some(&mut msg))
1135                .await
1136                .log_err(context)
1137                .ok();
1138            true
1139        } else {
1140            last_time > now
1141        };
1142        if update_config {
1143            context
1144                .set_config_internal(Config::LastCantDecryptOutgoingMsgs, Some(&now.to_string()))
1145                .await?;
1146        }
1147        info!(context, "Outgoing undecryptable message (TRASH).");
1148        true
1149    } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
1150        && !mime_parser.has_chat_version()
1151        && parent_message
1152            .as_ref()
1153            .is_none_or(|p| p.is_dc_message == MessengerMessage::No)
1154        && !context.get_config_bool(Config::IsChatmail).await?
1155        && ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
1156            .unwrap_or_default()
1157            == ShowEmails::Off
1158    {
1159        info!(context, "Classical email not shown (TRASH).");
1160        // the message is a classic email in a classic profile
1161        // (in chatmail profiles, we always show all messages, because shared dc-mua usage is not supported)
1162        true
1163    } else if mime_parser
1164        .get_header(HeaderDef::XMozillaDraftInfo)
1165        .is_some()
1166    {
1167        // Mozilla Thunderbird does not set \Draft flag on "Templates", but sets
1168        // X-Mozilla-Draft-Info header, which can be used to detect both drafts and templates
1169        // created by Thunderbird.
1170
1171        // Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
1172        info!(context, "Email is probably just a draft (TRASH).");
1173        true
1174    } else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1175        if let Some(part) = mime_parser.parts.first() {
1176            if part.typ == Viewtype::Text && part.msg.is_empty() {
1177                info!(context, "Message is a status update only (TRASH).");
1178                markseen_on_imap_table(context, rfc724_mid).await.ok();
1179                true
1180            } else {
1181                false
1182            }
1183        } else {
1184            false
1185        }
1186    } else {
1187        false
1188    };
1189
1190    should_trash |= if mime_parser.pre_message == PreMessageMode::Post {
1191        // if pre message exist, then trash after replacing, otherwise treat as normal message
1192        let pre_message_exists = msg_is_downloaded_for(context, rfc724_mid).await?;
1193        info!(
1194            context,
1195            "Message {rfc724_mid} is a post-message ({}).",
1196            if pre_message_exists {
1197                "pre-message exists already, so trash after replacing attachment"
1198            } else {
1199                "no pre-message -> Keep"
1200            }
1201        );
1202        pre_message_exists
1203    } else if let PreMessageMode::Pre {
1204        post_msg_rfc724_mid,
1205        ..
1206    } = &mime_parser.pre_message
1207    {
1208        let msg_id = rfc724_mid_exists(context, post_msg_rfc724_mid).await?;
1209        if let Some(msg_id) = msg_id {
1210            context
1211                .sql
1212                .execute(
1213                    "UPDATE msgs SET pre_rfc724_mid=? WHERE id=?",
1214                    (rfc724_mid, msg_id),
1215                )
1216                .await?;
1217        }
1218        let post_msg_exists = msg_id.is_some();
1219        info!(
1220            context,
1221            "Message {rfc724_mid} is a pre-message for {post_msg_rfc724_mid} (post_msg_exists:{post_msg_exists})."
1222        );
1223        post_msg_exists
1224    } else {
1225        false
1226    };
1227
1228    // Decide on the type of chat we assign the message to.
1229    //
1230    // The chat may not exist yet, i.e. there may be
1231    // no database row and ChatId yet.
1232    let mut num_recipients = 0;
1233    let mut has_self_addr = false;
1234    for recipient in &mime_parser.recipients {
1235        has_self_addr |= context.is_self_addr(&recipient.addr).await?;
1236        if addr_cmp(&recipient.addr, &mime_parser.from.addr) {
1237            continue;
1238        }
1239        num_recipients += 1;
1240    }
1241    if from_id != ContactId::SELF && !has_self_addr {
1242        num_recipients += 1;
1243    }
1244    let can_be_11_chat = num_recipients <= 1
1245        && (from_id != ContactId::SELF
1246            || !(mime_parser.recipients.is_empty() || has_self_addr)
1247            || mime_parser.was_encrypted());
1248
1249    let chat_assignment = if should_trash {
1250        ChatAssignment::Trash
1251    } else if mime_parser.get_mailinglist_header().is_some() {
1252        ChatAssignment::MailingListOrBroadcast
1253    } else if let Some(grpid) = mime_parser.get_chat_group_id() {
1254        if mime_parser.was_encrypted() {
1255            ChatAssignment::GroupChat {
1256                grpid: grpid.to_string(),
1257            }
1258        } else if let Some(parent) = &parent_message {
1259            if let Some((chat_id, chat_id_blocked)) =
1260                lookup_chat_by_reply(context, mime_parser, parent).await?
1261            {
1262                // Try to assign to a chat based on In-Reply-To/References.
1263                ChatAssignment::ExistingChat {
1264                    chat_id,
1265                    chat_id_blocked,
1266                }
1267            } else {
1268                ChatAssignment::AdHocGroup
1269            }
1270        } else {
1271            // Could be a message from old version
1272            // with opportunistic encryption.
1273            //
1274            // We still want to assign this to a group
1275            // even if it had only two members.
1276            //
1277            // Group ID is ignored, however.
1278            ChatAssignment::AdHocGroup
1279        }
1280    } else if let Some(parent) = &parent_message {
1281        if let Some((chat_id, chat_id_blocked)) =
1282            lookup_chat_by_reply(context, mime_parser, parent).await?
1283        {
1284            // Try to assign to a chat based on In-Reply-To/References.
1285            ChatAssignment::ExistingChat {
1286                chat_id,
1287                chat_id_blocked,
1288            }
1289        } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1290            ChatAssignment::AdHocGroup
1291        } else if can_be_11_chat {
1292            ChatAssignment::OneOneChat
1293        } else {
1294            ChatAssignment::AdHocGroup
1295        }
1296    } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1297        ChatAssignment::AdHocGroup
1298    } else if can_be_11_chat {
1299        ChatAssignment::OneOneChat
1300    } else {
1301        ChatAssignment::AdHocGroup
1302    };
1303    Ok(chat_assignment)
1304}
1305
1306/// Assigns the message to a chat.
1307///
1308/// Creates a new chat if necessary.
1309///
1310/// Returns the chat ID,
1311/// whether it is blocked
1312/// and if the chat was created by this function
1313/// (as opposed to being looked up among existing chats).
1314#[expect(clippy::too_many_arguments)]
1315async fn do_chat_assignment(
1316    context: &Context,
1317    chat_assignment: &ChatAssignment,
1318    from_id: ContactId,
1319    to_ids: &[Option<ContactId>],
1320    past_ids: &[Option<ContactId>],
1321    to_id: ContactId,
1322    allow_creation: bool,
1323    mime_parser: &mut MimeMessage,
1324    parent_message: Option<Message>,
1325) -> Result<(ChatId, Blocked, bool)> {
1326    let is_bot = context.get_config_bool(Config::Bot).await?;
1327
1328    let mut chat_id = None;
1329    let mut chat_id_blocked = Blocked::Not;
1330    let mut chat_created = false;
1331
1332    if mime_parser.incoming {
1333        let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
1334
1335        let create_blocked_default = if is_bot {
1336            Blocked::Not
1337        } else {
1338            Blocked::Request
1339        };
1340        let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
1341            match blocked {
1342                Blocked::Request => create_blocked_default,
1343                Blocked::Not => Blocked::Not,
1344                Blocked::Yes => {
1345                    if Contact::is_blocked_load(context, from_id).await? {
1346                        // User has blocked the contact.
1347                        // Block the group contact created as well.
1348                        Blocked::Yes
1349                    } else {
1350                        // 1:1 chat is blocked, but the contact is not.
1351                        // This happens when 1:1 chat is hidden
1352                        // during scanning of a group invitation code.
1353                        create_blocked_default
1354                    }
1355                }
1356            }
1357        } else {
1358            create_blocked_default
1359        };
1360
1361        match &chat_assignment {
1362            ChatAssignment::Trash => {
1363                chat_id = Some(DC_CHAT_ID_TRASH);
1364            }
1365            ChatAssignment::GroupChat { grpid } => {
1366                // Try to assign to a chat based on Chat-Group-ID.
1367                if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1368                    chat_id = Some(id);
1369                    chat_id_blocked = blocked;
1370                } else if (allow_creation || test_normal_chat.is_some())
1371                    && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1372                        context,
1373                        mime_parser,
1374                        create_blocked,
1375                        from_id,
1376                        to_ids,
1377                        past_ids,
1378                        grpid,
1379                    )
1380                    .await?
1381                {
1382                    chat_id = Some(new_chat_id);
1383                    chat_id_blocked = new_chat_id_blocked;
1384                    chat_created = true;
1385                }
1386            }
1387            ChatAssignment::MailingListOrBroadcast => {
1388                if let Some(mailinglist_header) = mime_parser.get_mailinglist_header()
1389                    && let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1390                        create_or_lookup_mailinglist_or_broadcast(
1391                            context,
1392                            allow_creation,
1393                            create_blocked,
1394                            mailinglist_header,
1395                            from_id,
1396                            mime_parser,
1397                        )
1398                        .await?
1399                {
1400                    chat_id = Some(new_chat_id);
1401                    chat_id_blocked = new_chat_id_blocked;
1402                    chat_created = new_chat_created;
1403
1404                    apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
1405                }
1406            }
1407            ChatAssignment::ExistingChat {
1408                chat_id: new_chat_id,
1409                chat_id_blocked: new_chat_id_blocked,
1410            } => {
1411                chat_id = Some(*new_chat_id);
1412                chat_id_blocked = *new_chat_id_blocked;
1413            }
1414            ChatAssignment::AdHocGroup => {
1415                if let Some((new_chat_id, new_chat_id_blocked, new_created)) =
1416                    lookup_or_create_adhoc_group(
1417                        context,
1418                        mime_parser,
1419                        to_ids,
1420                        allow_creation || test_normal_chat.is_some(),
1421                        create_blocked,
1422                    )
1423                    .await?
1424                {
1425                    chat_id = Some(new_chat_id);
1426                    chat_id_blocked = new_chat_id_blocked;
1427                    chat_created = new_created;
1428                }
1429            }
1430            ChatAssignment::OneOneChat => {}
1431        }
1432
1433        // if the chat is somehow blocked but we want to create a non-blocked chat,
1434        // unblock the chat
1435        if chat_id_blocked != Blocked::Not
1436            && create_blocked != Blocked::Yes
1437            && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
1438            && let Some(chat_id) = chat_id
1439        {
1440            chat_id.set_blocked(context, create_blocked).await?;
1441            chat_id_blocked = create_blocked;
1442        }
1443
1444        if chat_id.is_none() {
1445            // Try to create a 1:1 chat.
1446            let contact = Contact::get_by_id(context, from_id).await?;
1447            let create_blocked = match contact.is_blocked() {
1448                true => Blocked::Yes,
1449                false if is_bot => Blocked::Not,
1450                false => Blocked::Request,
1451            };
1452
1453            if let Some(chat) = test_normal_chat {
1454                chat_id = Some(chat.id);
1455                chat_id_blocked = chat.blocked;
1456            } else if allow_creation {
1457                let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
1458                    .await
1459                    .context("Failed to get (new) chat for contact")?;
1460                chat_id = Some(chat.id);
1461                chat_id_blocked = chat.blocked;
1462                chat_created = true;
1463            }
1464
1465            if let Some(chat_id) = chat_id
1466                && chat_id_blocked != Blocked::Not
1467            {
1468                if chat_id_blocked != create_blocked {
1469                    chat_id.set_blocked(context, create_blocked).await?;
1470                }
1471                if create_blocked == Blocked::Request && parent_message.is_some() {
1472                    // we do not want any chat to be created implicitly.  Because of the origin-scale-up,
1473                    // the contact requests will pop up and this should be just fine.
1474                    ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo).await?;
1475                    info!(
1476                        context,
1477                        "Message is a reply to a known message, mark sender as known.",
1478                    );
1479                }
1480            }
1481        }
1482    } else {
1483        // Outgoing
1484
1485        // Older Delta Chat versions with core <=1.152.2 only accepted
1486        // self-sent messages in Saved Messages with own address in the `To` field.
1487        // New Delta Chat versions may use empty `To` field
1488        // with only a single `hidden-recipients` group in this case.
1489        let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1490
1491        match &chat_assignment {
1492            ChatAssignment::Trash => {
1493                chat_id = Some(DC_CHAT_ID_TRASH);
1494            }
1495            ChatAssignment::GroupChat { grpid } => {
1496                if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1497                    chat_id = Some(id);
1498                    chat_id_blocked = blocked;
1499                } else if allow_creation
1500                    && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1501                        context,
1502                        mime_parser,
1503                        Blocked::Not,
1504                        from_id,
1505                        to_ids,
1506                        past_ids,
1507                        grpid,
1508                    )
1509                    .await?
1510                {
1511                    chat_id = Some(new_chat_id);
1512                    chat_id_blocked = new_chat_id_blocked;
1513                    chat_created = true;
1514                }
1515            }
1516            ChatAssignment::ExistingChat {
1517                chat_id: new_chat_id,
1518                chat_id_blocked: new_chat_id_blocked,
1519            } => {
1520                chat_id = Some(*new_chat_id);
1521                chat_id_blocked = *new_chat_id_blocked;
1522            }
1523            ChatAssignment::MailingListOrBroadcast => {
1524                // Check if the message belongs to a broadcast channel
1525                // (it can't be a mailing list, since it's outgoing)
1526                if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1527                    let listid = mailinglist_header_listid(mailinglist_header)?;
1528                    if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await? {
1529                        chat_id = Some(id);
1530                    } else {
1531                        // Looks like we missed the sync message that was creating this broadcast channel
1532                        let name =
1533                            compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1534                        if let Some(secret) = mime_parser
1535                            .get_header(HeaderDef::ChatBroadcastSecret)
1536                            .filter(|s| validate_broadcast_secret(s))
1537                        {
1538                            chat_created = true;
1539                            chat_id = Some(
1540                                chat::create_out_broadcast_ex(
1541                                    context,
1542                                    Nosync,
1543                                    listid,
1544                                    name,
1545                                    secret.to_string(),
1546                                )
1547                                .await?,
1548                            );
1549                        } else {
1550                            warn!(
1551                                context,
1552                                "Not creating outgoing broadcast with id {listid}, because secret is unknown"
1553                            );
1554                        }
1555                    }
1556                }
1557            }
1558            ChatAssignment::AdHocGroup => {
1559                if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1560                    lookup_or_create_adhoc_group(
1561                        context,
1562                        mime_parser,
1563                        to_ids,
1564                        allow_creation,
1565                        Blocked::Not,
1566                    )
1567                    .await?
1568                {
1569                    chat_id = Some(new_chat_id);
1570                    chat_id_blocked = new_chat_id_blocked;
1571                    chat_created = new_chat_created;
1572                }
1573            }
1574            ChatAssignment::OneOneChat => {}
1575        }
1576
1577        if !to_ids.is_empty() {
1578            if chat_id.is_none() && allow_creation {
1579                let to_contact = Contact::get_by_id(context, to_id).await?;
1580                if let Some(list_id) = to_contact.param.get(Param::ListId) {
1581                    if let Some((id, blocked)) =
1582                        chat::get_chat_id_by_grpid(context, list_id).await?
1583                    {
1584                        chat_id = Some(id);
1585                        chat_id_blocked = blocked;
1586                    }
1587                } else {
1588                    let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1589                    chat_id = Some(chat.id);
1590                    chat_id_blocked = chat.blocked;
1591                    chat_created = true;
1592                }
1593            }
1594            if chat_id.is_none()
1595                && mime_parser.has_chat_version()
1596                && let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await?
1597            {
1598                chat_id = Some(chat.id);
1599                chat_id_blocked = chat.blocked;
1600            }
1601        }
1602
1603        if chat_id.is_none() && self_sent {
1604            // from_id==to_id==ContactId::SELF - this is a self-sent messages,
1605            // maybe an Autocrypt Setup Message
1606            let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1607                .await
1608                .context("Failed to get (new) chat for contact")?;
1609
1610            chat_id = Some(chat.id);
1611            chat_id_blocked = chat.blocked;
1612
1613            if Blocked::Not != chat.blocked {
1614                chat.id.unblock_ex(context, Nosync).await?;
1615            }
1616        }
1617
1618        // automatically unblock chat when the user sends a message
1619        if chat_id_blocked != Blocked::Not
1620            && let Some(chat_id) = chat_id
1621        {
1622            chat_id.unblock_ex(context, Nosync).await?;
1623            chat_id_blocked = Blocked::Not;
1624        }
1625    }
1626    let chat_id = chat_id.unwrap_or_else(|| {
1627        info!(context, "No chat id for message (TRASH).");
1628        DC_CHAT_ID_TRASH
1629    });
1630    Ok((chat_id, chat_id_blocked, chat_created))
1631}
1632
1633/// Creates a `ReceivedMsg` from given parts which might consist of
1634/// multiple messages (if there are multiple attachments).
1635/// Every entry in `mime_parser.parts` produces a new row in the `msgs` table.
1636#[expect(clippy::too_many_arguments)]
1637async fn add_parts(
1638    context: &Context,
1639    mime_parser: &mut MimeMessage,
1640    imf_raw: &[u8],
1641    to_ids: &[Option<ContactId>],
1642    past_ids: &[Option<ContactId>],
1643    rfc724_mid: &str,
1644    from_id: ContactId,
1645    seen: bool,
1646    mut replace_msg_id: Option<MsgId>,
1647    prevent_rename: bool,
1648    mut chat_id: ChatId,
1649    mut chat_id_blocked: Blocked,
1650    is_dc_message: MessengerMessage,
1651) -> Result<ReceivedMsg> {
1652    let to_id = if mime_parser.incoming {
1653        ContactId::SELF
1654    } else {
1655        to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
1656    };
1657
1658    // if contact renaming is prevented (for mailinglists and bots),
1659    // we use name from From:-header as override name
1660    if prevent_rename && let Some(name) = &mime_parser.from.display_name {
1661        for part in &mut mime_parser.parts {
1662            part.param.set(Param::OverrideSenderDisplayname, name);
1663        }
1664    }
1665
1666    let mut chat = Chat::load_from_db(context, chat_id).await?;
1667
1668    if mime_parser.incoming && !chat_id.is_trash() {
1669        // It can happen that the message is put into a chat
1670        // but the From-address is not a member of this chat.
1671        if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
1672            // Mark the sender as overridden.
1673            // The UI will prepend `~` to the sender's name,
1674            // indicating that the sender is not part of the group.
1675            let from = &mime_parser.from;
1676            let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
1677            for part in &mut mime_parser.parts {
1678                part.param.set(Param::OverrideSenderDisplayname, name);
1679            }
1680
1681            if chat.typ == Chattype::InBroadcast {
1682                warn!(
1683                    context,
1684                    "Not assigning msg '{rfc724_mid}' to broadcast {chat_id}: wrong sender: {from_id}."
1685                );
1686                let direct_chat =
1687                    ChatIdBlocked::get_for_contact(context, from_id, Blocked::Request).await?;
1688                chat_id = direct_chat.id;
1689                chat_id_blocked = direct_chat.blocked;
1690                chat = Chat::load_from_db(context, chat_id).await?;
1691            }
1692        }
1693    }
1694
1695    let is_location_kml = mime_parser.location_kml.is_some();
1696    let is_mdn = !mime_parser.mdn_reports.is_empty();
1697
1698    let mut group_changes = match chat.typ {
1699        _ if chat.id.is_special() => GroupChangesInfo::default(),
1700        Chattype::Single => GroupChangesInfo::default(),
1701        Chattype::Mailinglist => GroupChangesInfo::default(),
1702        Chattype::OutBroadcast => {
1703            apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1704        }
1705        Chattype::Group => {
1706            apply_group_changes(context, mime_parser, &mut chat, from_id, to_ids, past_ids).await?
1707        }
1708        Chattype::InBroadcast => {
1709            apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1710        }
1711    };
1712
1713    let rfc724_mid_orig = &mime_parser
1714        .get_rfc724_mid()
1715        .unwrap_or(rfc724_mid.to_string());
1716
1717    // Extract ephemeral timer from the message
1718    let mut ephemeral_timer = if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer)
1719    {
1720        match value.parse::<EphemeralTimer>() {
1721            Ok(timer) => timer,
1722            Err(err) => {
1723                warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1724                EphemeralTimer::Disabled
1725            }
1726        }
1727    } else {
1728        EphemeralTimer::Disabled
1729    };
1730
1731    let state = if !mime_parser.incoming {
1732        MessageState::OutDelivered
1733    } else if seen || is_mdn || chat_id_blocked == Blocked::Yes || group_changes.silent
1734    // No check for `hidden` because only reactions are such and they should be `InFresh`.
1735    {
1736        MessageState::InSeen
1737    } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL {
1738        MessageState::InNoticed
1739    } else {
1740        MessageState::InFresh
1741    };
1742    let in_fresh = state == MessageState::InFresh;
1743
1744    let sort_to_bottom = false;
1745    let received = true;
1746    let sort_timestamp = chat_id
1747        .calc_sort_timestamp(
1748            context,
1749            mime_parser.timestamp_sent,
1750            sort_to_bottom,
1751            received,
1752            mime_parser.incoming,
1753        )
1754        .await?;
1755
1756    // Apply ephemeral timer changes to the chat.
1757    //
1758    // Only apply the timer when there are visible parts (e.g., the message does not consist only
1759    // of `location.kml` attachment).  Timer changes without visible received messages may be
1760    // confusing to the user.
1761    if !chat_id.is_special()
1762        && !mime_parser.parts.is_empty()
1763        && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1764    {
1765        let chat_contacts =
1766            HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1767        let is_from_in_chat =
1768            !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1769
1770        info!(
1771            context,
1772            "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1773        );
1774        if !is_from_in_chat {
1775            warn!(
1776                context,
1777                "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1778            );
1779        } else if is_dc_message == MessengerMessage::Yes
1780            && get_previous_message(context, mime_parser)
1781                .await?
1782                .map(|p| p.ephemeral_timer)
1783                == Some(ephemeral_timer)
1784            && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1785        {
1786            // The message is a Delta Chat message, so we know that previous message according to
1787            // References header is the last message in the chat as seen by the sender. The timer
1788            // is the same in both the received message and the last message, so we know that the
1789            // sender has not seen any change of the timer between these messages. As our timer
1790            // value is different, it means the sender has not received some timer update that we
1791            // have seen or sent ourselves, so we ignore incoming timer to prevent a rollback.
1792            warn!(
1793                context,
1794                "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1795            );
1796        } else if chat_id
1797            .update_timestamp(
1798                context,
1799                Param::EphemeralSettingsTimestamp,
1800                mime_parser.timestamp_sent,
1801            )
1802            .await?
1803        {
1804            if let Err(err) = chat_id
1805                .inner_set_ephemeral_timer(context, ephemeral_timer)
1806                .await
1807            {
1808                warn!(
1809                    context,
1810                    "Failed to modify timer for chat {chat_id}: {err:#}."
1811                );
1812            } else {
1813                info!(
1814                    context,
1815                    "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1816                );
1817                if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1818                    chat::add_info_msg_with_cmd(
1819                        context,
1820                        chat_id,
1821                        &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1822                        SystemMessage::Unknown,
1823                        Some(sort_timestamp),
1824                        mime_parser.timestamp_sent,
1825                        None,
1826                        None,
1827                        None,
1828                    )
1829                    .await?;
1830                }
1831            }
1832        } else {
1833            warn!(
1834                context,
1835                "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1836            );
1837        }
1838    }
1839
1840    let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1841    {
1842        Some(stock_str::msg_location_enabled_by(context, from_id).await)
1843    } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1844        let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1845
1846        // Do not delete the system message itself.
1847        //
1848        // This prevents confusion when timer is changed
1849        // to 1 week, and then changed to 1 hour: after 1
1850        // hour, only the message about the change to 1
1851        // week is left.
1852        ephemeral_timer = EphemeralTimer::Disabled;
1853
1854        Some(better_msg)
1855    } else {
1856        None
1857    };
1858
1859    drop(chat); // Avoid using stale `chat` object.
1860
1861    let sort_timestamp = tweak_sort_timestamp(
1862        context,
1863        mime_parser,
1864        group_changes.silent,
1865        chat_id,
1866        sort_timestamp,
1867    )
1868    .await?;
1869
1870    let mime_in_reply_to = mime_parser
1871        .get_header(HeaderDef::InReplyTo)
1872        .unwrap_or_default();
1873    let mime_references = mime_parser
1874        .get_header(HeaderDef::References)
1875        .unwrap_or_default();
1876
1877    // fine, so far.  now, split the message into simple parts usable as "short messages"
1878    // and add them to the database (mails sent by other messenger clients should result
1879    // into only one message; mails sent by other clients may result in several messages
1880    // (eg. one per attachment))
1881    let icnt = mime_parser.parts.len();
1882
1883    let subject = mime_parser.get_subject().unwrap_or_default();
1884
1885    let is_system_message = mime_parser.is_system_message;
1886
1887    // if indicated by the parser,
1888    // we save the full mime-message and add a flag
1889    // that the ui should show button to display the full message.
1890
1891    // We add "Show Full Message" button to the last message bubble (part) if this flag evaluates to
1892    // `true` finally.
1893    let mut save_mime_modified = false;
1894
1895    let mime_headers = if mime_parser.is_mime_modified {
1896        let headers = if !mime_parser.decoded_data.is_empty() {
1897            mime_parser.decoded_data.clone()
1898        } else {
1899            imf_raw.to_vec()
1900        };
1901        tokio::task::block_in_place(move || buf_compress(&headers))?
1902    } else {
1903        Vec::new()
1904    };
1905
1906    let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
1907
1908    if let Some(m) = group_changes.better_msg {
1909        match &better_msg {
1910            None => better_msg = Some(m),
1911            Some(_) => {
1912                if !m.is_empty() {
1913                    group_changes.extra_msgs.push((m, is_system_message, None))
1914                }
1915            }
1916        }
1917    }
1918
1919    let chat_id = if better_msg
1920        .as_ref()
1921        .is_some_and(|better_msg| better_msg.is_empty())
1922    {
1923        DC_CHAT_ID_TRASH
1924    } else {
1925        chat_id
1926    };
1927
1928    for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
1929        chat::add_info_msg_with_cmd(
1930            context,
1931            chat_id,
1932            &group_changes_msg,
1933            cmd,
1934            Some(sort_timestamp),
1935            mime_parser.timestamp_sent,
1936            None,
1937            None,
1938            added_removed_id,
1939        )
1940        .await?;
1941    }
1942
1943    if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
1944        match mime_parser.get_header(HeaderDef::InReplyTo) {
1945            Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
1946                Some(instance_id) => {
1947                    if let Err(err) =
1948                        add_gossip_peer_from_header(context, instance_id, node_addr).await
1949                    {
1950                        warn!(context, "Failed to add iroh peer from header: {err:#}.");
1951                    }
1952                }
1953                None => {
1954                    warn!(
1955                        context,
1956                        "Cannot add iroh peer because WebXDC instance does not exist."
1957                    );
1958                }
1959            },
1960            None => {
1961                warn!(
1962                    context,
1963                    "Cannot add iroh peer because the message has no In-Reply-To."
1964                );
1965            }
1966        }
1967    }
1968
1969    handle_edit_delete(context, mime_parser, from_id).await?;
1970    handle_post_message(context, mime_parser, from_id, state).await?;
1971
1972    if mime_parser.is_system_message == SystemMessage::CallAccepted
1973        || mime_parser.is_system_message == SystemMessage::CallEnded
1974    {
1975        if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
1976            if let Some(call) =
1977                message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
1978            {
1979                context
1980                    .handle_call_msg(call.get_id(), mime_parser, from_id)
1981                    .await?;
1982            } else {
1983                warn!(context, "Call: Cannot load parent.")
1984            }
1985        } else {
1986            warn!(context, "Call: Not a reply.")
1987        }
1988    }
1989
1990    let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
1991    let mut parts = mime_parser.parts.iter().peekable();
1992    while let Some(part) = parts.next() {
1993        let hidden = part.is_reaction;
1994        if part.is_reaction {
1995            let reaction_str = simplify::remove_footers(part.msg.as_str());
1996            let is_incoming_fresh = mime_parser.incoming && !seen;
1997            set_msg_reaction(
1998                context,
1999                mime_in_reply_to,
2000                chat_id,
2001                from_id,
2002                sort_timestamp,
2003                Reaction::from(reaction_str.as_str()),
2004                is_incoming_fresh,
2005            )
2006            .await?;
2007        }
2008
2009        let mut param = part.param.clone();
2010        if is_system_message != SystemMessage::Unknown {
2011            param.set_int(Param::Cmd, is_system_message as i32);
2012        }
2013
2014        if let Some(replace_msg_id) = replace_msg_id {
2015            let placeholder = Message::load_from_db(context, replace_msg_id).await?;
2016            for key in [
2017                Param::WebxdcSummary,
2018                Param::WebxdcSummaryTimestamp,
2019                Param::WebxdcDocument,
2020                Param::WebxdcDocumentTimestamp,
2021            ] {
2022                if let Some(value) = placeholder.param.get(key) {
2023                    param.set(key, value);
2024                }
2025            }
2026        }
2027
2028        let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
2029            (better_msg, Viewtype::Text)
2030        } else {
2031            (&part.msg, part.typ)
2032        };
2033        let part_is_empty =
2034            typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
2035
2036        if let Some(contact_id) = group_changes.added_removed_id {
2037            param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
2038        }
2039
2040        save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
2041        let save_mime_modified = save_mime_modified && parts.peek().is_none();
2042
2043        let ephemeral_timestamp = if in_fresh {
2044            0
2045        } else {
2046            match ephemeral_timer {
2047                EphemeralTimer::Disabled => 0,
2048                EphemeralTimer::Enabled { duration } => {
2049                    mime_parser.timestamp_rcvd.saturating_add(duration.into())
2050                }
2051            }
2052        };
2053
2054        if let PreMessageMode::Pre {
2055            metadata: Some(metadata),
2056            ..
2057        } = &mime_parser.pre_message
2058        {
2059            param.apply_post_msg_metadata(metadata);
2060        };
2061
2062        // If you change which information is skipped if the message is trashed,
2063        // also change `MsgId::trash()` and `delete_expired_messages()`
2064        let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2065
2066        let row_id = context
2067            .sql
2068            .call_write(|conn| {
2069                let mut stmt = conn.prepare_cached(
2070            r#"
2071INSERT INTO msgs
2072  (
2073    id,
2074    rfc724_mid, pre_rfc724_mid, chat_id,
2075    from_id, to_id, timestamp, timestamp_sent, 
2076    timestamp_rcvd, type, state, msgrmsg, 
2077    txt, txt_normalized, subject, param, hidden,
2078    bytes, mime_headers, mime_compressed, mime_in_reply_to,
2079    mime_references, mime_modified, error, ephemeral_timer,
2080    ephemeral_timestamp, download_state, hop_info
2081  )
2082  VALUES (
2083    ?,
2084    ?, ?, ?, ?, ?,
2085    ?, ?, ?, ?,
2086    ?, ?, ?, ?,
2087    ?, ?, ?, ?, ?, 1,
2088    ?, ?, ?, ?,
2089    ?, ?, ?, ?
2090  )
2091ON CONFLICT (id) DO UPDATE
2092SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
2093    from_id=excluded.from_id, to_id=excluded.to_id, timestamp_sent=excluded.timestamp_sent,
2094    type=excluded.type, state=max(state,excluded.state), msgrmsg=excluded.msgrmsg,
2095    txt=excluded.txt, txt_normalized=excluded.txt_normalized, subject=excluded.subject,
2096    param=excluded.param,
2097    hidden=excluded.hidden,bytes=excluded.bytes, mime_headers=excluded.mime_headers,
2098    mime_compressed=excluded.mime_compressed, mime_in_reply_to=excluded.mime_in_reply_to,
2099    mime_references=excluded.mime_references, mime_modified=excluded.mime_modified, error=excluded.error, ephemeral_timer=excluded.ephemeral_timer,
2100    ephemeral_timestamp=excluded.ephemeral_timestamp, download_state=excluded.download_state, hop_info=excluded.hop_info
2101RETURNING id
2102"#)?;
2103                let row_id: MsgId = stmt.query_row(params![
2104                    replace_msg_id,
2105                    if let PreMessageMode::Pre {post_msg_rfc724_mid, ..} = &mime_parser.pre_message {
2106                        post_msg_rfc724_mid
2107                    } else { rfc724_mid_orig },
2108                    if let PreMessageMode::Pre {..} = &mime_parser.pre_message {
2109                        rfc724_mid_orig
2110                    } else { "" },
2111                    if trash { DC_CHAT_ID_TRASH } else { chat_id },
2112                    if trash { ContactId::UNDEFINED } else { from_id },
2113                    if trash { ContactId::UNDEFINED } else { to_id },
2114                    sort_timestamp,
2115                    if trash { 0 } else { mime_parser.timestamp_sent },
2116                    if trash { 0 } else { mime_parser.timestamp_rcvd },
2117                    if trash {
2118                        Viewtype::Unknown
2119                    } else if let PreMessageMode::Pre {..} = mime_parser.pre_message {
2120                        Viewtype::Text
2121                    } else { typ },
2122                    if trash { MessageState::Undefined } else { state },
2123                    if trash { MessengerMessage::No } else { is_dc_message },
2124                    if trash || hidden { "" } else { msg },
2125                    if trash || hidden { None } else { normalize_text(msg) },
2126                    if trash || hidden { "" } else { &subject },
2127                    if trash {
2128                        "".to_string()
2129                    } else {
2130                        param.to_string()
2131                    },
2132                    !trash && hidden,
2133                    if trash { 0 } else { part.bytes as isize },
2134                    if save_mime_modified && !(trash || hidden) {
2135                        mime_headers.clone()
2136                    } else {
2137                        Vec::new()
2138                    },
2139                    if trash { "" } else { mime_in_reply_to },
2140                    if trash { "" } else { mime_references },
2141                    !trash && save_mime_modified,
2142                    if trash { "" } else { part.error.as_deref().unwrap_or_default() },
2143                    if trash { 0 } else { ephemeral_timer.to_u32() },
2144                    if trash { 0 } else { ephemeral_timestamp },
2145                    if trash {
2146                        DownloadState::Done
2147                    } else if mime_parser.decrypting_failed {
2148                        DownloadState::Undecipherable
2149                    } else if let PreMessageMode::Pre {..} = mime_parser.pre_message {
2150                        DownloadState::Available
2151                    } else {
2152                        DownloadState::Done
2153                    },
2154                    if trash { "" } else { &mime_parser.hop_info },
2155                ],
2156                |row| {
2157                    let msg_id: MsgId = row.get(0)?;
2158                    Ok(msg_id)
2159                }
2160                )?;
2161                Ok(row_id)
2162            })
2163            .await?;
2164
2165        // We only replace placeholder with a first part,
2166        // afterwards insert additional parts.
2167        replace_msg_id = None;
2168
2169        ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2170        created_db_entries.push(row_id);
2171    }
2172
2173    // Maybe set logging xdc and add gossip topics for webxdcs.
2174    for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2175        // check if any part contains a webxdc topic id
2176        if part.typ == Viewtype::Webxdc {
2177            if let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic) {
2178                // default encoding of topic ids is `hex`.
2179                let mut topic_raw = [0u8; 32];
2180                BASE32_NOPAD
2181                    .decode_mut(topic.to_ascii_uppercase().as_bytes(), &mut topic_raw)
2182                    .map_err(|e| e.error)
2183                    .context("Wrong gossip topic header")?;
2184
2185                let topic = TopicId::from_bytes(topic_raw);
2186                insert_topic_stub(context, *msg_id, topic).await?;
2187            } else {
2188                warn!(context, "webxdc doesn't have a gossip topic")
2189            }
2190        }
2191
2192        maybe_set_logging_xdc_inner(
2193            context,
2194            part.typ,
2195            chat_id,
2196            part.param.get(Param::Filename),
2197            *msg_id,
2198        )
2199        .await?;
2200    }
2201
2202    if let Some(replace_msg_id) = replace_msg_id {
2203        // Trash the "replace" placeholder with a message that has no parts. If it has the original
2204        // "Message-ID", mark the placeholder for server-side deletion so as if the user deletes the
2205        // fully downloaded message later, the server-side deletion is issued.
2206        let on_server = rfc724_mid == rfc724_mid_orig;
2207        replace_msg_id.trash(context, on_server).await?;
2208    }
2209
2210    let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2211        Some(addr) => context.is_self_addr(addr).await?,
2212        None => true,
2213    };
2214    if unarchive {
2215        chat_id.unarchive_if_not_muted(context, state).await?;
2216    }
2217
2218    info!(
2219        context,
2220        "Message has {icnt} parts and is assigned to chat #{chat_id}."
2221    );
2222
2223    if !chat_id.is_trash() && !hidden {
2224        let mut chat = Chat::load_from_db(context, chat_id).await?;
2225        let mut update_param = false;
2226
2227        // In contrast to most other update-timestamps,
2228        // use `sort_timestamp` instead of `sent_timestamp` for the subject-timestamp comparison.
2229        // This way, `LastSubject` actually refers to the most recent message _shown_ in the chat.
2230        if chat
2231            .param
2232            .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2233        {
2234            // write the last subject even if empty -
2235            // otherwise a reply may get an outdated subject.
2236            let subject = mime_parser.get_subject().unwrap_or_default();
2237
2238            chat.param.set(Param::LastSubject, subject);
2239            update_param = true;
2240        }
2241
2242        if chat.is_unpromoted() {
2243            chat.param.remove(Param::Unpromoted);
2244            update_param = true;
2245        }
2246        if update_param {
2247            chat.update_param(context).await?;
2248        }
2249    }
2250
2251    // Normally outgoing MDNs sent by us never appear in mailboxes, but Gmail saves all
2252    // outgoing messages, including MDNs, to the Sent folder. If we detect such saved MDN,
2253    // delete it.
2254    let needs_delete_job =
2255        !mime_parser.incoming && is_mdn && is_dc_message == MessengerMessage::Yes;
2256
2257    Ok(ReceivedMsg {
2258        chat_id,
2259        state,
2260        hidden,
2261        sort_timestamp,
2262        msg_ids: created_db_entries,
2263        needs_delete_job,
2264    })
2265}
2266
2267/// Checks for "Chat-Edit" and "Chat-Delete" headers,
2268/// and edits/deletes existing messages accordingly.
2269///
2270/// Returns `true` if this message is an edit/deletion request.
2271async fn handle_edit_delete(
2272    context: &Context,
2273    mime_parser: &MimeMessage,
2274    from_id: ContactId,
2275) -> Result<()> {
2276    if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2277        if let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? {
2278            if let Some(mut original_msg) =
2279                Message::load_from_db_optional(context, original_msg_id).await?
2280            {
2281                if original_msg.from_id == from_id {
2282                    if let Some(part) = mime_parser.parts.first() {
2283                        let edit_msg_showpadlock = part
2284                            .param
2285                            .get_bool(Param::GuaranteeE2ee)
2286                            .unwrap_or_default();
2287                        if edit_msg_showpadlock || !original_msg.get_showpadlock() {
2288                            let new_text =
2289                                part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2290                            chat::save_text_edit_to_db(context, &mut original_msg, new_text)
2291                                .await?;
2292                        } else {
2293                            warn!(context, "Edit message: Not encrypted.");
2294                        }
2295                    }
2296                } else {
2297                    warn!(context, "Edit message: Bad sender.");
2298                }
2299            } else {
2300                warn!(context, "Edit message: Database entry does not exist.");
2301            }
2302        } else {
2303            warn!(
2304                context,
2305                "Edit message: rfc724_mid {rfc724_mid:?} not found."
2306            );
2307        }
2308    } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
2309        && let Some(part) = mime_parser.parts.first()
2310    {
2311        // See `message::delete_msgs_ex()`, unlike edit requests, DC doesn't send unencrypted
2312        // deletion requests, so there's no need to support them.
2313        if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
2314            let mut modified_chat_ids = HashSet::new();
2315            let mut msg_ids = Vec::new();
2316
2317            let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2318            for rfc724_mid in rfc724_mid_vec {
2319                if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
2320                    if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
2321                        if msg.from_id == from_id {
2322                            message::delete_msg_locally(context, &msg).await?;
2323                            msg_ids.push(msg.id);
2324                            modified_chat_ids.insert(msg.chat_id);
2325                        } else {
2326                            warn!(context, "Delete message: Bad sender.");
2327                        }
2328                    } else {
2329                        warn!(context, "Delete message: Database entry does not exist.");
2330                    }
2331                } else {
2332                    warn!(context, "Delete message: {rfc724_mid:?} not found.");
2333                }
2334            }
2335            message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2336        } else {
2337            warn!(context, "Delete message: Not encrypted.");
2338        }
2339    }
2340    Ok(())
2341}
2342
2343async fn handle_post_message(
2344    context: &Context,
2345    mime_parser: &MimeMessage,
2346    from_id: ContactId,
2347    state: MessageState,
2348) -> Result<()> {
2349    let PreMessageMode::Post = &mime_parser.pre_message else {
2350        return Ok(());
2351    };
2352    // if Pre-Message exist, replace attachment
2353    // only replacing attachment ensures that doesn't overwrite the text if it was edited before.
2354    let rfc724_mid = mime_parser
2355        .get_rfc724_mid()
2356        .context("expected Post-Message to have a message id")?;
2357
2358    let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
2359        warn!(
2360            context,
2361            "handle_post_message: {rfc724_mid}: Database entry does not exist."
2362        );
2363        return Ok(());
2364    };
2365    let Some(original_msg) = Message::load_from_db_optional(context, msg_id).await? else {
2366        // else: message is processed like a normal message
2367        warn!(
2368            context,
2369            "handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
2370        );
2371        return Ok(());
2372    };
2373    let Some(part) = mime_parser.parts.first() else {
2374        return Ok(());
2375    };
2376
2377    // Do nothing if safety checks fail, the worst case is the message modifies the chat if the
2378    // sender is a member.
2379    if from_id != original_msg.from_id {
2380        warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
2381        return Ok(());
2382    }
2383    let post_msg_showpadlock = part
2384        .param
2385        .get_bool(Param::GuaranteeE2ee)
2386        .unwrap_or_default();
2387    if !post_msg_showpadlock && original_msg.get_showpadlock() {
2388        warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
2389        return Ok(());
2390    }
2391
2392    if !part.typ.has_file() {
2393        warn!(
2394            context,
2395            "handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
2396        );
2397        return Ok(());
2398    }
2399
2400    let mut new_params = original_msg.param.clone();
2401    new_params
2402        .merge_in_params(part.param.clone())
2403        .remove(Param::PostMessageFileBytes)
2404        .remove(Param::PostMessageViewtype);
2405    // Don't update `chat_id`: even if it differs from pre-message's one somehow so the result
2406    // depends on message download order, we don't want messages jumping across chats.
2407    context
2408        .sql
2409        .execute(
2410            "
2411UPDATE msgs SET param=?, type=?, bytes=?, error=?, state=max(state,?), download_state=?
2412WHERE id=?
2413            ",
2414            (
2415                new_params.to_string(),
2416                part.typ,
2417                part.bytes as isize,
2418                part.error.as_deref().unwrap_or_default(),
2419                state,
2420                DownloadState::Done as u32,
2421                original_msg.id,
2422            ),
2423        )
2424        .await?;
2425    context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2426
2427    Ok(())
2428}
2429
2430async fn tweak_sort_timestamp(
2431    context: &Context,
2432    mime_parser: &mut MimeMessage,
2433    silent: bool,
2434    chat_id: ChatId,
2435    sort_timestamp: i64,
2436) -> Result<i64> {
2437    // Ensure replies to messages are sorted after the parent message.
2438    //
2439    // This is useful in a case where sender clocks are not
2440    // synchronized and parent message has a Date: header with a
2441    // timestamp higher than reply timestamp.
2442    //
2443    // This does not help if parent message arrives later than the
2444    // reply.
2445    let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2446    let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2447        std::cmp::max(sort_timestamp, parent_timestamp)
2448    });
2449
2450    // If the message should be silent,
2451    // set the timestamp to be no more than the same as last message
2452    // so that the chat is not sorted to the top of the chatlist.
2453    if silent {
2454        let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2455            t
2456        } else {
2457            chat_id.created_timestamp(context).await?
2458        };
2459        sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2460    }
2461    Ok(sort_timestamp)
2462}
2463
2464/// Saves attached locations to the database.
2465///
2466/// Emits an event if at least one new location was added.
2467async fn save_locations(
2468    context: &Context,
2469    mime_parser: &MimeMessage,
2470    chat_id: ChatId,
2471    from_id: ContactId,
2472    msg_id: MsgId,
2473) -> Result<()> {
2474    if chat_id.is_special() {
2475        // Do not save locations for trashed messages.
2476        return Ok(());
2477    }
2478
2479    let mut send_event = false;
2480
2481    if let Some(message_kml) = &mime_parser.message_kml
2482        && let Some(newest_location_id) =
2483            location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2484    {
2485        location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2486        send_event = true;
2487    }
2488
2489    if let Some(location_kml) = &mime_parser.location_kml
2490        && let Some(addr) = &location_kml.addr
2491    {
2492        let contact = Contact::get_by_id(context, from_id).await?;
2493        if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2494            if location::save(context, chat_id, from_id, &location_kml.locations, false)
2495                .await?
2496                .is_some()
2497            {
2498                send_event = true;
2499            }
2500        } else {
2501            warn!(
2502                context,
2503                "Address in location.kml {:?} is not the same as the sender address {:?}.",
2504                addr,
2505                contact.get_addr()
2506            );
2507        }
2508    }
2509    if send_event {
2510        context.emit_location_changed(Some(from_id)).await?;
2511    }
2512    Ok(())
2513}
2514
2515async fn lookup_chat_by_reply(
2516    context: &Context,
2517    mime_parser: &MimeMessage,
2518    parent: &Message,
2519) -> Result<Option<(ChatId, Blocked)>> {
2520    // If the message is encrypted and has group ID,
2521    // lookup by reply should never be needed
2522    // as we can directly assign the message to the chat
2523    // by its group ID.
2524    ensure_and_debug_assert!(
2525        mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2526        "Encrypted message has group ID {}",
2527        mime_parser.get_chat_group_id().unwrap_or_default(),
2528    );
2529
2530    // Try to assign message to the same chat as the parent message.
2531    let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2532        return Ok(None);
2533    };
2534
2535    // If this was a private message just to self, it was probably a private reply.
2536    // It should not go into the group then, but into the private chat.
2537    if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2538        return Ok(None);
2539    }
2540
2541    // If the parent chat is a 1:1 chat, and the sender added
2542    // a new person to TO/CC, then the message should not go to the 1:1 chat, but to a
2543    // newly created ad-hoc group.
2544    let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2545    if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2546        return Ok(None);
2547    }
2548
2549    // Do not assign unencrypted messages to encrypted chats.
2550    if parent_chat.is_encrypted(context).await? && !mime_parser.was_encrypted() {
2551        return Ok(None);
2552    }
2553
2554    info!(
2555        context,
2556        "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2557    );
2558    Ok(Some((parent_chat.id, parent_chat.blocked)))
2559}
2560
2561async fn lookup_or_create_adhoc_group(
2562    context: &Context,
2563    mime_parser: &MimeMessage,
2564    to_ids: &[Option<ContactId>],
2565    allow_creation: bool,
2566    create_blocked: Blocked,
2567) -> Result<Option<(ChatId, Blocked, bool)>> {
2568    if mime_parser.decrypting_failed {
2569        warn!(
2570            context,
2571            "Not creating ad-hoc group for message that cannot be decrypted."
2572        );
2573        return Ok(None);
2574    }
2575
2576    // Lookup address-contact by the From address.
2577    let fingerprint = None;
2578    let find_key_contact_by_addr = false;
2579    let prevent_rename = should_prevent_rename(mime_parser);
2580    let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2581        context,
2582        &mime_parser.from,
2583        fingerprint,
2584        prevent_rename,
2585        find_key_contact_by_addr,
2586    )
2587    .await?
2588    .context("Cannot lookup address-contact by the From field")?;
2589
2590    let grpname = mime_parser
2591        .get_header(HeaderDef::ChatGroupName)
2592        .map(|s| s.to_string())
2593        .unwrap_or_else(|| {
2594            mime_parser
2595                .get_subject()
2596                .map(|s| remove_subject_prefix(&s))
2597                .unwrap_or_else(|| "👥📧".to_string())
2598        });
2599    let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2600    let mut contact_ids = BTreeSet::<ContactId>::from_iter(to_ids.iter().copied());
2601    contact_ids.insert(from_id);
2602    let trans_fn = |t: &mut rusqlite::Transaction| {
2603        t.pragma_update(None, "query_only", "0")?;
2604        t.execute(
2605            "CREATE TEMP TABLE temp.contacts (
2606                id INTEGER PRIMARY KEY
2607            ) STRICT",
2608            (),
2609        )
2610        .context("CREATE TEMP TABLE temp.contacts")?;
2611        let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2612        for &id in &contact_ids {
2613            stmt.execute((id,)).context("INSERT INTO temp.contacts")?;
2614        }
2615        let val = t
2616            .query_row(
2617                "SELECT c.id, c.blocked
2618                FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2619                WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2620                AND (SELECT COUNT(*) FROM chats_contacts
2621                     WHERE chat_id=c.id
2622                     AND add_timestamp >= remove_timestamp)=?
2623                AND (SELECT COUNT(*) FROM chats_contacts
2624                     WHERE chat_id=c.id
2625                     AND contact_id NOT IN (SELECT id FROM temp.contacts)
2626                     AND add_timestamp >= remove_timestamp)=0
2627                ORDER BY m.timestamp DESC",
2628                (&grpname, contact_ids.len()),
2629                |row| {
2630                    let id: ChatId = row.get(0)?;
2631                    let blocked: Blocked = row.get(1)?;
2632                    Ok((id, blocked))
2633                },
2634            )
2635            .optional()
2636            .context("Select chat with matching name and members")?;
2637        t.execute("DROP TABLE temp.contacts", ())
2638            .context("DROP TABLE temp.contacts")?;
2639        Ok(val)
2640    };
2641    let query_only = true;
2642    if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2643        info!(
2644            context,
2645            "Assigning message to ad-hoc group {chat_id} with matching name and members."
2646        );
2647        return Ok(Some((chat_id, blocked, false)));
2648    }
2649    if !allow_creation {
2650        return Ok(None);
2651    }
2652    Ok(create_adhoc_group(
2653        context,
2654        mime_parser,
2655        create_blocked,
2656        from_id,
2657        &to_ids,
2658        &grpname,
2659    )
2660    .await
2661    .context("Could not create ad hoc group")?
2662    .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2663}
2664
2665/// If this method returns true, the message shall be assigned to the 1:1 chat with the sender.
2666/// If it returns false, it shall be assigned to the parent chat.
2667async fn is_probably_private_reply(
2668    context: &Context,
2669    mime_parser: &MimeMessage,
2670    parent_chat_id: ChatId,
2671) -> Result<bool> {
2672    // Message cannot be a private reply if it has an explicit Chat-Group-ID header.
2673    if mime_parser.get_chat_group_id().is_some() {
2674        return Ok(false);
2675    }
2676
2677    // Usually we don't want to show private replies in the parent chat, but in the
2678    // 1:1 chat with the sender.
2679    //
2680    // There is one exception: Classical MUA replies to two-member groups
2681    // should be assigned to the group chat. We restrict this exception to classical emails, as chat-group-messages
2682    // contain a Chat-Group-Id header and can be sorted into the correct chat this way.
2683
2684    if mime_parser.recipients.len() != 1 {
2685        return Ok(false);
2686    }
2687
2688    if !mime_parser.has_chat_version() {
2689        let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2690        if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2691            return Ok(false);
2692        }
2693    }
2694
2695    Ok(true)
2696}
2697
2698/// This function tries to extract the group-id from the message and create a new group
2699/// chat with this ID. If there is no group-id and there are more
2700/// than two members, a new ad hoc group is created.
2701///
2702/// On success the function returns the created (chat_id, chat_blocked) tuple.
2703async fn create_group(
2704    context: &Context,
2705    mime_parser: &mut MimeMessage,
2706    create_blocked: Blocked,
2707    from_id: ContactId,
2708    to_ids: &[Option<ContactId>],
2709    past_ids: &[Option<ContactId>],
2710    grpid: &str,
2711) -> Result<Option<(ChatId, Blocked)>> {
2712    let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2713    let mut chat_id = None;
2714    let mut chat_id_blocked = Default::default();
2715
2716    if chat_id.is_none()
2717            && !mime_parser.is_mailinglist_message()
2718            && !grpid.is_empty()
2719            && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2720            // otherwise, a pending "quit" message may pop up
2721            && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2722    {
2723        // Group does not exist but should be created.
2724        let grpname = mime_parser
2725            .get_header(HeaderDef::ChatGroupName)
2726            .context("Chat-Group-Name vanished")?
2727            // Workaround for the "Space added before long group names after MIME
2728            // serialization/deserialization #3650" issue. DC itself never creates group names with
2729            // leading/trailing whitespace.
2730            .trim();
2731        let new_chat_id = ChatId::create_multiuser_record(
2732            context,
2733            Chattype::Group,
2734            grpid,
2735            grpname,
2736            create_blocked,
2737            None,
2738            mime_parser.timestamp_sent,
2739        )
2740        .await
2741        .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2742
2743        chat_id = Some(new_chat_id);
2744        chat_id_blocked = create_blocked;
2745
2746        // Create initial member list.
2747        if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2748            let mut new_to_ids = to_ids.to_vec();
2749            if !new_to_ids.contains(&Some(from_id)) {
2750                new_to_ids.insert(0, Some(from_id));
2751                chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2752            }
2753
2754            update_chats_contacts_timestamps(
2755                context,
2756                new_chat_id,
2757                None,
2758                &new_to_ids,
2759                past_ids,
2760                &chat_group_member_timestamps,
2761            )
2762            .await?;
2763        } else {
2764            let mut members = vec![ContactId::SELF];
2765            if !from_id.is_special() {
2766                members.push(from_id);
2767            }
2768            members.extend(to_ids_flat);
2769
2770            // Add all members with 0 timestamp
2771            // because we don't know the real timestamp of their addition.
2772            // This will allow other senders who support
2773            // `Chat-Group-Member-Timestamps` to overwrite
2774            // timestamps later.
2775            let timestamp = 0;
2776
2777            chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2778        }
2779
2780        context.emit_event(EventType::ChatModified(new_chat_id));
2781        chatlist_events::emit_chatlist_changed(context);
2782        chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2783    }
2784
2785    if let Some(chat_id) = chat_id {
2786        Ok(Some((chat_id, chat_id_blocked)))
2787    } else if mime_parser.decrypting_failed {
2788        // It is possible that the message was sent to a valid,
2789        // yet unknown group, which was rejected because
2790        // Chat-Group-Name, which is in the encrypted part, was
2791        // not found. We can't create a properly named group in
2792        // this case, so assign error message to 1:1 chat with the
2793        // sender instead.
2794        Ok(None)
2795    } else {
2796        // The message was decrypted successfully, but contains a late "quit" or otherwise
2797        // unwanted message.
2798        info!(context, "Message belongs to unwanted group (TRASH).");
2799        Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2800    }
2801}
2802
2803async fn update_chats_contacts_timestamps(
2804    context: &Context,
2805    chat_id: ChatId,
2806    ignored_id: Option<ContactId>,
2807    to_ids: &[Option<ContactId>],
2808    past_ids: &[Option<ContactId>],
2809    chat_group_member_timestamps: &[i64],
2810) -> Result<bool> {
2811    let expected_timestamps_count = to_ids.len() + past_ids.len();
2812
2813    if chat_group_member_timestamps.len() != expected_timestamps_count {
2814        warn!(
2815            context,
2816            "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2817            chat_group_member_timestamps.len(),
2818            expected_timestamps_count
2819        );
2820        return Ok(false);
2821    }
2822
2823    let mut modified = false;
2824
2825    context
2826        .sql
2827        .transaction(|transaction| {
2828            let mut add_statement = transaction.prepare(
2829                "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2830                 VALUES                     (?1,      ?2,         ?3)
2831                 ON CONFLICT (chat_id, contact_id)
2832                 DO
2833                   UPDATE SET add_timestamp=?3
2834                   WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2835            )?;
2836
2837            for (contact_id, ts) in iter::zip(
2838                to_ids.iter(),
2839                chat_group_member_timestamps.iter().take(to_ids.len()),
2840            ) {
2841                if let Some(contact_id) = contact_id
2842                    && Some(*contact_id) != ignored_id
2843                {
2844                    // It could be that member was already added,
2845                    // but updated addition timestamp
2846                    // is also a modification worth notifying about.
2847                    modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2848                }
2849            }
2850
2851            let mut remove_statement = transaction.prepare(
2852                "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2853                 VALUES                     (?1,      ?2,         ?3)
2854                 ON CONFLICT (chat_id, contact_id)
2855                 DO
2856                   UPDATE SET remove_timestamp=?3
2857                   WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2858            )?;
2859
2860            for (contact_id, ts) in iter::zip(
2861                past_ids.iter(),
2862                chat_group_member_timestamps.iter().skip(to_ids.len()),
2863            ) {
2864                if let Some(contact_id) = contact_id {
2865                    // It could be that member was already removed,
2866                    // but updated removal timestamp
2867                    // is also a modification worth notifying about.
2868                    modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2869                }
2870            }
2871
2872            Ok(())
2873        })
2874        .await?;
2875
2876    Ok(modified)
2877}
2878
2879/// The return type of [apply_group_changes].
2880/// Contains information on which system messages
2881/// should be shown in the chat.
2882#[derive(Default)]
2883struct GroupChangesInfo {
2884    /// Optional: A better message that should replace the original system message.
2885    /// If this is an empty string, the original system message should be trashed.
2886    better_msg: Option<String>,
2887    /// Added/removed contact `better_msg` refers to.
2888    added_removed_id: Option<ContactId>,
2889    /// If true, the user should not be notified about the group change.
2890    silent: bool,
2891    /// A list of additional group changes messages that should be shown in the chat.
2892    extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
2893}
2894
2895/// Apply group member list, name, avatar and protection status changes from the MIME message.
2896///
2897/// Returns [GroupChangesInfo].
2898///
2899/// * `to_ids` - contents of the `To` and `Cc` headers.
2900/// * `past_ids` - contents of the `Chat-Group-Past-Members` header.
2901async fn apply_group_changes(
2902    context: &Context,
2903    mime_parser: &mut MimeMessage,
2904    chat: &mut Chat,
2905    from_id: ContactId,
2906    to_ids: &[Option<ContactId>],
2907    past_ids: &[Option<ContactId>],
2908) -> Result<GroupChangesInfo> {
2909    let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2910    ensure!(chat.typ == Chattype::Group);
2911    ensure!(!chat.id.is_special());
2912
2913    let mut send_event_chat_modified = false;
2914    let (mut removed_id, mut added_id) = (None, None);
2915    let mut better_msg = None;
2916    let mut silent = false;
2917    let chat_contacts =
2918        HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
2919    let is_from_in_chat =
2920        !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
2921
2922    if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2923        if !is_from_in_chat {
2924            better_msg = Some(String::new());
2925        } else if let Some(removed_fpr) =
2926            mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr)
2927        {
2928            removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
2929        } else {
2930            // Removal message sent by a legacy Delta Chat client.
2931            removed_id =
2932                lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?;
2933        }
2934        if let Some(id) = removed_id {
2935            better_msg = if id == from_id {
2936                silent = true;
2937                Some(stock_str::msg_group_left_local(context, from_id).await)
2938            } else {
2939                Some(stock_str::msg_del_member_local(context, id, from_id).await)
2940            };
2941        } else {
2942            warn!(context, "Removed {removed_addr:?} has no contact id.")
2943        }
2944    } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2945        if !is_from_in_chat {
2946            better_msg = Some(String::new());
2947        } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
2948            if !chat_contacts.contains(&from_id) {
2949                chat::add_to_chat_contacts_table(
2950                    context,
2951                    mime_parser.timestamp_sent,
2952                    chat.id,
2953                    &[from_id],
2954                )
2955                .await?;
2956            }
2957
2958            // TODO: if gossiped keys contain the same address multiple times,
2959            // we may lookup the wrong contact.
2960            // This can be fixed by looking at ChatGroupMemberAddedFpr,
2961            // just like we look at ChatGroupMemberRemovedFpr.
2962            // The result of the error is that info message
2963            // may contain display name of the wrong contact.
2964            let fingerprint = key.public_key.dc_fingerprint().hex();
2965            if let Some(contact_id) =
2966                lookup_key_contact_by_fingerprint(context, &fingerprint).await?
2967            {
2968                added_id = Some(contact_id);
2969                better_msg =
2970                    Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
2971            } else {
2972                warn!(context, "Added {added_addr:?} has no contact id.");
2973            }
2974        } else {
2975            warn!(context, "Added {added_addr:?} has no gossiped key.");
2976        }
2977    }
2978
2979    if is_from_in_chat {
2980        apply_chat_name_and_avatar_changes(
2981            context,
2982            mime_parser,
2983            from_id,
2984            chat,
2985            &mut send_event_chat_modified,
2986            &mut better_msg,
2987        )
2988        .await?;
2989
2990        if chat.member_list_is_stale(context).await? {
2991            info!(context, "Member list is stale.");
2992            let mut new_members: HashSet<ContactId> =
2993                HashSet::from_iter(to_ids_flat.iter().copied());
2994            new_members.insert(ContactId::SELF);
2995            if !from_id.is_special() {
2996                new_members.insert(from_id);
2997            }
2998
2999            context
3000                .sql
3001                .transaction(|transaction| {
3002                    // Remove all contacts and tombstones.
3003                    transaction.execute(
3004                        "DELETE FROM chats_contacts
3005                         WHERE chat_id=?",
3006                        (chat.id,),
3007                    )?;
3008
3009                    // Insert contacts with default timestamps of 0.
3010                    let mut statement = transaction.prepare(
3011                        "INSERT INTO chats_contacts (chat_id, contact_id)
3012                         VALUES                     (?,       ?)",
3013                    )?;
3014                    for contact_id in &new_members {
3015                        statement.execute((chat.id, contact_id))?;
3016                    }
3017
3018                    Ok(())
3019                })
3020                .await?;
3021            send_event_chat_modified = true;
3022        } else if let Some(ref chat_group_member_timestamps) =
3023            mime_parser.chat_group_member_timestamps()
3024        {
3025            send_event_chat_modified |= update_chats_contacts_timestamps(
3026                context,
3027                chat.id,
3028                Some(from_id),
3029                to_ids,
3030                past_ids,
3031                chat_group_member_timestamps,
3032            )
3033            .await?;
3034        } else {
3035            let mut new_members: HashSet<ContactId>;
3036            // True if a Delta Chat client has explicitly and really added our primary address to an
3037            // already existing group.
3038            let self_added =
3039                if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3040                    addr_cmp(&context.get_primary_self_addr().await?, added_addr)
3041                        && !chat_contacts.contains(&ContactId::SELF)
3042                } else {
3043                    false
3044                };
3045            if self_added {
3046                new_members = HashSet::from_iter(to_ids_flat.iter().copied());
3047                new_members.insert(ContactId::SELF);
3048                if !from_id.is_special() {
3049                    new_members.insert(from_id);
3050                }
3051            } else {
3052                new_members = chat_contacts.clone();
3053            }
3054
3055            // Allow non-Delta Chat MUAs to add members.
3056            if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
3057                // Don't delete any members locally, but instead add absent ones to provide group
3058                // membership consistency for all members:
3059                new_members.extend(to_ids_flat.iter());
3060            }
3061
3062            // Apply explicit addition if any.
3063            if let Some(added_id) = added_id {
3064                new_members.insert(added_id);
3065            }
3066
3067            // Apply explicit removal if any.
3068            if let Some(removed_id) = removed_id {
3069                new_members.remove(&removed_id);
3070            }
3071
3072            if new_members != chat_contacts {
3073                chat::update_chat_contacts_table(
3074                    context,
3075                    mime_parser.timestamp_sent,
3076                    chat.id,
3077                    &new_members,
3078                )
3079                .await?;
3080                send_event_chat_modified = true;
3081            }
3082        }
3083
3084        chat.id
3085            .update_timestamp(
3086                context,
3087                Param::MemberListTimestamp,
3088                mime_parser.timestamp_sent,
3089            )
3090            .await?;
3091    }
3092
3093    let new_chat_contacts = HashSet::<ContactId>::from_iter(
3094        chat::get_chat_contacts(context, chat.id)
3095            .await?
3096            .iter()
3097            .copied(),
3098    );
3099
3100    // These are for adding info messages about implicit membership changes.
3101    let mut added_ids: HashSet<ContactId> = new_chat_contacts
3102        .difference(&chat_contacts)
3103        .copied()
3104        .collect();
3105    let mut removed_ids: HashSet<ContactId> = chat_contacts
3106        .difference(&new_chat_contacts)
3107        .copied()
3108        .collect();
3109
3110    if let Some(added_id) = added_id
3111        && !added_ids.remove(&added_id)
3112        && added_id != ContactId::SELF
3113    {
3114        // No-op "Member added" message. An exception is self-addition messages because they at
3115        // least must be shown when a chat is created on our side.
3116        info!(context, "No-op 'Member added' message (TRASH)");
3117        better_msg = Some(String::new());
3118    }
3119    if let Some(removed_id) = removed_id {
3120        removed_ids.remove(&removed_id);
3121    }
3122    let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
3123        && new_chat_contacts.contains(&ContactId::SELF)
3124    {
3125        Vec::new()
3126    } else {
3127        group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
3128    };
3129
3130    if send_event_chat_modified {
3131        context.emit_event(EventType::ChatModified(chat.id));
3132        chatlist_events::emit_chatlist_item_changed(context, chat.id);
3133    }
3134    Ok(GroupChangesInfo {
3135        better_msg,
3136        added_removed_id: if added_id.is_some() {
3137            added_id
3138        } else {
3139            removed_id
3140        },
3141        silent,
3142        extra_msgs: group_changes_msgs,
3143    })
3144}
3145
3146/// Applies incoming changes to the group's or broadcast channel's name and avatar.
3147///
3148/// - `send_event_chat_modified` is set to `true` if ChatModified event should be sent
3149/// - `better_msg` is filled with an info message about name change, if necessary
3150async fn apply_chat_name_and_avatar_changes(
3151    context: &Context,
3152    mime_parser: &MimeMessage,
3153    from_id: ContactId,
3154    chat: &mut Chat,
3155    send_event_chat_modified: &mut bool,
3156    better_msg: &mut Option<String>,
3157) -> Result<()> {
3158    // ========== Apply chat name changes ==========
3159
3160    let group_name_timestamp = mime_parser
3161        .get_header(HeaderDef::ChatGroupNameTimestamp)
3162        .and_then(|s| s.parse::<i64>().ok());
3163
3164    if let Some(old_name) = mime_parser
3165        .get_header(HeaderDef::ChatGroupNameChanged)
3166        .map(|s| s.trim())
3167        .or(match group_name_timestamp {
3168            Some(0) => None,
3169            Some(_) => Some(chat.name.as_str()),
3170            None => None,
3171        })
3172        && let Some(grpname) = mime_parser
3173            .get_header(HeaderDef::ChatGroupName)
3174            .map(|grpname| grpname.trim())
3175            .filter(|grpname| grpname.len() < 200)
3176    {
3177        let grpname = &sanitize_single_line(grpname);
3178
3179        let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3180        let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3181        // To provide group name consistency, compare names if timestamps are equal.
3182        if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3183            && chat
3184                .id
3185                .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3186                .await?
3187            && grpname != &chat.name
3188        {
3189            info!(context, "Updating grpname for chat {}.", chat.id);
3190            context
3191                .sql
3192                .execute(
3193                    "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3194                    (grpname, normalize_text(grpname), chat.id),
3195                )
3196                .await?;
3197            *send_event_chat_modified = true;
3198        }
3199        if mime_parser
3200            .get_header(HeaderDef::ChatGroupNameChanged)
3201            .is_some()
3202        {
3203            let old_name = &sanitize_single_line(old_name);
3204            better_msg
3205                .get_or_insert(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
3206        }
3207    }
3208
3209    // ========== Apply chat avatar changes ==========
3210
3211    if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3212        && value == "group-avatar-changed"
3213        && let Some(avatar_action) = &mime_parser.group_avatar
3214    {
3215        // this is just an explicit message containing the group-avatar,
3216        // apart from that, the group-avatar is send along with various other messages
3217        better_msg.get_or_insert(match avatar_action {
3218            AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
3219            AvatarAction::Change(_) => stock_str::msg_grp_img_changed(context, from_id).await,
3220        });
3221    }
3222
3223    if let Some(avatar_action) = &mime_parser.group_avatar {
3224        info!(context, "Group-avatar change for {}.", chat.id);
3225        if chat
3226            .param
3227            .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3228        {
3229            match avatar_action {
3230                AvatarAction::Change(profile_image) => {
3231                    chat.param.set(Param::ProfileImage, profile_image);
3232                }
3233                AvatarAction::Delete => {
3234                    chat.param.remove(Param::ProfileImage);
3235                }
3236            };
3237            chat.update_param(context).await?;
3238            *send_event_chat_modified = true;
3239        }
3240    }
3241
3242    Ok(())
3243}
3244
3245/// Returns a list of strings that should be shown as info messages, informing about group membership changes.
3246async fn group_changes_msgs(
3247    context: &Context,
3248    added_ids: &HashSet<ContactId>,
3249    removed_ids: &HashSet<ContactId>,
3250    chat_id: ChatId,
3251) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3252    let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3253    if !added_ids.is_empty() {
3254        warn!(
3255            context,
3256            "Implicit addition of {added_ids:?} to chat {chat_id}."
3257        );
3258    }
3259    if !removed_ids.is_empty() {
3260        warn!(
3261            context,
3262            "Implicit removal of {removed_ids:?} from chat {chat_id}."
3263        );
3264    }
3265    group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3266    for contact_id in added_ids {
3267        group_changes_msgs.push((
3268            stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3269            SystemMessage::MemberAddedToGroup,
3270            Some(*contact_id),
3271        ));
3272    }
3273    for contact_id in removed_ids {
3274        group_changes_msgs.push((
3275            stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3276            SystemMessage::MemberRemovedFromGroup,
3277            Some(*contact_id),
3278        ));
3279    }
3280
3281    Ok(group_changes_msgs)
3282}
3283
3284static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3285
3286fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3287    Ok(match LIST_ID_REGEX.captures(list_id_header) {
3288        Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3289        None => list_id_header
3290            .trim()
3291            .trim_start_matches('<')
3292            .trim_end_matches('>'),
3293    }
3294    .to_string())
3295}
3296
3297/// Create or lookup a mailing list or incoming broadcast channel chat.
3298///
3299/// `list_id_header` contains the Id that must be used for the mailing list
3300/// and has the form `Name <Id>`, `<Id>` or just `Id`.
3301/// Depending on the mailing list type, `list_id_header`
3302/// was picked from `ListId:`-header or the `Sender:`-header.
3303///
3304/// `mime_parser` is the corresponding message
3305/// and is used to figure out the mailing list name from different header fields.
3306///
3307/// Returns the chat ID,
3308/// whether it is blocked
3309/// and if the chat was created by this function
3310/// (as opposed to being looked up among existing chats).
3311async fn create_or_lookup_mailinglist_or_broadcast(
3312    context: &Context,
3313    allow_creation: bool,
3314    create_blocked: Blocked,
3315    list_id_header: &str,
3316    from_id: ContactId,
3317    mime_parser: &MimeMessage,
3318) -> Result<Option<(ChatId, Blocked, bool)>> {
3319    let listid = mailinglist_header_listid(list_id_header)?;
3320
3321    if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3322        return Ok(Some((chat_id, blocked, false)));
3323    }
3324
3325    let chattype = if mime_parser.was_encrypted() {
3326        Chattype::InBroadcast
3327    } else {
3328        Chattype::Mailinglist
3329    };
3330
3331    let name = if chattype == Chattype::InBroadcast {
3332        mime_parser
3333            .get_header(HeaderDef::ChatGroupName)
3334            .unwrap_or("Broadcast Channel")
3335    } else {
3336        &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3337    };
3338
3339    if allow_creation {
3340        // Broadcast channel / mailinglist does not exist but should be created
3341        let param = mime_parser.list_post.as_ref().map(|list_post| {
3342            let mut p = Params::new();
3343            p.set(Param::ListPost, list_post);
3344            p.to_string()
3345        });
3346
3347        let chat_id = ChatId::create_multiuser_record(
3348            context,
3349            chattype,
3350            &listid,
3351            name,
3352            create_blocked,
3353            param,
3354            mime_parser.timestamp_sent,
3355        )
3356        .await
3357        .with_context(|| {
3358            format!(
3359                "failed to create mailinglist '{}' for grpid={}",
3360                &name, &listid
3361            )
3362        })?;
3363
3364        if chattype == Chattype::InBroadcast {
3365            chat::add_to_chat_contacts_table(
3366                context,
3367                mime_parser.timestamp_sent,
3368                chat_id,
3369                &[from_id],
3370            )
3371            .await?;
3372        }
3373
3374        context.emit_event(EventType::ChatModified(chat_id));
3375        chatlist_events::emit_chatlist_changed(context);
3376        chatlist_events::emit_chatlist_item_changed(context, chat_id);
3377
3378        Ok(Some((chat_id, create_blocked, true)))
3379    } else {
3380        info!(context, "Creating list forbidden by caller.");
3381        Ok(None)
3382    }
3383}
3384
3385fn compute_mailinglist_name(
3386    list_id_header: &str,
3387    listid: &str,
3388    mime_parser: &MimeMessage,
3389) -> String {
3390    let mut name = match LIST_ID_REGEX
3391        .captures(list_id_header)
3392        .and_then(|caps| caps.get(1))
3393    {
3394        Some(cap) => cap.as_str().trim().to_string(),
3395        None => "".to_string(),
3396    };
3397
3398    // for mailchimp lists, the name in `ListId` is just a long number.
3399    // a usable name for these lists is in the `From` header
3400    // and we can detect these lists by a unique `ListId`-suffix.
3401    if listid.ends_with(".list-id.mcsv.net")
3402        && let Some(display_name) = &mime_parser.from.display_name
3403    {
3404        name.clone_from(display_name);
3405    }
3406
3407    // additional names in square brackets in the subject are preferred
3408    // (as that part is much more visible, we assume, that names is shorter and comes more to the point,
3409    // than the sometimes longer part from ListId)
3410    let subject = mime_parser.get_subject().unwrap_or_default();
3411    static SUBJECT: LazyLock<Regex> =
3412        LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); // remove square brackets around first name
3413    if let Some(cap) = SUBJECT.captures(&subject) {
3414        name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3415    }
3416
3417    // if we do not have a name yet and `From` indicates, that this is a notification list,
3418    // a usable name is often in the `From` header (seen for several parcel service notifications).
3419    // same, if we do not have a name yet and `List-Id` has a known suffix (`.xt.local`)
3420    //
3421    // this pattern is similar to mailchimp above, however,
3422    // with weaker conditions and does not overwrite existing names.
3423    if name.is_empty()
3424        && (mime_parser.from.addr.contains("noreply")
3425            || mime_parser.from.addr.contains("no-reply")
3426            || mime_parser.from.addr.starts_with("notifications@")
3427            || mime_parser.from.addr.starts_with("newsletter@")
3428            || listid.ends_with(".xt.local"))
3429        && let Some(display_name) = &mime_parser.from.display_name
3430    {
3431        name.clone_from(display_name);
3432    }
3433
3434    // as a last resort, use the ListId as the name
3435    // but strip some known, long hash prefixes
3436    if name.is_empty() {
3437        // 51231231231231231231231232869f58.xing.com -> xing.com
3438        static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3439            LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3440        if let Some(cap) = PREFIX_32_CHARS_HEX
3441            .captures(listid)
3442            .and_then(|caps| caps.get(2))
3443        {
3444            name = cap.as_str().to_string();
3445        } else {
3446            name = listid.to_string();
3447        }
3448    }
3449
3450    sanitize_single_line(&name)
3451}
3452
3453/// Set ListId param on the contact and ListPost param the chat.
3454/// Only called for incoming messages since outgoing messages never have a
3455/// List-Post header, anyway.
3456async fn apply_mailinglist_changes(
3457    context: &Context,
3458    mime_parser: &MimeMessage,
3459    chat_id: ChatId,
3460) -> Result<()> {
3461    let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3462        return Ok(());
3463    };
3464
3465    let mut chat = Chat::load_from_db(context, chat_id).await?;
3466    if chat.typ != Chattype::Mailinglist {
3467        return Ok(());
3468    }
3469    let listid = &chat.grpid;
3470
3471    let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3472    if chat.name != new_name
3473        && chat_id
3474            .update_timestamp(
3475                context,
3476                Param::GroupNameTimestamp,
3477                mime_parser.timestamp_sent,
3478            )
3479            .await?
3480    {
3481        info!(context, "Updating listname for chat {chat_id}.");
3482        context
3483            .sql
3484            .execute(
3485                "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3486                (&new_name, normalize_text(&new_name), chat_id),
3487            )
3488            .await?;
3489        context.emit_event(EventType::ChatModified(chat_id));
3490    }
3491
3492    let Some(list_post) = &mime_parser.list_post else {
3493        return Ok(());
3494    };
3495
3496    let list_post = match ContactAddress::new(list_post) {
3497        Ok(list_post) => list_post,
3498        Err(err) => {
3499            warn!(context, "Invalid List-Post: {:#}.", err);
3500            return Ok(());
3501        }
3502    };
3503    let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3504    let mut contact = Contact::get_by_id(context, contact_id).await?;
3505    if contact.param.get(Param::ListId) != Some(listid) {
3506        contact.param.set(Param::ListId, listid);
3507        contact.update_param(context).await?;
3508    }
3509
3510    if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3511        if list_post.as_ref() != old_list_post {
3512            // Apparently the mailing list is using a different List-Post header in each message.
3513            // Make the mailing list read-only because we wouldn't know which message the user wants to reply to.
3514            chat.param.remove(Param::ListPost);
3515            chat.update_param(context).await?;
3516        }
3517    } else {
3518        chat.param.set(Param::ListPost, list_post);
3519        chat.update_param(context).await?;
3520    }
3521
3522    Ok(())
3523}
3524
3525async fn apply_out_broadcast_changes(
3526    context: &Context,
3527    mime_parser: &MimeMessage,
3528    chat: &mut Chat,
3529    from_id: ContactId,
3530) -> Result<GroupChangesInfo> {
3531    ensure!(chat.typ == Chattype::OutBroadcast);
3532
3533    let mut send_event_chat_modified = false;
3534    let mut better_msg = None;
3535
3536    if from_id == ContactId::SELF {
3537        apply_chat_name_and_avatar_changes(
3538            context,
3539            mime_parser,
3540            from_id,
3541            chat,
3542            &mut send_event_chat_modified,
3543            &mut better_msg,
3544        )
3545        .await?;
3546    }
3547
3548    if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3549        if from_id == ContactId::SELF {
3550            let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3551            if let Some(added_id) = added_id {
3552                if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3553                    info!(context, "No-op broadcast addition (TRASH)");
3554                    better_msg.get_or_insert("".to_string());
3555                } else {
3556                    chat::add_to_chat_contacts_table(
3557                        context,
3558                        mime_parser.timestamp_sent,
3559                        chat.id,
3560                        &[added_id],
3561                    )
3562                    .await?;
3563                    let msg =
3564                        stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3565                            .await;
3566                    better_msg.get_or_insert(msg);
3567                    send_event_chat_modified = true;
3568                }
3569            } else {
3570                warn!(context, "Failed to find contact with fpr {added_fpr}");
3571            }
3572        }
3573    } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3574        send_event_chat_modified = true;
3575        let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3576        if removed_id == Some(from_id) {
3577            // The sender of the message left the broadcast channel
3578            // Silently remove them without notifying the user
3579            chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3580            info!(context, "Broadcast leave message (TRASH)");
3581            better_msg = Some("".to_string());
3582        } else if from_id == ContactId::SELF
3583            && let Some(removed_id) = removed_id
3584        {
3585            chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3586                .await?;
3587
3588            better_msg.get_or_insert(
3589                stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3590            );
3591        }
3592    }
3593
3594    if send_event_chat_modified {
3595        context.emit_event(EventType::ChatModified(chat.id));
3596        chatlist_events::emit_chatlist_item_changed(context, chat.id);
3597    }
3598    Ok(GroupChangesInfo {
3599        better_msg,
3600        added_removed_id: None,
3601        silent: false,
3602        extra_msgs: vec![],
3603    })
3604}
3605
3606async fn apply_in_broadcast_changes(
3607    context: &Context,
3608    mime_parser: &MimeMessage,
3609    chat: &mut Chat,
3610    from_id: ContactId,
3611) -> Result<GroupChangesInfo> {
3612    ensure!(chat.typ == Chattype::InBroadcast);
3613
3614    if let Some(part) = mime_parser.parts.first()
3615        && let Some(error) = &part.error
3616    {
3617        warn!(
3618            context,
3619            "Not applying broadcast changes from message with error: {error}"
3620        );
3621        return Ok(GroupChangesInfo::default());
3622    }
3623
3624    let mut send_event_chat_modified = false;
3625    let mut better_msg = None;
3626
3627    apply_chat_name_and_avatar_changes(
3628        context,
3629        mime_parser,
3630        from_id,
3631        chat,
3632        &mut send_event_chat_modified,
3633        &mut better_msg,
3634    )
3635    .await?;
3636
3637    if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3638        && context.is_self_addr(added_addr).await?
3639    {
3640        let msg = if chat.is_self_in_chat(context).await? {
3641            // Self is already in the chat.
3642            // Probably Alice has two devices and her second device added us again;
3643            // just hide the message.
3644            info!(context, "No-op broadcast 'Member added' message (TRASH)");
3645            "".to_string()
3646        } else {
3647            stock_str::msg_you_joined_broadcast(context).await
3648        };
3649
3650        better_msg.get_or_insert(msg);
3651        send_event_chat_modified = true;
3652    }
3653
3654    if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3655        // We are not supposed to receive a notification when someone else than self is removed:
3656        if removed_fpr != self_fingerprint(context).await? {
3657            logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3658            return Ok(GroupChangesInfo::default());
3659        }
3660        chat::delete_broadcast_secret(context, chat.id).await?;
3661
3662        if from_id == ContactId::SELF {
3663            better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
3664        } else {
3665            better_msg.get_or_insert(
3666                stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3667            );
3668        }
3669
3670        chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3671            .await?;
3672        send_event_chat_modified = true;
3673    } else if !chat.is_self_in_chat(context).await? {
3674        chat::add_to_chat_contacts_table(
3675            context,
3676            mime_parser.timestamp_sent,
3677            chat.id,
3678            &[ContactId::SELF],
3679        )
3680        .await?;
3681        send_event_chat_modified = true;
3682    }
3683
3684    if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3685        if validate_broadcast_secret(secret) {
3686            save_broadcast_secret(context, chat.id, secret).await?;
3687        } else {
3688            warn!(context, "Not saving invalid broadcast secret");
3689        }
3690    }
3691
3692    if send_event_chat_modified {
3693        context.emit_event(EventType::ChatModified(chat.id));
3694        chatlist_events::emit_chatlist_item_changed(context, chat.id);
3695    }
3696    Ok(GroupChangesInfo {
3697        better_msg,
3698        added_removed_id: None,
3699        silent: false,
3700        extra_msgs: vec![],
3701    })
3702}
3703
3704/// Creates ad-hoc group and returns chat ID on success.
3705async fn create_adhoc_group(
3706    context: &Context,
3707    mime_parser: &MimeMessage,
3708    create_blocked: Blocked,
3709    from_id: ContactId,
3710    to_ids: &[ContactId],
3711    grpname: &str,
3712) -> Result<Option<(ChatId, Blocked)>> {
3713    let mut member_ids: Vec<ContactId> = to_ids.to_vec();
3714    if !member_ids.contains(&(from_id)) {
3715        member_ids.push(from_id);
3716    }
3717    if !member_ids.contains(&(ContactId::SELF)) {
3718        member_ids.push(ContactId::SELF);
3719    }
3720
3721    if mime_parser.is_mailinglist_message() {
3722        return Ok(None);
3723    }
3724    if mime_parser
3725        .get_header(HeaderDef::ChatGroupMemberRemoved)
3726        .is_some()
3727    {
3728        info!(
3729            context,
3730            "Message removes member from unknown ad-hoc group (TRASH)."
3731        );
3732        return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3733    }
3734
3735    let new_chat_id: ChatId = ChatId::create_multiuser_record(
3736        context,
3737        Chattype::Group,
3738        "", // Ad hoc groups have no ID.
3739        grpname,
3740        create_blocked,
3741        None,
3742        mime_parser.timestamp_sent,
3743    )
3744    .await?;
3745
3746    info!(
3747        context,
3748        "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3749    );
3750    chat::add_to_chat_contacts_table(
3751        context,
3752        mime_parser.timestamp_sent,
3753        new_chat_id,
3754        &member_ids,
3755    )
3756    .await?;
3757
3758    context.emit_event(EventType::ChatModified(new_chat_id));
3759    chatlist_events::emit_chatlist_changed(context);
3760    chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3761
3762    Ok(Some((new_chat_id, create_blocked)))
3763}
3764
3765#[derive(Debug, PartialEq, Eq)]
3766enum VerifiedEncryption {
3767    Verified,
3768    NotVerified(String), // The string contains the reason why it's not verified
3769}
3770
3771/// Checks whether the message is allowed to appear in a protected chat.
3772///
3773/// This means that it is encrypted and signed with a verified key.
3774async fn has_verified_encryption(
3775    context: &Context,
3776    mimeparser: &MimeMessage,
3777    from_id: ContactId,
3778) -> Result<VerifiedEncryption> {
3779    use VerifiedEncryption::*;
3780
3781    if !mimeparser.was_encrypted() {
3782        return Ok(NotVerified("This message is not encrypted".to_string()));
3783    };
3784
3785    if from_id == ContactId::SELF {
3786        return Ok(Verified);
3787    }
3788
3789    let from_contact = Contact::get_by_id(context, from_id).await?;
3790
3791    let Some(fingerprint) = from_contact.fingerprint() else {
3792        return Ok(NotVerified(
3793            "The message was sent without encryption".to_string(),
3794        ));
3795    };
3796
3797    if from_contact.get_verifier_id(context).await?.is_none() {
3798        return Ok(NotVerified(
3799            "The message was sent by non-verified contact".to_string(),
3800        ));
3801    }
3802
3803    let signed_with_verified_key = mimeparser
3804        .signature
3805        .as_ref()
3806        .is_some_and(|signature| *signature == fingerprint);
3807    if signed_with_verified_key {
3808        Ok(Verified)
3809    } else {
3810        Ok(NotVerified(
3811            "The message was sent with non-verified encryption".to_string(),
3812        ))
3813    }
3814}
3815
3816async fn mark_recipients_as_verified(
3817    context: &Context,
3818    from_id: ContactId,
3819    mimeparser: &MimeMessage,
3820) -> Result<()> {
3821    let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
3822
3823    // We don't yet send the _verified property in autocrypt headers.
3824    // Until we do, we instead accept the Chat-Verified header as indication all contacts are verified.
3825    // TODO: Ignore ChatVerified header once we reset existing verifications.
3826    let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
3827
3828    for gossiped_key in mimeparser
3829        .gossiped_keys
3830        .values()
3831        .filter(|gossiped_key| gossiped_key.verified || chat_verified)
3832    {
3833        let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
3834        let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
3835            continue;
3836        };
3837
3838        if to_id == ContactId::SELF || to_id == from_id {
3839            continue;
3840        }
3841
3842        mark_contact_id_as_verified(context, to_id, verifier_id).await?;
3843    }
3844
3845    Ok(())
3846}
3847
3848/// Returns the last message referenced from `References` header if it is in the database.
3849///
3850/// For Delta Chat messages it is the last message in the chat of the sender.
3851async fn get_previous_message(
3852    context: &Context,
3853    mime_parser: &MimeMessage,
3854) -> Result<Option<Message>> {
3855    if let Some(field) = mime_parser.get_header(HeaderDef::References)
3856        && let Some(rfc724mid) = parse_message_ids(field).last()
3857        && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
3858    {
3859        return Message::load_from_db_optional(context, msg_id).await;
3860    }
3861    Ok(None)
3862}
3863
3864/// Returns the last message referenced from References: header found in the database.
3865///
3866/// If none found, tries In-Reply-To: as a fallback for classic MUAs that don't set the
3867/// References: header.
3868async fn get_parent_message(
3869    context: &Context,
3870    references: Option<&str>,
3871    in_reply_to: Option<&str>,
3872) -> Result<Option<Message>> {
3873    let mut mids = Vec::new();
3874    if let Some(field) = in_reply_to {
3875        mids = parse_message_ids(field);
3876    }
3877    if let Some(field) = references {
3878        mids.append(&mut parse_message_ids(field));
3879    }
3880    message::get_by_rfc724_mids(context, &mids).await
3881}
3882
3883pub(crate) async fn get_prefetch_parent_message(
3884    context: &Context,
3885    headers: &[mailparse::MailHeader<'_>],
3886) -> Result<Option<Message>> {
3887    get_parent_message(
3888        context,
3889        headers.get_header_value(HeaderDef::References).as_deref(),
3890        headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
3891    )
3892    .await
3893}
3894
3895/// Looks up contact IDs from the database given the list of recipients.
3896async fn add_or_lookup_contacts_by_address_list(
3897    context: &Context,
3898    address_list: &[SingleInfo],
3899    origin: Origin,
3900) -> Result<Vec<Option<ContactId>>> {
3901    let mut contact_ids = Vec::new();
3902    for info in address_list {
3903        let addr = &info.addr;
3904        if !may_be_valid_addr(addr) {
3905            contact_ids.push(None);
3906            continue;
3907        }
3908        let display_name = info.display_name.as_deref();
3909        if let Ok(addr) = ContactAddress::new(addr) {
3910            let (contact_id, _) =
3911                Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
3912                    .await?;
3913            contact_ids.push(Some(contact_id));
3914        } else {
3915            warn!(context, "Contact with address {:?} cannot exist.", addr);
3916            contact_ids.push(None);
3917        }
3918    }
3919
3920    Ok(contact_ids)
3921}
3922
3923/// Looks up contact IDs from the database given the list of recipients.
3924async fn add_or_lookup_key_contacts(
3925    context: &Context,
3926    address_list: &[SingleInfo],
3927    gossiped_keys: &BTreeMap<String, GossipedKey>,
3928    fingerprints: &[Fingerprint],
3929    origin: Origin,
3930) -> Result<Vec<Option<ContactId>>> {
3931    let mut contact_ids = Vec::new();
3932    let mut fingerprint_iter = fingerprints.iter();
3933    for info in address_list {
3934        let addr = &info.addr;
3935        if !may_be_valid_addr(addr) {
3936            contact_ids.push(None);
3937            continue;
3938        }
3939        let fingerprint: String = if let Some(fp) = fingerprint_iter.next() {
3940            // Iterator has not ran out of fingerprints yet.
3941            fp.hex()
3942        } else if let Some(key) = gossiped_keys.get(addr) {
3943            key.public_key.dc_fingerprint().hex()
3944        } else if context.is_self_addr(addr).await? {
3945            contact_ids.push(Some(ContactId::SELF));
3946            continue;
3947        } else {
3948            contact_ids.push(None);
3949            continue;
3950        };
3951        let display_name = info.display_name.as_deref();
3952        if let Ok(addr) = ContactAddress::new(addr) {
3953            let (contact_id, _) = Contact::add_or_lookup_ex(
3954                context,
3955                display_name.unwrap_or_default(),
3956                &addr,
3957                &fingerprint,
3958                origin,
3959            )
3960            .await?;
3961            contact_ids.push(Some(contact_id));
3962        } else {
3963            warn!(context, "Contact with address {:?} cannot exist.", addr);
3964            contact_ids.push(None);
3965        }
3966    }
3967
3968    ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
3969    Ok(contact_ids)
3970}
3971
3972/// Looks up a key-contact by email address.
3973///
3974/// If provided, `chat_id` must be an encrypted chat ID that has key-contacts inside.
3975/// Otherwise the function searches in all contacts, preferring accepted and most recently seen ones.
3976async fn lookup_key_contact_by_address(
3977    context: &Context,
3978    addr: &str,
3979    chat_id: Option<ChatId>,
3980) -> Result<Option<ContactId>> {
3981    if context.is_self_addr(addr).await? {
3982        if chat_id.is_none() {
3983            return Ok(Some(ContactId::SELF));
3984        }
3985        let is_self_in_chat = context
3986            .sql
3987            .exists(
3988                "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
3989                (chat_id,),
3990            )
3991            .await?;
3992        if is_self_in_chat {
3993            return Ok(Some(ContactId::SELF));
3994        }
3995    }
3996    let contact_id: Option<ContactId> = match chat_id {
3997        Some(chat_id) => {
3998            context
3999                .sql
4000                .query_row_optional(
4001                    "SELECT id FROM contacts
4002                     WHERE contacts.addr=?
4003                     AND EXISTS (SELECT 1 FROM chats_contacts
4004                                 WHERE contact_id=contacts.id
4005                                 AND chat_id=?)
4006                     AND fingerprint<>'' -- Should always be true
4007                     ",
4008                    (addr, chat_id),
4009                    |row| {
4010                        let contact_id: ContactId = row.get(0)?;
4011                        Ok(contact_id)
4012                    },
4013                )
4014                .await?
4015        }
4016        None => {
4017            context
4018                .sql
4019                .query_row_optional(
4020                    "SELECT id FROM contacts
4021                     WHERE addr=?
4022                     AND fingerprint<>''
4023                     ORDER BY
4024                         (
4025                             SELECT COUNT(*) FROM chats c
4026                             INNER JOIN chats_contacts cc
4027                             ON c.id=cc.chat_id
4028                             WHERE c.type=?
4029                                 AND c.id>?
4030                                 AND c.blocked=?
4031                                 AND cc.contact_id=contacts.id
4032                         ) DESC,
4033                         last_seen DESC, id DESC
4034                     ",
4035                    (
4036                        addr,
4037                        Chattype::Single,
4038                        constants::DC_CHAT_ID_LAST_SPECIAL,
4039                        Blocked::Not,
4040                    ),
4041                    |row| {
4042                        let contact_id: ContactId = row.get(0)?;
4043                        Ok(contact_id)
4044                    },
4045                )
4046                .await?
4047        }
4048    };
4049    Ok(contact_id)
4050}
4051
4052async fn lookup_key_contact_by_fingerprint(
4053    context: &Context,
4054    fingerprint: &str,
4055) -> Result<Option<ContactId>> {
4056    logged_debug_assert!(
4057        context,
4058        !fingerprint.is_empty(),
4059        "lookup_key_contact_by_fingerprint: fingerprint is empty."
4060    );
4061    if fingerprint.is_empty() {
4062        // Avoid accidentally looking up a non-key-contact.
4063        return Ok(None);
4064    }
4065    if let Some(contact_id) = context
4066        .sql
4067        .query_row_optional(
4068            "SELECT id FROM contacts
4069             WHERE fingerprint=? AND fingerprint!=''",
4070            (fingerprint,),
4071            |row| {
4072                let contact_id: ContactId = row.get(0)?;
4073                Ok(contact_id)
4074            },
4075        )
4076        .await?
4077    {
4078        Ok(Some(contact_id))
4079    } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4080        if self_fp == fingerprint {
4081            Ok(Some(ContactId::SELF))
4082        } else {
4083            Ok(None)
4084        }
4085    } else {
4086        Ok(None)
4087    }
4088}
4089
4090/// Looks up key-contacts by email addresses.
4091///
4092/// `fingerprints` may be empty.
4093/// This is used as a fallback when email addresses are available,
4094/// but not the fingerprints, e.g. when core 1.157.3
4095/// client sends the `To` and `Chat-Group-Past-Members` header
4096/// but not the corresponding fingerprint list.
4097///
4098/// Lookup is restricted to the chat ID.
4099///
4100/// If contact cannot be found, `None` is returned.
4101/// This ensures that the length of the result vector
4102/// is the same as the number of addresses in the header
4103/// and it is possible to find corresponding
4104/// `Chat-Group-Member-Timestamps` items.
4105async fn lookup_key_contacts_by_address_list(
4106    context: &Context,
4107    address_list: &[SingleInfo],
4108    fingerprints: &[Fingerprint],
4109    chat_id: Option<ChatId>,
4110) -> Result<Vec<Option<ContactId>>> {
4111    let mut contact_ids = Vec::new();
4112    let mut fingerprint_iter = fingerprints.iter();
4113    for info in address_list {
4114        let addr = &info.addr;
4115        if !may_be_valid_addr(addr) {
4116            contact_ids.push(None);
4117            continue;
4118        }
4119
4120        if let Some(fp) = fingerprint_iter.next() {
4121            // Iterator has not ran out of fingerprints yet.
4122            let display_name = info.display_name.as_deref();
4123            let fingerprint: String = fp.hex();
4124
4125            if let Ok(addr) = ContactAddress::new(addr) {
4126                let (contact_id, _) = Contact::add_or_lookup_ex(
4127                    context,
4128                    display_name.unwrap_or_default(),
4129                    &addr,
4130                    &fingerprint,
4131                    Origin::Hidden,
4132                )
4133                .await?;
4134                contact_ids.push(Some(contact_id));
4135            } else {
4136                warn!(context, "Contact with address {:?} cannot exist.", addr);
4137                contact_ids.push(None);
4138            }
4139        } else {
4140            let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4141            contact_ids.push(contact_id);
4142        }
4143    }
4144    ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4145    Ok(contact_ids)
4146}
4147
4148/// Returns true if the message should not result in renaming
4149/// of the sender contact.
4150fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4151    (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4152        || mime_parser.get_header(HeaderDef::Sender).is_some()
4153}
4154
4155#[cfg(test)]
4156mod receive_imf_tests;