deltachat/
receive_imf.rs

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