1use std::cmp;
4use std::collections::{BTreeMap, BTreeSet};
5use std::iter;
6use std::str::FromStr as _;
7use std::sync::LazyLock;
8
9use anyhow::{Context as _, Result, ensure};
10use deltachat_contact_tools::{
11 ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_bidi_characters,
12 sanitize_single_line,
13};
14use mailparse::SingleInfo;
15use regex::Regex;
16
17use crate::chat::{
18 self, Chat, ChatId, ChatIdBlocked, ChatVisibility, is_contact_in_chat, save_broadcast_secret,
19};
20use crate::config::Config;
21use crate::constants::{self, Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX};
22use crate::contact::{self, Contact, ContactId, Origin, mark_contact_id_as_verified};
23use crate::context::Context;
24use crate::debug_logging::maybe_set_logging_xdc_inner;
25use crate::download::{DownloadState, msg_is_downloaded_for};
26use crate::ephemeral::{Timer as EphemeralTimer, stock_ephemeral_timer_changed};
27use crate::events::EventType;
28use crate::headerdef::{HeaderDef, HeaderDefMap};
29use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table};
30use crate::key::{DcKey, Fingerprint};
31use crate::key::{
32 load_self_public_key, load_self_public_key_opt, self_fingerprint, self_fingerprint_opt,
33};
34use crate::log::{LogExt as _, warn};
35use crate::message::{
36 self, Message, MessageState, MessengerMessage, MsgId, Viewtype, insert_tombstone,
37 rfc724_mid_exists,
38};
39use crate::mimeparser::{
40 AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids,
41};
42use crate::param::{Param, Params};
43use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub, iroh_topic_from_str};
44use crate::reaction::{Reaction, set_msg_reaction};
45use crate::rusqlite::OptionalExtension;
46use crate::securejoin::{
47 self, get_secure_join_step, handle_securejoin_handshake, observe_securejoin_on_other_device,
48};
49use crate::simplify;
50use crate::smtp::msg_has_pending_smtp_job;
51use crate::stats::STATISTICS_BOT_EMAIL;
52use crate::stock_str;
53use crate::sync::Sync::*;
54use crate::tools::{
55 self, buf_compress, normalize_text, remove_subject_prefix, validate_broadcast_secret,
56};
57use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
58use crate::{logged_debug_assert, mimeparser};
59
60#[derive(Debug)]
65pub struct ReceivedMsg {
66 pub chat_id: ChatId,
68
69 pub state: MessageState,
71
72 pub hidden: bool,
74
75 pub sort_timestamp: i64,
77
78 pub msg_ids: Vec<MsgId>,
80
81 pub needs_delete_job: bool,
83}
84
85#[derive(Debug)]
98enum ChatAssignment {
99 Trash,
101
102 GroupChat { grpid: String },
107
108 MailingListOrBroadcast,
123
124 AdHocGroup,
128
129 ExistingChat {
132 chat_id: ChatId,
135
136 chat_id_blocked: Blocked,
144 },
145
146 OneOneChat,
154}
155
156#[cfg(any(test, feature = "internals"))]
161pub async fn receive_imf(
162 context: &Context,
163 imf_raw: &[u8],
164 seen: bool,
165) -> Result<Option<ReceivedMsg>> {
166 let mail = mailparse::parse_mail(imf_raw).context("can't parse mail")?;
167 let rfc724_mid = crate::imap::prefetch_get_message_id(&mail.headers)
168 .unwrap_or_else(crate::imap::create_message_id);
169 receive_imf_from_inbox(context, &rfc724_mid, imf_raw, seen).await
170}
171
172#[cfg(any(test, feature = "internals"))]
176pub(crate) async fn receive_imf_from_inbox(
177 context: &Context,
178 rfc724_mid: &str,
179 imf_raw: &[u8],
180 seen: bool,
181) -> Result<Option<ReceivedMsg>> {
182 receive_imf_inner(context, rfc724_mid, imf_raw, seen).await
183}
184
185async fn get_to_and_past_contact_ids(
186 context: &Context,
187 mime_parser: &MimeMessage,
188 chat_assignment: &mut ChatAssignment,
189 parent_message: &Option<Message>,
190 incoming_origin: Origin,
191) -> Result<(Vec<Option<ContactId>>, Vec<Option<ContactId>>)> {
192 let to_ids: Vec<Option<ContactId>>;
204 let past_ids: Vec<Option<ContactId>>;
205
206 let chat_id = match chat_assignment {
213 ChatAssignment::Trash => None,
214 ChatAssignment::GroupChat { grpid } => {
215 if let Some((chat_id, _blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
216 Some(chat_id)
217 } else {
218 None
219 }
220 }
221 ChatAssignment::AdHocGroup => {
222 None
227 }
228 ChatAssignment::ExistingChat { chat_id, .. } => Some(*chat_id),
229 ChatAssignment::MailingListOrBroadcast => None,
230 ChatAssignment::OneOneChat => {
231 if !mime_parser.incoming {
232 parent_message.as_ref().map(|m| m.chat_id)
233 } else {
234 None
235 }
236 }
237 };
238
239 let member_fingerprints = mime_parser.chat_group_member_fingerprints();
240 let to_member_fingerprints;
241 let past_member_fingerprints;
242
243 if !member_fingerprints.is_empty() {
244 if member_fingerprints.len() >= mime_parser.recipients.len() {
245 (to_member_fingerprints, past_member_fingerprints) =
246 member_fingerprints.split_at(mime_parser.recipients.len());
247 } else {
248 warn!(
249 context,
250 "Unexpected length of the fingerprint header, expected at least {}, got {}.",
251 mime_parser.recipients.len(),
252 member_fingerprints.len()
253 );
254 to_member_fingerprints = &[];
255 past_member_fingerprints = &[];
256 }
257 } else {
258 to_member_fingerprints = &[];
259 past_member_fingerprints = &[];
260 }
261
262 match chat_assignment {
263 ChatAssignment::GroupChat { .. } => {
264 to_ids = add_or_lookup_key_contacts(
265 context,
266 &mime_parser.recipients,
267 &mime_parser.gossiped_keys,
268 to_member_fingerprints,
269 Origin::Hidden,
270 )
271 .await?;
272
273 if let Some(chat_id) = chat_id {
274 past_ids = lookup_key_contacts_fallback_to_chat(
275 context,
276 &mime_parser.past_members,
277 past_member_fingerprints,
278 Some(chat_id),
279 )
280 .await?;
281 } else {
282 past_ids = add_or_lookup_key_contacts(
283 context,
284 &mime_parser.past_members,
285 &mime_parser.gossiped_keys,
286 past_member_fingerprints,
287 Origin::Hidden,
288 )
289 .await?;
290 }
291 }
292 ChatAssignment::Trash => {
293 to_ids = Vec::new();
294 past_ids = Vec::new();
295 }
296 ChatAssignment::ExistingChat { chat_id, .. } => {
297 let chat = Chat::load_from_db(context, *chat_id).await?;
298 if chat.is_encrypted(context).await? {
299 to_ids = add_or_lookup_key_contacts(
300 context,
301 &mime_parser.recipients,
302 &mime_parser.gossiped_keys,
303 to_member_fingerprints,
304 Origin::Hidden,
305 )
306 .await?;
307 past_ids = lookup_key_contacts_fallback_to_chat(
308 context,
309 &mime_parser.past_members,
310 past_member_fingerprints,
311 Some(*chat_id),
312 )
313 .await?;
314 } else {
315 to_ids = add_or_lookup_contacts_by_address_list(
316 context,
317 &mime_parser.recipients,
318 if !mime_parser.incoming {
319 Origin::OutgoingTo
320 } else if incoming_origin.is_known() {
321 Origin::IncomingTo
322 } else {
323 Origin::IncomingUnknownTo
324 },
325 )
326 .await?;
327
328 past_ids = add_or_lookup_contacts_by_address_list(
329 context,
330 &mime_parser.past_members,
331 Origin::Hidden,
332 )
333 .await?;
334 }
335 }
336 ChatAssignment::AdHocGroup => {
337 to_ids = add_or_lookup_contacts_by_address_list(
338 context,
339 &mime_parser.recipients,
340 if !mime_parser.incoming {
341 Origin::OutgoingTo
342 } else if incoming_origin.is_known() {
343 Origin::IncomingTo
344 } else {
345 Origin::IncomingUnknownTo
346 },
347 )
348 .await?;
349
350 past_ids = add_or_lookup_contacts_by_address_list(
351 context,
352 &mime_parser.past_members,
353 Origin::Hidden,
354 )
355 .await?;
356 }
357 ChatAssignment::OneOneChat | ChatAssignment::MailingListOrBroadcast => {
361 let pgp_to_ids = add_or_lookup_key_contacts(
362 context,
363 &mime_parser.recipients,
364 &mime_parser.gossiped_keys,
365 to_member_fingerprints,
366 Origin::Hidden,
367 )
368 .await?;
369 if pgp_to_ids
370 .first()
371 .is_some_and(|contact_id| contact_id.is_some())
372 {
373 to_ids = pgp_to_ids
377 } else {
378 let ids = if mime_parser.was_encrypted() {
379 let mut recipient_fps = mime_parser
380 .signature
381 .as_ref()
382 .map(|(_, recipient_fps)| recipient_fps.iter().cloned().collect::<Vec<_>>())
383 .unwrap_or_default();
384 if !recipient_fps.is_empty() && recipient_fps.len() <= 2 {
387 let self_fp = load_self_public_key(context).await?.dc_fingerprint();
388 recipient_fps.retain(|fp| *fp != self_fp);
389 if recipient_fps.is_empty() {
390 vec![Some(ContactId::SELF)]
391 } else {
392 add_or_lookup_key_contacts(
393 context,
394 &mime_parser.recipients,
395 &mime_parser.gossiped_keys,
396 &recipient_fps,
397 Origin::Hidden,
398 )
399 .await?
400 }
401 } else {
402 lookup_key_contacts_fallback_to_chat(
403 context,
404 &mime_parser.recipients,
405 to_member_fingerprints,
406 chat_id,
407 )
408 .await?
409 }
410 } else {
411 vec![]
412 };
413 if mime_parser.was_encrypted() && !ids.contains(&None)
414 || ids
417 .iter()
418 .any(|&c| c.is_some() && c != Some(ContactId::SELF))
419 {
420 to_ids = ids;
421 } else {
422 if mime_parser.was_encrypted() {
423 warn!(
424 context,
425 "No key-contact looked up. Downgrading to AdHocGroup."
426 );
427 *chat_assignment = ChatAssignment::AdHocGroup;
428 }
429 to_ids = add_or_lookup_contacts_by_address_list(
430 context,
431 &mime_parser.recipients,
432 if !mime_parser.incoming {
433 Origin::OutgoingTo
434 } else if incoming_origin.is_known() {
435 Origin::IncomingTo
436 } else {
437 Origin::IncomingUnknownTo
438 },
439 )
440 .await?;
441 }
442 }
443
444 past_ids = add_or_lookup_contacts_by_address_list(
445 context,
446 &mime_parser.past_members,
447 Origin::Hidden,
448 )
449 .await?;
450 }
451 };
452
453 Ok((to_ids, past_ids))
454}
455
456pub(crate) async fn receive_imf_inner(
466 context: &Context,
467 rfc724_mid: &str,
468 imf_raw: &[u8],
469 seen: bool,
470) -> Result<Option<ReceivedMsg>> {
471 ensure!(
472 !context
473 .get_config_bool(Config::SimulateReceiveImfError)
474 .await?
475 );
476 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
477 info!(
478 context,
479 "receive_imf: incoming message mime-body:\n{}",
480 String::from_utf8_lossy(imf_raw),
481 );
482 }
483
484 let trash = || async {
485 let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
486 Ok(Some(ReceivedMsg {
487 chat_id: DC_CHAT_ID_TRASH,
488 state: MessageState::Undefined,
489 hidden: false,
490 sort_timestamp: 0,
491 msg_ids,
492 needs_delete_job: false,
493 }))
494 };
495
496 let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw).await {
497 Err(err) => {
498 warn!(context, "receive_imf: can't parse MIME: {err:#}.");
499 if rfc724_mid.starts_with(GENERATED_PREFIX) {
500 return Ok(None);
502 }
503 return trash().await;
504 }
505 Ok(mime_parser) => mime_parser,
506 };
507
508 let rfc724_mid_orig = &mime_parser
509 .get_rfc724_mid()
510 .unwrap_or(rfc724_mid.to_string());
511
512 if let Some((_, recipient_fps)) = &mime_parser.signature
513 && !recipient_fps.is_empty()
514 && let Some(self_pubkey) = load_self_public_key_opt(context).await?
515 && !recipient_fps.contains(&self_pubkey.dc_fingerprint())
516 {
517 warn!(
518 context,
519 "Message {rfc724_mid_orig:?} is not intended for us (TRASH)."
520 );
521 return trash().await;
522 }
523 info!(
524 context,
525 "Receiving message {rfc724_mid_orig:?}, seen={seen}...",
526 );
527
528 if mime_parser.pre_message == mimeparser::PreMessageMode::Post {
530 } else if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid_orig).await? {
533 info!(
534 context,
535 "Message {rfc724_mid} is already in some chat or deleted."
536 );
537 if mime_parser.incoming {
538 return Ok(None);
539 }
540 let self_addr = context.get_primary_self_addr().await?;
543 context
544 .sql
545 .execute(
546 "DELETE FROM smtp \
547 WHERE rfc724_mid=?1 AND (recipients LIKE ?2 OR recipients LIKE ('% ' || ?2))",
548 (rfc724_mid_orig, &self_addr),
549 )
550 .await?;
551 if !msg_has_pending_smtp_job(context, msg_id).await? {
552 msg_id.set_delivered(context).await?;
553 }
554 return Ok(None);
555 }
556
557 let prevent_rename = should_prevent_rename(&mime_parser);
558
559 let fingerprint = mime_parser.signature.as_ref().map(|(fp, _)| fp);
571 let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
572 context,
573 &mime_parser.from,
574 fingerprint,
575 prevent_rename,
576 false,
577 )
578 .await?
579 {
580 Some(contact_id_res) => contact_id_res,
581 None => {
582 warn!(
583 context,
584 "receive_imf: From field does not contain an acceptable address."
585 );
586 return Ok(None);
587 }
588 };
589
590 let parent_message = get_parent_message(
601 context,
602 mime_parser.get_header(HeaderDef::References),
603 mime_parser.get_header(HeaderDef::InReplyTo),
604 )
605 .await?;
606
607 let mut chat_assignment =
608 decide_chat_assignment(context, &mime_parser, &parent_message, rfc724_mid, from_id).await?;
609 let (to_ids, past_ids) = get_to_and_past_contact_ids(
610 context,
611 &mime_parser,
612 &mut chat_assignment,
613 &parent_message,
614 incoming_origin,
615 )
616 .await?;
617
618 let received_msg;
619 if let Some(_step) = get_secure_join_step(&mime_parser) {
620 let res = if mime_parser.incoming {
621 handle_securejoin_handshake(context, &mut mime_parser, from_id)
622 .await
623 .with_context(|| {
624 format!(
625 "Error in Secure-Join '{}' message handling",
626 mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
627 )
628 })?
629 } else if let Some(to_id) = to_ids.first().copied().flatten() {
630 observe_securejoin_on_other_device(context, &mime_parser, to_id)
632 .await
633 .with_context(|| {
634 format!(
635 "Error in Secure-Join '{}' watching",
636 mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
637 )
638 })?
639 } else {
640 securejoin::HandshakeMessage::Propagate
641 };
642
643 match res {
644 securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
645 let msg_id = insert_tombstone(context, rfc724_mid).await?;
646 received_msg = Some(ReceivedMsg {
647 chat_id: DC_CHAT_ID_TRASH,
648 state: MessageState::InSeen,
649 hidden: false,
650 sort_timestamp: mime_parser.timestamp_sent,
651 msg_ids: vec![msg_id],
652 needs_delete_job: res == securejoin::HandshakeMessage::Done,
653 });
654 }
655 securejoin::HandshakeMessage::Propagate => {
656 received_msg = None;
657 }
658 }
659 } else {
660 received_msg = None;
661 }
662
663 let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?;
664
665 if verified_encryption == VerifiedEncryption::Verified {
666 mark_recipients_as_verified(context, from_id, &mime_parser).await?;
667 }
668
669 let is_old_contact_request;
670 let received_msg = if let Some(received_msg) = received_msg {
671 is_old_contact_request = false;
672 received_msg
673 } else {
674 let is_dc_message = if mime_parser.has_chat_version() {
675 MessengerMessage::Yes
676 } else if let Some(parent_message) = &parent_message {
677 match parent_message.is_dc_message {
678 MessengerMessage::No => MessengerMessage::No,
679 MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply,
680 }
681 } else {
682 MessengerMessage::No
683 };
684
685 let allow_creation = if mime_parser.decryption_error.is_some() {
686 false
687 } else {
688 !mime_parser.parts.iter().all(|part| part.is_reaction)
689 };
690
691 let to_id = if mime_parser.incoming {
692 ContactId::SELF
693 } else {
694 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
695 };
696
697 let (chat_id, chat_id_blocked, is_created) = do_chat_assignment(
698 context,
699 &chat_assignment,
700 from_id,
701 &to_ids,
702 &past_ids,
703 to_id,
704 allow_creation,
705 &mut mime_parser,
706 parent_message,
707 )
708 .await?;
709 is_old_contact_request = chat_id_blocked == Blocked::Request && !is_created;
710
711 add_parts(
713 context,
714 &mut mime_parser,
715 imf_raw,
716 &to_ids,
717 &past_ids,
718 rfc724_mid_orig,
719 from_id,
720 seen,
721 prevent_rename,
722 chat_id,
723 chat_id_blocked,
724 is_dc_message,
725 is_created,
726 )
727 .await
728 .context("add_parts error")?
729 };
730
731 if !from_id.is_special() {
732 contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
733 }
734
735 let chat_id = received_msg.chat_id;
739 if !chat_id.is_special() {
740 for gossiped_key in mime_parser.gossiped_keys.values() {
741 context
742 .sql
743 .transaction(move |transaction| {
744 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
745 transaction.execute(
746 "INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
747 VALUES (?, ?, ?)
748 ON CONFLICT (chat_id, fingerprint)
749 DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
750 (chat_id, &fingerprint, mime_parser.timestamp_sent),
751 )?;
752
753 Ok(())
754 })
755 .await?;
756 }
757 }
758
759 let insert_msg_id = if let Some(msg_id) = received_msg.msg_ids.last() {
760 *msg_id
761 } else {
762 MsgId::new_unset()
763 };
764
765 save_locations(context, &mime_parser, chat_id, from_id, insert_msg_id).await?;
766
767 if let Some(ref sync_items) = mime_parser.sync_items {
768 if from_id == ContactId::SELF {
769 if mime_parser.was_encrypted() {
770 context
771 .execute_sync_items(sync_items, mime_parser.timestamp_sent)
772 .await;
773
774 let from_addr = &mime_parser.from.addr;
776
777 let transport_changed = context
778 .sql
779 .transaction(|transaction| {
780 let transport_exists = transaction.query_row(
781 "SELECT COUNT(*) FROM transports WHERE addr=?",
782 (from_addr,),
783 |row| {
784 let count: i64 = row.get(0)?;
785 Ok(count > 0)
786 },
787 )?;
788
789 let transport_changed = if transport_exists {
790 transaction.execute(
791 "
792UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
793 ",
794 (from_addr,),
795 )? > 0
796 } else {
797 warn!(
798 context,
799 "Received sync message from unknown address {from_addr:?}."
800 );
801 false
802 };
803 Ok(transport_changed)
804 })
805 .await?;
806 if transport_changed {
807 info!(context, "Primary transport changed to {from_addr:?}.");
808 context.sql.uncache_raw_config("configured_addr").await;
809 context.self_public_key.lock().await.take();
810
811 context.emit_event(EventType::TransportsModified);
812 }
813 } else {
814 warn!(context, "Sync items are not encrypted.");
815 }
816 } else {
817 warn!(context, "Sync items not sent by self.");
818 }
819 }
820
821 if let Some(ref status_update) = mime_parser.webxdc_status_update {
822 let can_info_msg;
823 let instance = if mime_parser
824 .parts
825 .first()
826 .is_some_and(|part| part.typ == Viewtype::Webxdc)
827 {
828 can_info_msg = false;
829 if mime_parser.pre_message == PreMessageMode::Post
830 && let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid_orig).await?
831 {
832 Some(
835 Message::load_from_db(context, msg_id)
836 .await
837 .context("Failed to load webxdc instance that we just checked exists")?,
838 )
839 } else {
840 Some(
841 Message::load_from_db(context, insert_msg_id)
842 .await
843 .context("Failed to load just created webxdc instance")?,
844 )
845 }
846 } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
847 if let Some(instance) =
848 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
849 {
850 can_info_msg = instance.download_state() == DownloadState::Done;
851 Some(instance)
852 } else {
853 can_info_msg = false;
854 None
855 }
856 } else {
857 can_info_msg = false;
858 None
859 };
860
861 if let Some(instance) = instance {
862 if let Err(err) = context
863 .receive_status_update(
864 from_id,
865 &instance,
866 received_msg.sort_timestamp,
867 can_info_msg,
868 status_update,
869 )
870 .await
871 {
872 warn!(context, "receive_imf cannot update status: {err:#}.");
873 }
874 } else {
875 warn!(
876 context,
877 "Received webxdc update, but cannot assign it to message."
878 );
879 }
880 }
881
882 if let Some(avatar_action) = &mime_parser.user_avatar
883 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
884 && context
885 .update_contacts_timestamp(from_id, Param::AvatarTimestamp, mime_parser.timestamp_sent)
886 .await?
887 && let Err(err) = contact::set_profile_image(context, from_id, avatar_action).await
888 {
889 warn!(context, "receive_imf cannot update profile image: {err:#}.");
890 };
891
892 if let Some(footer) = &mime_parser.footer
894 && !mime_parser.is_mailinglist_message()
895 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
896 && context
897 .update_contacts_timestamp(from_id, Param::StatusTimestamp, mime_parser.timestamp_sent)
898 .await?
899 && let Err(err) = contact::set_status(context, from_id, footer.to_string()).await
900 {
901 warn!(context, "Cannot update contact status: {err:#}.");
902 }
903
904 let delete_server_after = context.get_config_delete_server_after().await?;
906
907 if !received_msg.msg_ids.is_empty() {
908 let target = if received_msg.needs_delete_job || delete_server_after == Some(0) {
909 Some("".to_string())
910 } else {
911 None
912 };
913 if target.is_some() || rfc724_mid_orig != rfc724_mid {
914 let target_subst = match &target {
915 Some(_) => "target=?1,",
916 None => "",
917 };
918 context
919 .sql
920 .execute(
921 &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
922 (
923 target.as_deref().unwrap_or_default(),
924 rfc724_mid_orig,
925 rfc724_mid,
926 ),
927 )
928 .await?;
929 context.scheduler.interrupt_inbox().await;
930 }
931 if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
932 {
933 markseen_on_imap_table(context, rfc724_mid_orig).await?;
935 }
936 if !mime_parser.incoming && !context.get_config_bool(Config::TeamProfile).await? {
937 let mut updated_chats = BTreeMap::new();
938 let mut archived_chats_maybe_noticed = false;
939 for report in &mime_parser.mdn_reports {
940 for msg_rfc724_mid in report
941 .original_message_id
942 .iter()
943 .chain(&report.additional_message_ids)
944 {
945 let Some(msg_id) = rfc724_mid_exists(context, msg_rfc724_mid).await? else {
946 continue;
947 };
948 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
949 continue;
950 };
951 if msg.state < MessageState::InFresh || msg.state >= MessageState::InSeen {
952 continue;
953 }
954 if !mime_parser.was_encrypted() && msg.get_showpadlock() {
955 warn!(context, "MDN: Not encrypted. Ignoring.");
956 continue;
957 }
958 message::update_msg_state(context, msg_id, MessageState::InSeen).await?;
959 if let Err(e) = msg_id.start_ephemeral_timer(context).await {
960 error!(context, "start_ephemeral_timer for {msg_id}: {e:#}.");
961 }
962 if !mime_parser.has_chat_version() {
963 continue;
964 }
965 archived_chats_maybe_noticed |= msg.state < MessageState::InNoticed
966 && msg.chat_visibility == ChatVisibility::Archived;
967 updated_chats
968 .entry(msg.chat_id)
969 .and_modify(|pos| *pos = cmp::max(*pos, (msg.timestamp_sort, msg.id)))
970 .or_insert((msg.timestamp_sort, msg.id));
971 }
972 }
973 for (chat_id, (timestamp_sort, msg_id)) in updated_chats {
974 context
975 .sql
976 .execute(
977 "
978UPDATE msgs SET state=? WHERE
979 state=? AND
980 hidden=0 AND
981 chat_id=? AND
982 (timestamp,id)<(?,?)",
983 (
984 MessageState::InNoticed,
985 MessageState::InFresh,
986 chat_id,
987 timestamp_sort,
988 msg_id,
989 ),
990 )
991 .await
992 .context("UPDATE msgs.state")?;
993 if chat_id.get_fresh_msg_cnt(context).await? == 0 {
994 context.emit_event(EventType::MsgsNoticed(chat_id));
996 } else {
997 context.emit_msgs_changed_without_msg_id(chat_id);
998 }
999 chatlist_events::emit_chatlist_item_changed(context, chat_id);
1000 }
1001 if archived_chats_maybe_noticed {
1002 context.on_archived_chats_maybe_noticed();
1003 }
1004 }
1005 }
1006
1007 if mime_parser.is_call() {
1008 context
1009 .handle_call_msg(insert_msg_id, &mime_parser, from_id)
1010 .await?;
1011 } else if received_msg.hidden {
1012 } else if !chat_id.is_trash() {
1014 let fresh = received_msg.state == MessageState::InFresh
1015 && mime_parser.is_system_message != SystemMessage::CallAccepted
1016 && mime_parser.is_system_message != SystemMessage::CallEnded;
1017 let is_bot = context.get_config_bool(Config::Bot).await?;
1018 let is_pre_message = matches!(mime_parser.pre_message, PreMessageMode::Pre { .. });
1019 let skip_bot_notify = is_bot && is_pre_message;
1020 let is_empty = !is_pre_message
1021 && mime_parser.parts.first().is_none_or(|p| {
1022 p.typ == Viewtype::Text && p.msg.is_empty() && p.param.get(Param::Quote).is_none()
1023 });
1024 let important = mime_parser.incoming
1025 && !is_empty
1026 && fresh
1027 && !is_old_contact_request
1028 && !skip_bot_notify;
1029
1030 for msg_id in &received_msg.msg_ids {
1031 chat_id.emit_msg_event(context, *msg_id, important);
1032 }
1033 }
1034 context.new_msgs_notify.notify_one();
1035
1036 mime_parser
1037 .handle_reports(context, from_id, &mime_parser.parts)
1038 .await;
1039
1040 if let Some(is_bot) = mime_parser.is_bot {
1041 if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
1044 from_id.mark_bot(context, is_bot).await?;
1045 }
1046 }
1047
1048 Ok(Some(received_msg))
1049}
1050
1051pub async fn from_field_to_contact_id(
1069 context: &Context,
1070 from: &SingleInfo,
1071 fingerprint: Option<&Fingerprint>,
1072 prevent_rename: bool,
1073 find_key_contact_by_addr: bool,
1074) -> Result<Option<(ContactId, bool, Origin)>> {
1075 let fingerprint = fingerprint.as_ref().map(|fp| fp.hex()).unwrap_or_default();
1076 let display_name = if prevent_rename {
1077 Some("")
1078 } else {
1079 from.display_name.as_deref()
1080 };
1081 let from_addr = match ContactAddress::new(&from.addr) {
1082 Ok(from_addr) => from_addr,
1083 Err(err) => {
1084 warn!(
1085 context,
1086 "Cannot create a contact for the given From field: {err:#}."
1087 );
1088 return Ok(None);
1089 }
1090 };
1091
1092 if fingerprint.is_empty() && find_key_contact_by_addr {
1093 let addr_normalized = addr_normalize(&from_addr);
1094
1095 if let Some((from_id, origin)) = context
1097 .sql
1098 .query_row_optional(
1099 "SELECT id, origin FROM contacts
1100 WHERE addr=?1 COLLATE NOCASE
1101 AND fingerprint<>'' -- Only key-contacts
1102 AND id>?2 AND origin>=?3 AND blocked=?4
1103 ORDER BY last_seen DESC
1104 LIMIT 1",
1105 (
1106 &addr_normalized,
1107 ContactId::LAST_SPECIAL,
1108 Origin::IncomingUnknownFrom,
1109 Blocked::Not,
1110 ),
1111 |row| {
1112 let id: ContactId = row.get(0)?;
1113 let origin: Origin = row.get(1)?;
1114 Ok((id, origin))
1115 },
1116 )
1117 .await?
1118 {
1119 return Ok(Some((from_id, false, origin)));
1120 }
1121 }
1122
1123 let (from_id, _) = Contact::add_or_lookup_ex(
1124 context,
1125 display_name.unwrap_or_default(),
1126 &from_addr,
1127 &fingerprint,
1128 Origin::IncomingUnknownFrom,
1129 )
1130 .await?;
1131
1132 if from_id == ContactId::SELF {
1133 Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
1134 } else {
1135 let contact = Contact::get_by_id(context, from_id).await?;
1136 let from_id_blocked = contact.blocked;
1137 let incoming_origin = contact.origin;
1138
1139 context
1140 .sql
1141 .execute(
1142 "UPDATE contacts SET addr=? WHERE id=?",
1143 (from_addr, from_id),
1144 )
1145 .await?;
1146
1147 Ok(Some((from_id, from_id_blocked, incoming_origin)))
1148 }
1149}
1150
1151#[expect(clippy::arithmetic_side_effects)]
1152async fn decide_chat_assignment(
1153 context: &Context,
1154 mime_parser: &MimeMessage,
1155 parent_message: &Option<Message>,
1156 rfc724_mid: &str,
1157 from_id: ContactId,
1158) -> Result<ChatAssignment> {
1159 let mut should_trash = if !mime_parser.mdn_reports.is_empty() {
1160 info!(context, "Message is an MDN (TRASH).");
1161 true
1162 } else if mime_parser.delivery_report.is_some() {
1163 info!(context, "Message is a DSN (TRASH).");
1164 markseen_on_imap_table(context, rfc724_mid).await.ok();
1165 true
1166 } else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
1167 || mime_parser.get_header(HeaderDef::ChatDelete).is_some()
1168 || mime_parser.get_header(HeaderDef::IrohNodeAddr).is_some()
1169 || mime_parser.sync_items.is_some()
1170 {
1171 info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
1172 true
1173 } else if mime_parser.is_system_message == SystemMessage::CallAccepted
1174 || mime_parser.is_system_message == SystemMessage::CallEnded
1175 {
1176 info!(context, "Call state changed (TRASH).");
1177 true
1178 } else if let Some(ref decryption_error) = mime_parser.decryption_error
1179 && !mime_parser.incoming
1180 {
1181 let last_time = context
1183 .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1184 .await?;
1185 let now = tools::time();
1186 let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1187 let txt = format!(
1188 "⚠️ 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. (Error: {decryption_error}, {rfc724_mid})."
1189 );
1190 let mut msg = Message::new_text(txt.to_string());
1191 chat::add_device_msg(context, None, Some(&mut msg))
1192 .await
1193 .log_err(context)
1194 .ok();
1195 true
1196 } else {
1197 last_time > now
1198 };
1199 if update_config {
1200 context
1201 .set_config_internal(Config::LastCantDecryptOutgoingMsgs, Some(&now.to_string()))
1202 .await?;
1203 }
1204 info!(context, "Outgoing undecryptable message (TRASH).");
1205 true
1206 } else if mime_parser
1207 .get_header(HeaderDef::XMozillaDraftInfo)
1208 .is_some()
1209 {
1210 info!(context, "Email is probably just a draft (TRASH).");
1216 true
1217 } else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1218 if let Some(part) = mime_parser.parts.first() {
1219 if part.typ == Viewtype::Text && part.msg.is_empty() {
1220 info!(context, "Message is a status update only (TRASH).");
1221 markseen_on_imap_table(context, rfc724_mid).await.ok();
1222 true
1223 } else {
1224 false
1225 }
1226 } else {
1227 false
1228 }
1229 } else {
1230 false
1231 };
1232
1233 should_trash |= if mime_parser.pre_message == PreMessageMode::Post {
1234 let pre_message_exists = msg_is_downloaded_for(context, rfc724_mid).await?;
1236 info!(
1237 context,
1238 "Message {rfc724_mid} is a post-message ({}).",
1239 if pre_message_exists {
1240 "pre-message exists already, so trash after replacing attachment"
1241 } else {
1242 "no pre-message -> Keep"
1243 }
1244 );
1245 pre_message_exists
1246 } else if let PreMessageMode::Pre {
1247 post_msg_rfc724_mid,
1248 ..
1249 } = &mime_parser.pre_message
1250 {
1251 let post_msg_exists = if let Some((msg_id, not_downloaded)) =
1252 message::rfc724_mid_exists_ex(context, post_msg_rfc724_mid, "download_state<>0").await?
1253 {
1254 context
1255 .sql
1256 .execute(
1257 "UPDATE msgs SET pre_rfc724_mid=? WHERE id=?",
1258 (rfc724_mid, msg_id),
1259 )
1260 .await?;
1261 !not_downloaded
1262 } else {
1263 false
1264 };
1265 info!(
1266 context,
1267 "Message {rfc724_mid} is a pre-message for {post_msg_rfc724_mid} (post_msg_exists:{post_msg_exists})."
1268 );
1269 post_msg_exists
1270 } else {
1271 false
1272 };
1273
1274 let mut num_recipients = 0;
1279 let mut has_self_addr = false;
1280
1281 if let Some((sender_fingerprint, intended_recipient_fingerprints)) = mime_parser
1282 .signature
1283 .as_ref()
1284 .filter(|(_sender_fingerprint, fps)| !fps.is_empty())
1285 {
1286 has_self_addr = true;
1291
1292 num_recipients = intended_recipient_fingerprints
1293 .iter()
1294 .filter(|fp| *fp != sender_fingerprint)
1295 .count();
1296 } else {
1297 for recipient in &mime_parser.recipients {
1300 has_self_addr |= context.is_self_addr(&recipient.addr).await?;
1301 if addr_cmp(&recipient.addr, &mime_parser.from.addr) {
1302 continue;
1303 }
1304 num_recipients += 1;
1305 }
1306 if from_id != ContactId::SELF && !has_self_addr {
1307 num_recipients += 1;
1308 }
1309 }
1310 let mut can_be_11_chat_log = String::new();
1311 let mut l = |cond: bool, s: String| {
1312 can_be_11_chat_log += &s;
1313 cond
1314 };
1315 let can_be_11_chat = l(
1316 num_recipients <= 1,
1317 format!("num_recipients={num_recipients}."),
1318 ) && (l(from_id != ContactId::SELF, format!(" from_id={from_id}."))
1319 || !(l(
1320 mime_parser.recipients.is_empty(),
1321 format!(" Raw recipients len={}.", mime_parser.recipients.len()),
1322 ) || l(has_self_addr, format!(" has_self_addr={has_self_addr}.")))
1323 || l(
1324 mime_parser.was_encrypted(),
1325 format!(" was_encrypted={}.", mime_parser.was_encrypted()),
1326 ));
1327
1328 let chat_assignment_log;
1329 let chat_assignment = if should_trash {
1330 chat_assignment_log = "".to_string();
1331 ChatAssignment::Trash
1332 } else if mime_parser.get_mailinglist_header().is_some() {
1333 chat_assignment_log = "Mailing list header found.".to_string();
1334 ChatAssignment::MailingListOrBroadcast
1335 } else if let Some(grpid) = mime_parser.get_chat_group_id() {
1336 if mime_parser.was_encrypted() {
1337 chat_assignment_log = "Encrypted group message.".to_string();
1338 ChatAssignment::GroupChat {
1339 grpid: grpid.to_string(),
1340 }
1341 } else if let Some(parent) = &parent_message {
1342 if let Some((chat_id, chat_id_blocked)) =
1343 lookup_chat_by_reply(context, mime_parser, parent).await?
1344 {
1345 chat_assignment_log = "Unencrypted group reply.".to_string();
1347 ChatAssignment::ExistingChat {
1348 chat_id,
1349 chat_id_blocked,
1350 }
1351 } else {
1352 chat_assignment_log = "Unencrypted group reply.".to_string();
1353 ChatAssignment::AdHocGroup
1354 }
1355 } else {
1356 chat_assignment_log = "Unencrypted group message, no parent.".to_string();
1364 ChatAssignment::AdHocGroup
1365 }
1366 } else if let Some(parent) = &parent_message {
1367 if let Some((chat_id, chat_id_blocked)) =
1368 lookup_chat_by_reply(context, mime_parser, parent).await?
1369 {
1370 chat_assignment_log = "Reply w/o grpid.".to_string();
1372 ChatAssignment::ExistingChat {
1373 chat_id,
1374 chat_id_blocked,
1375 }
1376 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1377 chat_assignment_log = "Reply with Chat-Group-Name.".to_string();
1378 ChatAssignment::AdHocGroup
1379 } else if can_be_11_chat {
1380 chat_assignment_log = format!("Non-group reply. {can_be_11_chat_log}");
1381 ChatAssignment::OneOneChat
1382 } else {
1383 chat_assignment_log = format!("Non-group reply. {can_be_11_chat_log}");
1384 ChatAssignment::AdHocGroup
1385 }
1386 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1387 chat_assignment_log = "Message with Chat-Group-Name, no parent.".to_string();
1388 ChatAssignment::AdHocGroup
1389 } else if can_be_11_chat {
1390 chat_assignment_log = format!("Non-group message, no parent. {can_be_11_chat_log}");
1391 ChatAssignment::OneOneChat
1392 } else {
1393 chat_assignment_log = format!("Non-group message, no parent. {can_be_11_chat_log}");
1394 ChatAssignment::AdHocGroup
1395 };
1396
1397 if !chat_assignment_log.is_empty() {
1398 info!(
1399 context,
1400 "{chat_assignment_log} Chat assignment = {chat_assignment:?}."
1401 );
1402 }
1403 Ok(chat_assignment)
1404}
1405
1406#[expect(clippy::too_many_arguments)]
1415async fn do_chat_assignment(
1416 context: &Context,
1417 chat_assignment: &ChatAssignment,
1418 from_id: ContactId,
1419 to_ids: &[Option<ContactId>],
1420 past_ids: &[Option<ContactId>],
1421 to_id: ContactId,
1422 allow_creation: bool,
1423 mime_parser: &mut MimeMessage,
1424 parent_message: Option<Message>,
1425) -> Result<(ChatId, Blocked, bool)> {
1426 let is_bot = context.get_config_bool(Config::Bot).await?;
1427
1428 let mut chat_id = None;
1429 let mut chat_id_blocked = Blocked::Not;
1430 let mut chat_created = false;
1431
1432 if mime_parser.incoming {
1433 let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
1434
1435 let create_blocked_default = if is_bot {
1436 Blocked::Not
1437 } else {
1438 Blocked::Request
1439 };
1440 let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
1441 match blocked {
1442 Blocked::Request => create_blocked_default,
1443 Blocked::Not => Blocked::Not,
1444 Blocked::Yes => {
1445 if Contact::is_blocked_load(context, from_id).await? {
1446 Blocked::Yes
1449 } else {
1450 create_blocked_default
1454 }
1455 }
1456 }
1457 } else {
1458 create_blocked_default
1459 };
1460
1461 match &chat_assignment {
1462 ChatAssignment::Trash => {
1463 chat_id = Some(DC_CHAT_ID_TRASH);
1464 }
1465 ChatAssignment::GroupChat { grpid } => {
1466 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1468 chat_id = Some(id);
1469 chat_id_blocked = blocked;
1470 } else if (allow_creation || test_normal_chat.is_some())
1471 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1472 context,
1473 mime_parser,
1474 create_blocked,
1475 from_id,
1476 to_ids,
1477 past_ids,
1478 grpid,
1479 )
1480 .await?
1481 {
1482 chat_id = Some(new_chat_id);
1483 chat_id_blocked = new_chat_id_blocked;
1484 chat_created = true;
1485 }
1486 }
1487 ChatAssignment::MailingListOrBroadcast => {
1488 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header()
1489 && let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1490 create_or_lookup_mailinglist_or_broadcast(
1491 context,
1492 allow_creation,
1493 create_blocked,
1494 mailinglist_header,
1495 from_id,
1496 mime_parser,
1497 )
1498 .await?
1499 {
1500 chat_id = Some(new_chat_id);
1501 chat_id_blocked = new_chat_id_blocked;
1502 chat_created = new_chat_created;
1503
1504 apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
1505 }
1506 }
1507 ChatAssignment::ExistingChat {
1508 chat_id: new_chat_id,
1509 chat_id_blocked: new_chat_id_blocked,
1510 } => {
1511 chat_id = Some(*new_chat_id);
1512 chat_id_blocked = *new_chat_id_blocked;
1513 }
1514 ChatAssignment::AdHocGroup => {
1515 if let Some((new_chat_id, new_chat_id_blocked, new_created)) =
1516 lookup_or_create_adhoc_group(
1517 context,
1518 mime_parser,
1519 to_ids,
1520 allow_creation || test_normal_chat.is_some(),
1521 create_blocked,
1522 )
1523 .await?
1524 {
1525 chat_id = Some(new_chat_id);
1526 chat_id_blocked = new_chat_id_blocked;
1527 chat_created = new_created;
1528 }
1529 }
1530 ChatAssignment::OneOneChat => {}
1531 }
1532
1533 if chat_id_blocked != Blocked::Not
1536 && create_blocked != Blocked::Yes
1537 && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
1538 && let Some(chat_id) = chat_id
1539 {
1540 chat_id.set_blocked(context, create_blocked).await?;
1541 chat_id_blocked = create_blocked;
1542 }
1543
1544 if chat_id.is_none() {
1545 let contact = Contact::get_by_id(context, from_id).await?;
1547 let create_blocked = match contact.is_blocked() {
1548 true => Blocked::Yes,
1549 false if is_bot => Blocked::Not,
1550 false => Blocked::Request,
1551 };
1552
1553 if let Some(chat) = test_normal_chat {
1554 chat_id = Some(chat.id);
1555 chat_id_blocked = chat.blocked;
1556 } else if allow_creation {
1557 let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
1558 .await
1559 .context("Failed to get (new) chat for contact")?;
1560 chat_id = Some(chat.id);
1561 chat_id_blocked = chat.blocked;
1562 chat_created = true;
1563 }
1564
1565 if let Some(chat_id) = chat_id
1566 && chat_id_blocked != Blocked::Not
1567 {
1568 if chat_id_blocked != create_blocked {
1569 chat_id.set_blocked(context, create_blocked).await?;
1570 }
1571 if create_blocked == Blocked::Request && parent_message.is_some() {
1572 ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo).await?;
1575 info!(
1576 context,
1577 "Message is a reply to a known message, mark sender as known.",
1578 );
1579 }
1580 }
1581 }
1582 } else {
1583 let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1590
1591 match &chat_assignment {
1592 ChatAssignment::Trash => {
1593 chat_id = Some(DC_CHAT_ID_TRASH);
1594 }
1595 ChatAssignment::GroupChat { grpid } => {
1596 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1597 chat_id = Some(id);
1598 chat_id_blocked = blocked;
1599 } else if allow_creation
1600 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1601 context,
1602 mime_parser,
1603 Blocked::Not,
1604 from_id,
1605 to_ids,
1606 past_ids,
1607 grpid,
1608 )
1609 .await?
1610 {
1611 chat_id = Some(new_chat_id);
1612 chat_id_blocked = new_chat_id_blocked;
1613 chat_created = true;
1614 }
1615 }
1616 ChatAssignment::ExistingChat {
1617 chat_id: new_chat_id,
1618 chat_id_blocked: new_chat_id_blocked,
1619 } => {
1620 chat_id = Some(*new_chat_id);
1621 chat_id_blocked = *new_chat_id_blocked;
1622 }
1623 ChatAssignment::MailingListOrBroadcast => {
1624 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1627 let listid = mailinglist_header_listid(mailinglist_header)?;
1628 if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await? {
1629 chat_id = Some(id);
1630 } else {
1631 let name =
1633 compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1634 if let Some(secret) = mime_parser
1635 .get_header(HeaderDef::ChatBroadcastSecret)
1636 .filter(|s| validate_broadcast_secret(s))
1637 {
1638 chat_created = true;
1639 chat_id = Some(
1640 chat::create_out_broadcast_ex(
1641 context,
1642 Nosync,
1643 listid,
1644 name,
1645 secret.to_string(),
1646 )
1647 .await?,
1648 );
1649 } else {
1650 warn!(
1651 context,
1652 "Not creating outgoing broadcast with id {listid}, because secret is unknown"
1653 );
1654 }
1655 }
1656 }
1657 }
1658 ChatAssignment::AdHocGroup => {
1659 if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1660 lookup_or_create_adhoc_group(
1661 context,
1662 mime_parser,
1663 to_ids,
1664 allow_creation,
1665 Blocked::Not,
1666 )
1667 .await?
1668 {
1669 chat_id = Some(new_chat_id);
1670 chat_id_blocked = new_chat_id_blocked;
1671 chat_created = new_chat_created;
1672 }
1673 }
1674 ChatAssignment::OneOneChat => {}
1675 }
1676
1677 if !to_ids.is_empty() {
1678 if chat_id.is_none() && allow_creation {
1679 let to_contact = Contact::get_by_id(context, to_id).await?;
1680 if let Some(list_id) = to_contact.param.get(Param::ListId) {
1681 if let Some((id, blocked)) =
1682 chat::get_chat_id_by_grpid(context, list_id).await?
1683 {
1684 chat_id = Some(id);
1685 chat_id_blocked = blocked;
1686 }
1687 } else {
1688 let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1689 chat_id = Some(chat.id);
1690 chat_id_blocked = chat.blocked;
1691 chat_created = true;
1692 }
1693 }
1694 if chat_id.is_none()
1695 && mime_parser.has_chat_version()
1696 && let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await?
1697 {
1698 chat_id = Some(chat.id);
1699 chat_id_blocked = chat.blocked;
1700 }
1701 }
1702
1703 if chat_id.is_none() && self_sent {
1704 let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1707 .await
1708 .context("Failed to get (new) chat for contact")?;
1709
1710 chat_id = Some(chat.id);
1711 chat_id_blocked = chat.blocked;
1712
1713 if Blocked::Not != chat.blocked {
1714 chat.id.unblock_ex(context, Nosync).await?;
1715 }
1716 }
1717
1718 if chat_id_blocked != Blocked::Not
1720 && let Some(chat_id) = chat_id
1721 {
1722 chat_id.unblock_ex(context, Nosync).await?;
1723 chat_id_blocked = Blocked::Not;
1724 }
1725 }
1726 let chat_id = chat_id.unwrap_or_else(|| {
1727 info!(context, "No chat id for message (TRASH).");
1728 DC_CHAT_ID_TRASH
1729 });
1730 Ok((chat_id, chat_id_blocked, chat_created))
1731}
1732
1733#[expect(clippy::too_many_arguments)]
1737async fn add_parts(
1738 context: &Context,
1739 mime_parser: &mut MimeMessage,
1740 imf_raw: &[u8],
1741 to_ids: &[Option<ContactId>],
1742 past_ids: &[Option<ContactId>],
1743 rfc724_mid: &str,
1744 from_id: ContactId,
1745 seen: bool,
1746 prevent_rename: bool,
1747 mut chat_id: ChatId,
1748 mut chat_id_blocked: Blocked,
1749 is_dc_message: MessengerMessage,
1750 is_chat_created: bool,
1751) -> Result<ReceivedMsg> {
1752 let to_id = if mime_parser.incoming {
1753 ContactId::SELF
1754 } else {
1755 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
1756 };
1757
1758 if prevent_rename && let Some(name) = &mime_parser.from.display_name {
1761 for part in &mut mime_parser.parts {
1762 part.param.set(Param::OverrideSenderDisplayname, name);
1763 }
1764 }
1765
1766 let mut chat = Chat::load_from_db(context, chat_id).await?;
1767
1768 if mime_parser.incoming && !chat_id.is_trash() {
1769 if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
1772 let from = &mime_parser.from;
1776 let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
1777 for part in &mut mime_parser.parts {
1778 part.param.set(Param::OverrideSenderDisplayname, name);
1779 }
1780
1781 if chat.typ == Chattype::InBroadcast {
1782 warn!(
1783 context,
1784 "Not assigning msg '{rfc724_mid}' to broadcast {chat_id}: wrong sender: {from_id}."
1785 );
1786 let direct_chat =
1787 ChatIdBlocked::get_for_contact(context, from_id, Blocked::Request).await?;
1788 chat_id = direct_chat.id;
1789 chat_id_blocked = direct_chat.blocked;
1790 chat = Chat::load_from_db(context, chat_id).await?;
1791 }
1792 }
1793 }
1794
1795 let sort_to_bottom = !chat.is_self_in_chat(context).await?;
1804
1805 let is_location_kml = mime_parser.location_kml.is_some();
1806 let mut group_changes = match chat.typ {
1807 _ if chat.id.is_special() => GroupChangesInfo::default(),
1808 Chattype::Single => GroupChangesInfo::default(),
1809 Chattype::Mailinglist => GroupChangesInfo::default(),
1810 Chattype::OutBroadcast => {
1811 apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1812 }
1813 Chattype::Group => {
1814 apply_group_changes(
1815 context,
1816 mime_parser,
1817 &mut chat,
1818 from_id,
1819 to_ids,
1820 past_ids,
1821 is_chat_created,
1822 )
1823 .await?
1824 }
1825 Chattype::InBroadcast => {
1826 apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1827 }
1828 };
1829
1830 let rfc724_mid_orig = &mime_parser
1831 .get_rfc724_mid()
1832 .unwrap_or(rfc724_mid.to_string());
1833
1834 let mut ephemeral_timer = if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer)
1836 {
1837 match EphemeralTimer::from_str(value) {
1838 Ok(timer) => timer,
1839 Err(err) => {
1840 warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1841 EphemeralTimer::Disabled
1842 }
1843 }
1844 } else {
1845 EphemeralTimer::Disabled
1846 };
1847
1848 let state = if !mime_parser.incoming {
1849 MessageState::OutDelivered
1850 } else if seen
1851 || !mime_parser.mdn_reports.is_empty()
1852 || chat_id_blocked == Blocked::Yes
1853 || group_changes.silent
1854 {
1856 MessageState::InSeen
1857 } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL {
1858 MessageState::InNoticed
1859 } else {
1860 MessageState::InFresh
1861 };
1862 let in_fresh = state == MessageState::InFresh;
1863
1864 let sort_timestamp = chat_id
1865 .calc_sort_timestamp(context, mime_parser.timestamp_sent, sort_to_bottom)
1866 .await?;
1867
1868 if !chat_id.is_special()
1874 && !mime_parser.parts.is_empty()
1875 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1876 {
1877 let chat_contacts =
1878 BTreeSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1879 let is_from_in_chat =
1880 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1881
1882 info!(
1883 context,
1884 "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1885 );
1886 if !is_from_in_chat {
1887 warn!(
1888 context,
1889 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1890 );
1891 } else if is_dc_message == MessengerMessage::Yes
1892 && get_previous_message(context, mime_parser)
1893 .await?
1894 .map(|p| p.ephemeral_timer)
1895 == Some(ephemeral_timer)
1896 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1897 {
1898 warn!(
1905 context,
1906 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1907 );
1908 } else if chat_id
1909 .update_timestamp(
1910 context,
1911 Param::EphemeralSettingsTimestamp,
1912 mime_parser.timestamp_sent,
1913 )
1914 .await?
1915 {
1916 if let Err(err) = chat_id
1917 .inner_set_ephemeral_timer(context, ephemeral_timer)
1918 .await
1919 {
1920 warn!(
1921 context,
1922 "Failed to modify timer for chat {chat_id}: {err:#}."
1923 );
1924 } else {
1925 info!(
1926 context,
1927 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1928 );
1929 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1930 chat::add_info_msg_with_cmd(
1931 context,
1932 chat_id,
1933 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1934 SystemMessage::Unknown,
1935 Some(sort_timestamp),
1936 mime_parser.timestamp_sent,
1937 None,
1938 None,
1939 None,
1940 )
1941 .await?;
1942 }
1943 }
1944 } else {
1945 warn!(
1946 context,
1947 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1948 );
1949 }
1950 }
1951
1952 let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1953 {
1954 Some(stock_str::msg_location_enabled_by(context, from_id).await)
1955 } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1956 let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1957
1958 ephemeral_timer = EphemeralTimer::Disabled;
1965
1966 Some(better_msg)
1967 } else {
1968 None
1969 };
1970
1971 drop(chat); let sort_timestamp = tweak_sort_timestamp(
1974 context,
1975 mime_parser,
1976 group_changes.silent,
1977 chat_id,
1978 sort_timestamp,
1979 )
1980 .await?;
1981
1982 let mime_in_reply_to = mime_parser
1983 .get_header(HeaderDef::InReplyTo)
1984 .unwrap_or_default();
1985 let mime_references = mime_parser
1986 .get_header(HeaderDef::References)
1987 .unwrap_or_default();
1988
1989 let icnt = mime_parser.parts.len();
1994
1995 let subject = mime_parser.get_subject().unwrap_or_default();
1996
1997 let is_system_message = mime_parser.is_system_message;
1998
1999 let mut save_mime_modified = false;
2006
2007 let mime_headers = if mime_parser.is_mime_modified {
2008 let headers = if !mime_parser.decoded_data.is_empty() {
2009 mime_parser.decoded_data.clone()
2010 } else {
2011 imf_raw.to_vec()
2012 };
2013 tokio::task::block_in_place(move || buf_compress(&headers))?
2014 } else {
2015 Vec::new()
2016 };
2017
2018 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
2019
2020 if let Some(m) = group_changes.better_msg {
2021 match &better_msg {
2022 None => better_msg = Some(m),
2023 Some(_) => {
2024 if !m.is_empty() {
2025 group_changes.extra_msgs.push((m, is_system_message, None))
2026 }
2027 }
2028 }
2029 }
2030
2031 let chat_id = if better_msg
2032 .as_ref()
2033 .is_some_and(|better_msg| better_msg.is_empty())
2034 {
2035 DC_CHAT_ID_TRASH
2036 } else {
2037 chat_id
2038 };
2039
2040 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
2041 chat::add_info_msg_with_cmd(
2042 context,
2043 chat_id,
2044 &group_changes_msg,
2045 cmd,
2046 Some(sort_timestamp),
2047 mime_parser.timestamp_sent,
2048 None,
2049 None,
2050 added_removed_id,
2051 )
2052 .await?;
2053 }
2054
2055 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
2056 match mime_parser.get_header(HeaderDef::InReplyTo) {
2057 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
2058 Some(instance_id) => {
2059 if let Err(err) =
2060 add_gossip_peer_from_header(context, instance_id, node_addr).await
2061 {
2062 warn!(context, "Failed to add iroh peer from header: {err:#}.");
2063 }
2064 }
2065 None => {
2066 warn!(
2067 context,
2068 "Cannot add iroh peer because WebXDC instance does not exist."
2069 );
2070 }
2071 },
2072 None => {
2073 warn!(
2074 context,
2075 "Cannot add iroh peer because the message has no In-Reply-To."
2076 );
2077 }
2078 }
2079 }
2080
2081 handle_edit_delete(context, mime_parser, from_id).await?;
2082 handle_post_message(context, mime_parser, from_id, state).await?;
2083
2084 if mime_parser.is_system_message == SystemMessage::CallAccepted
2085 || mime_parser.is_system_message == SystemMessage::CallEnded
2086 {
2087 if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
2088 if let Some(call) =
2089 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
2090 {
2091 context
2092 .handle_call_msg(call.get_id(), mime_parser, from_id)
2093 .await?;
2094 } else {
2095 warn!(context, "Call: Cannot load parent.")
2096 }
2097 } else {
2098 warn!(context, "Call: Not a reply.")
2099 }
2100 }
2101
2102 let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
2103 let mut parts = mime_parser.parts.iter().peekable();
2104 while let Some(part) = parts.next() {
2105 let hidden = part.is_reaction;
2106 if part.is_reaction {
2107 let reaction_str = simplify::remove_footers(part.msg.as_str());
2108 let is_incoming_fresh = mime_parser.incoming && !seen;
2109 set_msg_reaction(
2110 context,
2111 mime_in_reply_to,
2112 chat_id,
2113 from_id,
2114 sort_timestamp,
2115 Reaction::new(reaction_str.as_str()),
2116 is_incoming_fresh,
2117 )
2118 .await?;
2119 }
2120
2121 let mut param = part.param.clone();
2122 if is_system_message != SystemMessage::Unknown {
2123 param.set_int(Param::Cmd, is_system_message as i32);
2124 }
2125
2126 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
2127 (better_msg, Viewtype::Text)
2128 } else {
2129 (&part.msg, part.typ)
2130 };
2131 let part_is_empty =
2132 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
2133
2134 if let Some(contact_id) = group_changes.added_removed_id {
2135 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
2136 }
2137
2138 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
2139 let save_mime_modified = save_mime_modified && parts.peek().is_none();
2140
2141 let ephemeral_timestamp = if in_fresh {
2142 0
2143 } else {
2144 match ephemeral_timer {
2145 EphemeralTimer::Disabled => 0,
2146 EphemeralTimer::Enabled { duration } => {
2147 mime_parser.timestamp_rcvd.saturating_add(duration.into())
2148 }
2149 }
2150 };
2151
2152 if let PreMessageMode::Pre {
2153 metadata: Some(metadata),
2154 ..
2155 } = &mime_parser.pre_message
2156 {
2157 param.apply_post_msg_metadata(metadata);
2158 };
2159
2160 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2163
2164 let row_id = context
2165 .sql
2166 .call_write(|conn| {
2167 let mut stmt = conn.prepare_cached(
2168 "
2169INSERT INTO msgs
2170 (
2171 rfc724_mid, pre_rfc724_mid, chat_id,
2172 from_id, to_id, timestamp, timestamp_sent,
2173 timestamp_rcvd, type, state, msgrmsg,
2174 txt, txt_normalized, subject, param, hidden,
2175 bytes, mime_headers, mime_compressed, mime_in_reply_to,
2176 mime_references, mime_modified, error, ephemeral_timer,
2177 ephemeral_timestamp, download_state, hop_info
2178 )
2179 VALUES (
2180 ?, ?, ?, ?, ?,
2181 ?, ?, ?, ?,
2182 ?, ?, ?, ?,
2183 ?, ?, ?, ?, ?, 1,
2184 ?, ?, ?, ?,
2185 ?, ?, ?, ?
2186 )",
2187 )?;
2188 let params = params![
2189 if let PreMessageMode::Pre {
2190 post_msg_rfc724_mid,
2191 ..
2192 } = &mime_parser.pre_message
2193 {
2194 post_msg_rfc724_mid
2195 } else {
2196 rfc724_mid_orig
2197 },
2198 if let PreMessageMode::Pre { .. } = &mime_parser.pre_message {
2199 rfc724_mid_orig
2200 } else {
2201 ""
2202 },
2203 if trash { DC_CHAT_ID_TRASH } else { chat_id },
2204 if trash { ContactId::UNDEFINED } else { from_id },
2205 if trash { ContactId::UNDEFINED } else { to_id },
2206 sort_timestamp,
2207 if trash { 0 } else { mime_parser.timestamp_sent },
2208 if trash { 0 } else { mime_parser.timestamp_rcvd },
2209 if trash {
2210 Viewtype::Unknown
2211 } else if let PreMessageMode::Pre { .. } = mime_parser.pre_message {
2212 Viewtype::Text
2213 } else {
2214 typ
2215 },
2216 if trash {
2217 MessageState::Undefined
2218 } else {
2219 state
2220 },
2221 if trash {
2222 MessengerMessage::No
2223 } else {
2224 is_dc_message
2225 },
2226 if trash || hidden { "" } else { msg },
2227 if trash || hidden {
2228 None
2229 } else {
2230 normalize_text(msg)
2231 },
2232 if trash || hidden { "" } else { &subject },
2233 if trash {
2234 "".to_string()
2235 } else {
2236 param.to_string()
2237 },
2238 !trash && hidden,
2239 if trash { 0 } else { part.bytes as isize },
2240 if save_mime_modified && !(trash || hidden) {
2241 mime_headers.clone()
2242 } else {
2243 Vec::new()
2244 },
2245 if trash { "" } else { mime_in_reply_to },
2246 if trash { "" } else { mime_references },
2247 !trash && save_mime_modified,
2248 if trash {
2249 ""
2250 } else {
2251 part.error.as_deref().unwrap_or_default()
2252 },
2253 if trash { 0 } else { ephemeral_timer.to_u32() },
2254 if trash { 0 } else { ephemeral_timestamp },
2255 if trash {
2256 DownloadState::Done
2257 } else if mime_parser.decryption_error.is_some() {
2258 DownloadState::Undecipherable
2259 } else if let PreMessageMode::Pre { .. } = mime_parser.pre_message {
2260 DownloadState::Available
2261 } else {
2262 DownloadState::Done
2263 },
2264 if trash { "" } else { &mime_parser.hop_info },
2265 ];
2266 let row_id = MsgId::new(stmt.insert(params)?.try_into()?);
2267 Ok(row_id)
2268 })
2269 .await?;
2270 ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2271 created_db_entries.push(row_id);
2272 }
2273
2274 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2276 if mime_parser.pre_message != PreMessageMode::Post
2277 && part.typ == Viewtype::Webxdc
2278 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2279 {
2280 let topic = iroh_topic_from_str(topic)?;
2281 insert_topic_stub(context, *msg_id, topic).await?;
2282 }
2283
2284 maybe_set_logging_xdc_inner(
2285 context,
2286 part.typ,
2287 chat_id,
2288 part.param.get(Param::Filename),
2289 *msg_id,
2290 )
2291 .await?;
2292 }
2293
2294 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2295 Some(addr) => context.is_self_addr(addr).await?,
2296 None => true,
2297 };
2298 if unarchive {
2299 chat_id.unarchive_if_not_muted(context, state).await?;
2300 }
2301
2302 info!(
2303 context,
2304 "Message has {icnt} parts and is assigned to chat #{chat_id}, timestamp={sort_timestamp}."
2305 );
2306
2307 if !chat_id.is_trash() && !hidden {
2308 let mut chat = Chat::load_from_db(context, chat_id).await?;
2309 let mut update_param = false;
2310
2311 if chat
2315 .param
2316 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2317 {
2318 let subject = mime_parser.get_subject().unwrap_or_default();
2321
2322 chat.param.set(Param::LastSubject, subject);
2323 update_param = true;
2324 }
2325
2326 if chat.is_unpromoted() {
2327 chat.param.remove(Param::Unpromoted);
2328 update_param = true;
2329 }
2330 if update_param {
2331 chat.update_param(context).await?;
2332 }
2333 }
2334
2335 Ok(ReceivedMsg {
2336 chat_id,
2337 state,
2338 hidden,
2339 sort_timestamp,
2340 msg_ids: created_db_entries,
2341 needs_delete_job: false,
2342 })
2343}
2344
2345async fn handle_edit_delete(
2348 context: &Context,
2349 mime_parser: &MimeMessage,
2350 from_id: ContactId,
2351) -> Result<()> {
2352 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2353 if let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? {
2354 if let Some(mut original_msg) =
2355 Message::load_from_db_optional(context, original_msg_id).await?
2356 {
2357 if original_msg.from_id == from_id {
2358 if let Some(part) = mime_parser.parts.first() {
2359 let edit_msg_showpadlock = part
2360 .param
2361 .get_bool(Param::GuaranteeE2ee)
2362 .unwrap_or_default();
2363 if edit_msg_showpadlock || !original_msg.get_showpadlock() {
2364 let new_text =
2365 part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2366 chat::save_text_edit_to_db(context, &mut original_msg, new_text)
2367 .await?;
2368 } else {
2369 warn!(context, "Edit message: Not encrypted.");
2370 }
2371 }
2372 } else {
2373 warn!(context, "Edit message: Bad sender.");
2374 }
2375 } else {
2376 warn!(context, "Edit message: Database entry does not exist.");
2377 }
2378 } else {
2379 warn!(
2380 context,
2381 "Edit message: rfc724_mid {rfc724_mid:?} not found."
2382 );
2383 }
2384 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
2385 && let Some(part) = mime_parser.parts.first()
2386 {
2387 if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
2390 let mut modified_chat_ids = BTreeSet::new();
2391 let mut msg_ids = Vec::new();
2392
2393 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2394 for rfc724_mid in rfc724_mid_vec {
2395 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2396 if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
2397 if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
2398 if msg.from_id == from_id {
2399 message::delete_msg_locally(context, &msg).await?;
2400 msg_ids.push(msg.id);
2401 modified_chat_ids.insert(msg.chat_id);
2402 } else {
2403 warn!(context, "Delete message: Bad sender.");
2404 }
2405 } else {
2406 warn!(context, "Delete message: Database entry does not exist.");
2407 }
2408 } else {
2409 warn!(context, "Delete message: {rfc724_mid:?} not found.");
2410 insert_tombstone(context, rfc724_mid).await?;
2412 }
2413 }
2414 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2415 } else {
2416 warn!(context, "Delete message: Not encrypted.");
2417 }
2418 }
2419 Ok(())
2420}
2421
2422async fn handle_post_message(
2423 context: &Context,
2424 mime_parser: &MimeMessage,
2425 from_id: ContactId,
2426 state: MessageState,
2427) -> Result<()> {
2428 let PreMessageMode::Post = &mime_parser.pre_message else {
2429 return Ok(());
2430 };
2431 let rfc724_mid = mime_parser
2434 .get_rfc724_mid()
2435 .context("expected Post-Message to have a message id")?;
2436
2437 let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
2438 warn!(
2439 context,
2440 "handle_post_message: {rfc724_mid}: Database entry does not exist."
2441 );
2442 return Ok(());
2443 };
2444 let Some(original_msg) = Message::load_from_db_optional(context, msg_id).await? else {
2445 warn!(
2447 context,
2448 "handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
2449 );
2450 return Ok(());
2451 };
2452 let Some(part) = mime_parser.parts.first() else {
2453 return Ok(());
2454 };
2455
2456 if from_id != original_msg.from_id {
2459 warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
2460 return Ok(());
2461 }
2462 let post_msg_showpadlock = part
2463 .param
2464 .get_bool(Param::GuaranteeE2ee)
2465 .unwrap_or_default();
2466 if !post_msg_showpadlock && original_msg.get_showpadlock() {
2467 warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
2468 return Ok(());
2469 }
2470
2471 if !part.typ.has_file() {
2472 warn!(
2473 context,
2474 "handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
2475 );
2476 return Ok(());
2477 }
2478
2479 if part.typ == Viewtype::Webxdc
2480 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2481 {
2482 let topic = iroh_topic_from_str(topic)?;
2483 insert_topic_stub(context, msg_id, topic).await?;
2484 }
2485
2486 let mut new_params = original_msg.param.clone();
2487 new_params
2488 .merge_in_params(part.param.clone())
2489 .remove(Param::PostMessageFileBytes)
2490 .remove(Param::PostMessageViewtype);
2491 context
2494 .sql
2495 .execute(
2496 "
2497UPDATE msgs SET param=?, type=?, bytes=?, error=?, state=max(state,?), download_state=?
2498WHERE id=?
2499 ",
2500 (
2501 new_params.to_string(),
2502 part.typ,
2503 part.bytes as isize,
2504 part.error.as_deref().unwrap_or_default(),
2505 state,
2506 DownloadState::Done as u32,
2507 original_msg.id,
2508 ),
2509 )
2510 .await?;
2511
2512 if context.get_config_bool(Config::Bot).await? {
2513 if original_msg.hidden {
2514 } else if !original_msg.chat_id.is_trash() {
2516 let fresh = original_msg.state == MessageState::InFresh;
2517 let important = mime_parser.incoming && fresh;
2518
2519 original_msg
2520 .chat_id
2521 .emit_msg_event(context, original_msg.id, important);
2522 context.new_msgs_notify.notify_one();
2523 }
2524 } else {
2525 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2526 }
2527
2528 Ok(())
2529}
2530
2531async fn tweak_sort_timestamp(
2532 context: &Context,
2533 mime_parser: &mut MimeMessage,
2534 silent: bool,
2535 chat_id: ChatId,
2536 sort_timestamp: i64,
2537) -> Result<i64> {
2538 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2547 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2548 std::cmp::max(sort_timestamp, parent_timestamp)
2549 });
2550
2551 if silent {
2555 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2556 t
2557 } else {
2558 chat_id.created_timestamp(context).await?
2559 };
2560 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2561 }
2562 Ok(sort_timestamp)
2563}
2564
2565async fn save_locations(
2569 context: &Context,
2570 mime_parser: &MimeMessage,
2571 chat_id: ChatId,
2572 from_id: ContactId,
2573 msg_id: MsgId,
2574) -> Result<()> {
2575 if chat_id.is_special() {
2576 return Ok(());
2578 }
2579
2580 let mut send_event = false;
2581
2582 if let Some(message_kml) = &mime_parser.message_kml
2583 && let Some(newest_location_id) =
2584 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2585 {
2586 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2587 send_event = true;
2588 }
2589
2590 if let Some(location_kml) = &mime_parser.location_kml
2591 && let Some(addr) = &location_kml.addr
2592 {
2593 let contact = Contact::get_by_id(context, from_id).await?;
2594 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2595 if location::save(context, chat_id, from_id, &location_kml.locations, false)
2596 .await?
2597 .is_some()
2598 {
2599 send_event = true;
2600 }
2601 } else {
2602 warn!(
2603 context,
2604 "Address in location.kml {:?} is not the same as the sender address {:?}.",
2605 addr,
2606 contact.get_addr()
2607 );
2608 }
2609 }
2610 if send_event {
2611 context.emit_location_changed(Some(from_id)).await?;
2612 }
2613 Ok(())
2614}
2615
2616async fn lookup_chat_by_reply(
2617 context: &Context,
2618 mime_parser: &MimeMessage,
2619 parent: &Message,
2620) -> Result<Option<(ChatId, Blocked)>> {
2621 ensure_and_debug_assert!(
2626 mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2627 "Encrypted message has group ID {}",
2628 mime_parser.get_chat_group_id().unwrap_or_default(),
2629 );
2630
2631 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2633 return Ok(None);
2634 };
2635
2636 if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2639 return Ok(None);
2640 }
2641
2642 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2646 if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2647 return Ok(None);
2648 }
2649
2650 if parent_chat.is_encrypted(context).await? && !mime_parser.was_encrypted() {
2652 return Ok(None);
2653 }
2654
2655 info!(
2656 context,
2657 "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2658 );
2659 Ok(Some((parent_chat.id, parent_chat.blocked)))
2660}
2661
2662async fn lookup_or_create_adhoc_group(
2663 context: &Context,
2664 mime_parser: &MimeMessage,
2665 to_ids: &[Option<ContactId>],
2666 allow_creation: bool,
2667 create_blocked: Blocked,
2668) -> Result<Option<(ChatId, Blocked, bool)>> {
2669 if mime_parser.decryption_error.is_some() {
2670 warn!(
2671 context,
2672 "Not creating ad-hoc group for message that cannot be decrypted."
2673 );
2674 return Ok(None);
2675 }
2676
2677 let fingerprint = None;
2679 let find_key_contact_by_addr = false;
2680 let prevent_rename = should_prevent_rename(mime_parser);
2681 let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2682 context,
2683 &mime_parser.from,
2684 fingerprint,
2685 prevent_rename,
2686 find_key_contact_by_addr,
2687 )
2688 .await?
2689 .context("Cannot lookup address-contact by the From field")?;
2690
2691 let grpname = mime_parser
2692 .get_header(HeaderDef::ChatGroupName)
2693 .map(|s| s.to_string())
2694 .unwrap_or_else(|| {
2695 mime_parser
2696 .get_subject()
2697 .map(|s| remove_subject_prefix(&s))
2698 .unwrap_or_else(|| "👥📧".to_string())
2699 });
2700 let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2701 let mut contact_ids = BTreeSet::<ContactId>::from_iter(to_ids.iter().copied());
2702 contact_ids.insert(from_id);
2703 if mime_parser.was_encrypted() {
2704 contact_ids.remove(&ContactId::SELF);
2705 }
2706 let trans_fn = |t: &mut rusqlite::Transaction| {
2707 t.pragma_update(None, "query_only", "0")?;
2708 t.execute(
2709 "CREATE TEMP TABLE temp.contacts (
2710 id INTEGER PRIMARY KEY
2711 ) STRICT",
2712 (),
2713 )
2714 .context("CREATE TEMP TABLE temp.contacts")?;
2715 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2716 for &id in &contact_ids {
2717 stmt.execute((id,)).context("INSERT INTO temp.contacts")?;
2718 }
2719 let val = t
2720 .query_row(
2721 "SELECT c.id, c.blocked
2722 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2723 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2724 AND (SELECT COUNT(*) FROM chats_contacts
2725 WHERE chat_id=c.id
2726 AND add_timestamp >= remove_timestamp)=?
2727 AND (SELECT COUNT(*) FROM chats_contacts
2728 WHERE chat_id=c.id
2729 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2730 AND add_timestamp >= remove_timestamp)=0
2731 ORDER BY m.timestamp DESC",
2732 (&grpname, contact_ids.len()),
2733 |row| {
2734 let id: ChatId = row.get(0)?;
2735 let blocked: Blocked = row.get(1)?;
2736 Ok((id, blocked))
2737 },
2738 )
2739 .optional()
2740 .context("Select chat with matching name and members")?;
2741 t.execute("DROP TABLE temp.contacts", ())
2742 .context("DROP TABLE temp.contacts")?;
2743 Ok(val)
2744 };
2745 let query_only = true;
2746 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2747 info!(
2748 context,
2749 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2750 );
2751 return Ok(Some((chat_id, blocked, false)));
2752 }
2753 if !allow_creation {
2754 return Ok(None);
2755 }
2756 Ok(create_adhoc_group(
2757 context,
2758 mime_parser,
2759 create_blocked,
2760 from_id,
2761 &to_ids,
2762 &grpname,
2763 )
2764 .await
2765 .context("Could not create ad hoc group")?
2766 .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2767}
2768
2769async fn is_probably_private_reply(
2772 context: &Context,
2773 mime_parser: &MimeMessage,
2774 parent_chat_id: ChatId,
2775) -> Result<bool> {
2776 if mime_parser.get_chat_group_id().is_some() {
2778 return Ok(false);
2779 }
2780
2781 if mime_parser.recipients.len() != 1 {
2789 return Ok(false);
2790 }
2791
2792 if !mime_parser.has_chat_version() {
2793 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2794 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2795 return Ok(false);
2796 }
2797 }
2798
2799 Ok(true)
2800}
2801
2802async fn create_group(
2808 context: &Context,
2809 mime_parser: &mut MimeMessage,
2810 create_blocked: Blocked,
2811 from_id: ContactId,
2812 to_ids: &[Option<ContactId>],
2813 past_ids: &[Option<ContactId>],
2814 grpid: &str,
2815) -> Result<Option<(ChatId, Blocked)>> {
2816 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2817 let mut chat_id = None;
2818 let mut chat_id_blocked = Default::default();
2819
2820 if !mime_parser.is_mailinglist_message()
2821 && !grpid.is_empty()
2822 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2823 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2825 {
2826 let grpname = mime_parser
2828 .get_header(HeaderDef::ChatGroupName)
2829 .context("Chat-Group-Name vanished")?
2830 .trim();
2834 let new_chat_id = ChatId::create_multiuser_record(
2835 context,
2836 Chattype::Group,
2837 grpid,
2838 grpname,
2839 create_blocked,
2840 None,
2841 mime_parser.timestamp_sent,
2842 )
2843 .await
2844 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2845
2846 chat_id = Some(new_chat_id);
2847 chat_id_blocked = create_blocked;
2848
2849 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2851 let mut new_to_ids = to_ids.to_vec();
2852 if !new_to_ids.contains(&Some(from_id)) {
2853 new_to_ids.insert(0, Some(from_id));
2854 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2855 }
2856
2857 update_chats_contacts_timestamps(
2858 context,
2859 new_chat_id,
2860 None,
2861 &new_to_ids,
2862 past_ids,
2863 &chat_group_member_timestamps,
2864 )
2865 .await?;
2866 } else {
2867 let mut members = vec![ContactId::SELF];
2868 if !from_id.is_special() {
2869 members.push(from_id);
2870 }
2871 members.extend(to_ids_flat);
2872
2873 let timestamp = 0;
2879
2880 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2881 }
2882
2883 context.emit_event(EventType::ChatModified(new_chat_id));
2884 chatlist_events::emit_chatlist_changed(context);
2885 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2886 }
2887
2888 if let Some(chat_id) = chat_id {
2889 Ok(Some((chat_id, chat_id_blocked)))
2890 } else if mime_parser.decryption_error.is_some() {
2891 Ok(None)
2898 } else {
2899 info!(context, "Message belongs to unwanted group (TRASH).");
2902 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2903 }
2904}
2905
2906#[expect(clippy::arithmetic_side_effects)]
2907async fn update_chats_contacts_timestamps(
2908 context: &Context,
2909 chat_id: ChatId,
2910 ignored_id: Option<ContactId>,
2911 to_ids: &[Option<ContactId>],
2912 past_ids: &[Option<ContactId>],
2913 chat_group_member_timestamps: &[i64],
2914) -> Result<bool> {
2915 let expected_timestamps_count = to_ids.len() + past_ids.len();
2916
2917 if chat_group_member_timestamps.len() != expected_timestamps_count {
2918 warn!(
2919 context,
2920 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2921 chat_group_member_timestamps.len(),
2922 expected_timestamps_count
2923 );
2924 return Ok(false);
2925 }
2926
2927 let mut modified = false;
2928
2929 context
2930 .sql
2931 .transaction(|transaction| {
2932 let mut add_statement = transaction.prepare(
2933 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2934 VALUES (?1, ?2, ?3)
2935 ON CONFLICT (chat_id, contact_id)
2936 DO
2937 UPDATE SET add_timestamp=?3
2938 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2939 )?;
2940
2941 for (contact_id, ts) in iter::zip(
2942 to_ids.iter(),
2943 chat_group_member_timestamps.iter().take(to_ids.len()),
2944 ) {
2945 if let Some(contact_id) = contact_id
2946 && Some(*contact_id) != ignored_id
2947 {
2948 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2952 }
2953 }
2954
2955 let mut remove_statement = transaction.prepare(
2956 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2957 VALUES (?1, ?2, ?3)
2958 ON CONFLICT (chat_id, contact_id)
2959 DO
2960 UPDATE SET remove_timestamp=?3
2961 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2962 )?;
2963
2964 for (contact_id, ts) in iter::zip(
2965 past_ids.iter(),
2966 chat_group_member_timestamps.iter().skip(to_ids.len()),
2967 ) {
2968 if let Some(contact_id) = contact_id {
2969 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2973 }
2974 }
2975
2976 Ok(())
2977 })
2978 .await?;
2979
2980 Ok(modified)
2981}
2982
2983#[derive(Default)]
2987struct GroupChangesInfo {
2988 better_msg: Option<String>,
2991 added_removed_id: Option<ContactId>,
2993 silent: bool,
2995 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
2997}
2998
2999async fn apply_group_changes(
3006 context: &Context,
3007 mime_parser: &mut MimeMessage,
3008 chat: &mut Chat,
3009 from_id: ContactId,
3010 to_ids: &[Option<ContactId>],
3011 past_ids: &[Option<ContactId>],
3012 is_chat_created: bool,
3013) -> Result<GroupChangesInfo> {
3014 let from_is_key_contact = Contact::get_by_id(context, from_id).await?.is_key_contact();
3015 ensure!(from_is_key_contact || chat.grpid.is_empty());
3016 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
3017 ensure!(chat.typ == Chattype::Group);
3018 ensure!(!chat.id.is_special());
3019
3020 let mut send_event_chat_modified = false;
3021 let (mut removed_id, mut added_id) = (None, None);
3022 let mut better_msg = None;
3023 let mut silent = false;
3024 let chat_contacts =
3025 BTreeSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
3026 let is_from_in_chat =
3027 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
3028
3029 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3030 if !is_from_in_chat {
3031 better_msg = Some(String::new());
3032 } else if let Some(removed_fpr) =
3033 mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr)
3034 {
3035 removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3036 } else {
3037 removed_id =
3039 lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?;
3040 }
3041 if let Some(id) = removed_id {
3042 better_msg = if id == from_id {
3043 silent = true;
3044 Some(stock_str::msg_group_left_local(context, from_id).await)
3045 } else {
3046 Some(stock_str::msg_del_member_local(context, id, from_id).await)
3047 };
3048 } else {
3049 warn!(context, "Removed {removed_addr:?} has no contact id.")
3050 }
3051 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3052 if !is_from_in_chat {
3053 better_msg = Some(String::new());
3054 } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
3055 if !chat_contacts.contains(&from_id) {
3056 chat::add_to_chat_contacts_table(
3057 context,
3058 mime_parser.timestamp_sent,
3059 chat.id,
3060 &[from_id],
3061 )
3062 .await?;
3063 }
3064
3065 let fingerprint = key.public_key.dc_fingerprint().hex();
3072 if let Some(contact_id) =
3073 lookup_key_contact_by_fingerprint(context, &fingerprint).await?
3074 {
3075 added_id = Some(contact_id);
3076 better_msg =
3077 Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
3078 } else {
3079 warn!(context, "Added {added_addr:?} has no contact id.");
3080 }
3081 } else {
3082 warn!(context, "Added {added_addr:?} has no gossiped key.");
3083 }
3084 }
3085
3086 apply_chat_name_avatar_and_description_changes(
3087 context,
3088 mime_parser,
3089 from_id,
3090 is_from_in_chat,
3091 chat,
3092 &mut send_event_chat_modified,
3093 &mut better_msg,
3094 )
3095 .await?;
3096
3097 if is_from_in_chat {
3098 if from_is_key_contact != chat.grpid.is_empty()
3100 && chat.member_list_is_stale(context).await?
3101 {
3102 info!(context, "Member list is stale.");
3103 let mut new_members: BTreeSet<ContactId> =
3104 BTreeSet::from_iter(to_ids_flat.iter().copied());
3105 new_members.insert(ContactId::SELF);
3106 if !from_id.is_special() {
3107 new_members.insert(from_id);
3108 }
3109 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3110 new_members.remove(&ContactId::SELF);
3111 }
3112 context
3113 .sql
3114 .transaction(|transaction| {
3115 transaction.execute(
3117 "DELETE FROM chats_contacts
3118 WHERE chat_id=?",
3119 (chat.id,),
3120 )?;
3121
3122 let mut statement = transaction.prepare(
3124 "INSERT INTO chats_contacts (chat_id, contact_id)
3125 VALUES (?, ?)",
3126 )?;
3127 for contact_id in &new_members {
3128 statement.execute((chat.id, contact_id))?;
3129 }
3130
3131 Ok(())
3132 })
3133 .await?;
3134 send_event_chat_modified = true;
3135 } else if let Some(ref chat_group_member_timestamps) =
3136 mime_parser.chat_group_member_timestamps()
3137 {
3138 send_event_chat_modified |= update_chats_contacts_timestamps(
3139 context,
3140 chat.id,
3141 Some(from_id),
3142 to_ids,
3143 past_ids,
3144 chat_group_member_timestamps,
3145 )
3146 .await?;
3147 } else {
3148 let mut new_members: BTreeSet<ContactId>;
3149 let self_added =
3152 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3153 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
3154 && !chat_contacts.contains(&ContactId::SELF)
3155 } else {
3156 false
3157 };
3158 if self_added {
3159 new_members = BTreeSet::from_iter(to_ids_flat.iter().copied());
3160 new_members.insert(ContactId::SELF);
3161 if !from_id.is_special() && from_is_key_contact != chat.grpid.is_empty() {
3162 new_members.insert(from_id);
3163 }
3164 } else {
3165 new_members = chat_contacts.clone();
3166 }
3167
3168 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
3170 new_members.extend(to_ids_flat.iter());
3173 }
3174
3175 if let Some(added_id) = added_id {
3177 new_members.insert(added_id);
3178 }
3179
3180 if let Some(removed_id) = removed_id {
3182 new_members.remove(&removed_id);
3183 }
3184
3185 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3186 new_members.remove(&ContactId::SELF);
3187 }
3188
3189 if new_members != chat_contacts {
3190 chat::update_chat_contacts_table(
3191 context,
3192 mime_parser.timestamp_sent,
3193 chat.id,
3194 &new_members,
3195 )
3196 .await?;
3197 send_event_chat_modified = true;
3198 }
3199 }
3200
3201 chat.id
3202 .update_timestamp(
3203 context,
3204 Param::MemberListTimestamp,
3205 mime_parser.timestamp_sent,
3206 )
3207 .await?;
3208 }
3209
3210 let new_chat_contacts = BTreeSet::<ContactId>::from_iter(
3211 chat::get_chat_contacts(context, chat.id)
3212 .await?
3213 .iter()
3214 .copied(),
3215 );
3216
3217 let mut added_ids: BTreeSet<ContactId> = new_chat_contacts
3219 .difference(&chat_contacts)
3220 .copied()
3221 .collect();
3222 let mut removed_ids: BTreeSet<ContactId> = chat_contacts
3223 .difference(&new_chat_contacts)
3224 .copied()
3225 .collect();
3226 let id_was_already_added = if let Some(added_id) = added_id {
3227 !added_ids.remove(&added_id)
3228 } else {
3229 false
3230 };
3231 if let Some(removed_id) = removed_id {
3232 removed_ids.remove(&removed_id);
3233 }
3234
3235 let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
3236 && new_chat_contacts.contains(&ContactId::SELF)
3237 {
3238 Vec::new()
3239 } else {
3240 group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
3241 };
3242
3243 if id_was_already_added && group_changes_msgs.is_empty() && !is_chat_created {
3244 info!(context, "No-op 'Member added' message (TRASH)");
3245 better_msg = Some(String::new());
3246 }
3247
3248 if send_event_chat_modified {
3249 context.emit_event(EventType::ChatModified(chat.id));
3250 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3251 }
3252 Ok(GroupChangesInfo {
3253 better_msg,
3254 added_removed_id: if added_id.is_some() {
3255 added_id
3256 } else {
3257 removed_id
3258 },
3259 silent,
3260 extra_msgs: group_changes_msgs,
3261 })
3262}
3263
3264async fn apply_chat_name_avatar_and_description_changes(
3269 context: &Context,
3270 mime_parser: &MimeMessage,
3271 from_id: ContactId,
3272 is_from_in_chat: bool,
3273 chat: &mut Chat,
3274 send_event_chat_modified: &mut bool,
3275 better_msg: &mut Option<String>,
3276) -> Result<()> {
3277 let group_name_timestamp = mime_parser
3280 .get_header(HeaderDef::ChatGroupNameTimestamp)
3281 .and_then(|s| s.parse::<i64>().ok());
3282
3283 if let Some(old_name) = mime_parser
3284 .get_header(HeaderDef::ChatGroupNameChanged)
3285 .map(|s| s.trim())
3286 .or(match group_name_timestamp {
3287 Some(0) => None,
3288 Some(_) => Some(chat.name.as_str()),
3289 None => None,
3290 })
3291 && let Some(grpname) = mime_parser
3292 .get_header(HeaderDef::ChatGroupName)
3293 .map(|grpname| grpname.trim())
3294 .filter(|grpname| grpname.len() < 200)
3295 {
3296 let grpname = &sanitize_single_line(grpname);
3297
3298 let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3299 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3300 if is_from_in_chat
3302 && (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3303 && chat
3304 .id
3305 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3306 .await?
3307 && grpname != &chat.name
3308 {
3309 info!(context, "Updating grpname for chat {}.", chat.id);
3310 context
3311 .sql
3312 .execute(
3313 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3314 (grpname, normalize_text(grpname), chat.id),
3315 )
3316 .await?;
3317 *send_event_chat_modified = true;
3318 }
3319 if mime_parser
3320 .get_header(HeaderDef::ChatGroupNameChanged)
3321 .is_some()
3322 {
3323 if is_from_in_chat {
3324 let old_name = &sanitize_single_line(old_name);
3325 better_msg.get_or_insert(
3326 if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
3327 stock_str::msg_broadcast_name_changed(context, old_name, grpname)
3328 } else {
3329 stock_str::msg_grp_name(context, old_name, grpname, from_id).await
3330 },
3331 );
3332 } else {
3333 *better_msg = Some(String::new());
3335 }
3336 }
3337 }
3338
3339 if let Some(new_description) = mime_parser
3342 .get_header(HeaderDef::ChatGroupDescription)
3343 .map(|d| d.trim())
3344 {
3345 let new_description = sanitize_bidi_characters(new_description.trim());
3346 let old_description = chat::get_chat_description(context, chat.id).await?;
3347
3348 let old_timestamp = chat
3349 .param
3350 .get_i64(Param::GroupDescriptionTimestamp)
3351 .unwrap_or(0);
3352 let timestamp_in_header = mime_parser
3353 .get_header(HeaderDef::ChatGroupDescriptionTimestamp)
3354 .and_then(|s| s.parse::<i64>().ok());
3355
3356 let new_timestamp = timestamp_in_header.unwrap_or(mime_parser.timestamp_sent);
3357 if is_from_in_chat
3359 && (old_timestamp, &old_description) < (new_timestamp, &new_description)
3360 && chat
3361 .id
3362 .update_timestamp(context, Param::GroupDescriptionTimestamp, new_timestamp)
3363 .await?
3364 && new_description != old_description
3365 {
3366 info!(context, "Updating description for chat {}.", chat.id);
3367 context
3368 .sql
3369 .execute(
3370 "INSERT OR REPLACE INTO chats_descriptions(chat_id, description) VALUES(?, ?)",
3371 (chat.id, &new_description),
3372 )
3373 .await?;
3374 *send_event_chat_modified = true;
3375 }
3376 if mime_parser
3377 .get_header(HeaderDef::ChatGroupDescriptionChanged)
3378 .is_some()
3379 {
3380 if is_from_in_chat {
3381 better_msg
3382 .get_or_insert(stock_str::msg_chat_description_changed(context, from_id).await);
3383 } else {
3384 *better_msg = Some(String::new());
3386 }
3387 }
3388 }
3389
3390 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3393 && value == "group-avatar-changed"
3394 && let Some(avatar_action) = &mime_parser.group_avatar
3395 {
3396 if is_from_in_chat {
3397 better_msg.get_or_insert(
3400 if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
3401 stock_str::msg_broadcast_img_changed(context)
3402 } else {
3403 match avatar_action {
3404 AvatarAction::Delete => {
3405 stock_str::msg_grp_img_deleted(context, from_id).await
3406 }
3407 AvatarAction::Change(_) => {
3408 stock_str::msg_grp_img_changed(context, from_id).await
3409 }
3410 }
3411 },
3412 );
3413 } else {
3414 *better_msg = Some(String::new());
3416 }
3417 }
3418
3419 if let Some(avatar_action) = &mime_parser.group_avatar
3420 && is_from_in_chat
3421 && chat
3422 .param
3423 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3424 {
3425 info!(context, "Group-avatar change for {}.", chat.id);
3426 match avatar_action {
3427 AvatarAction::Change(profile_image) => {
3428 chat.param.set(Param::ProfileImage, profile_image);
3429 }
3430 AvatarAction::Delete => {
3431 chat.param.remove(Param::ProfileImage);
3432 }
3433 };
3434 chat.update_param(context).await?;
3435 *send_event_chat_modified = true;
3436 }
3437
3438 Ok(())
3439}
3440
3441#[expect(clippy::arithmetic_side_effects)]
3443async fn group_changes_msgs(
3444 context: &Context,
3445 added_ids: &BTreeSet<ContactId>,
3446 removed_ids: &BTreeSet<ContactId>,
3447 chat_id: ChatId,
3448) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3449 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3450 if !added_ids.is_empty() {
3451 warn!(
3452 context,
3453 "Implicit addition of {added_ids:?} to chat {chat_id}."
3454 );
3455 }
3456 if !removed_ids.is_empty() {
3457 warn!(
3458 context,
3459 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3460 );
3461 }
3462 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3463 for contact_id in added_ids {
3464 group_changes_msgs.push((
3465 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3466 SystemMessage::MemberAddedToGroup,
3467 Some(*contact_id),
3468 ));
3469 }
3470 for contact_id in removed_ids {
3471 group_changes_msgs.push((
3472 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3473 SystemMessage::MemberRemovedFromGroup,
3474 Some(*contact_id),
3475 ));
3476 }
3477
3478 Ok(group_changes_msgs)
3479}
3480
3481static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3482
3483fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3484 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3485 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3486 None => list_id_header
3487 .trim()
3488 .trim_start_matches('<')
3489 .trim_end_matches('>'),
3490 }
3491 .to_string())
3492}
3493
3494async fn create_or_lookup_mailinglist_or_broadcast(
3509 context: &Context,
3510 allow_creation: bool,
3511 create_blocked: Blocked,
3512 list_id_header: &str,
3513 from_id: ContactId,
3514 mime_parser: &MimeMessage,
3515) -> Result<Option<(ChatId, Blocked, bool)>> {
3516 let listid = mailinglist_header_listid(list_id_header)?;
3517
3518 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3519 return Ok(Some((chat_id, blocked, false)));
3520 }
3521
3522 let chattype = if mime_parser.was_encrypted() {
3523 Chattype::InBroadcast
3524 } else {
3525 Chattype::Mailinglist
3526 };
3527
3528 let name = if chattype == Chattype::InBroadcast {
3529 mime_parser
3530 .get_header(HeaderDef::ChatGroupName)
3531 .unwrap_or("Broadcast Channel")
3532 } else {
3533 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3534 };
3535
3536 if allow_creation {
3537 let param = mime_parser.list_post.as_ref().map(|list_post| {
3539 let mut p = Params::new();
3540 p.set(Param::ListPost, list_post);
3541 p.to_string()
3542 });
3543
3544 let chat_id = ChatId::create_multiuser_record(
3545 context,
3546 chattype,
3547 &listid,
3548 name,
3549 if chattype == Chattype::InBroadcast {
3550 Blocked::Not
3554 } else {
3555 create_blocked
3556 },
3557 param,
3558 mime_parser.timestamp_sent,
3559 )
3560 .await
3561 .with_context(|| format!("failed to create mailinglist '{name}' for grpid={listid}",))?;
3562
3563 if chattype == Chattype::InBroadcast {
3564 chat::add_to_chat_contacts_table(
3565 context,
3566 mime_parser.timestamp_sent,
3567 chat_id,
3568 &[from_id],
3569 )
3570 .await?;
3571 }
3572
3573 context.emit_event(EventType::ChatModified(chat_id));
3574 chatlist_events::emit_chatlist_changed(context);
3575 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3576
3577 Ok(Some((chat_id, create_blocked, true)))
3578 } else {
3579 info!(context, "Creating list forbidden by caller.");
3580 Ok(None)
3581 }
3582}
3583
3584fn compute_mailinglist_name(
3585 list_id_header: &str,
3586 listid: &str,
3587 mime_parser: &MimeMessage,
3588) -> String {
3589 let mut name = match LIST_ID_REGEX
3590 .captures(list_id_header)
3591 .and_then(|caps| caps.get(1))
3592 {
3593 Some(cap) => cap.as_str().trim().to_string(),
3594 None => "".to_string(),
3595 };
3596
3597 if listid.ends_with(".list-id.mcsv.net")
3601 && let Some(display_name) = &mime_parser.from.display_name
3602 {
3603 name.clone_from(display_name);
3604 }
3605
3606 let subject = mime_parser.get_subject().unwrap_or_default();
3610 static SUBJECT: LazyLock<Regex> =
3611 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3613 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3614 }
3615
3616 if name.is_empty()
3623 && (mime_parser.from.addr.contains("noreply")
3624 || mime_parser.from.addr.contains("no-reply")
3625 || mime_parser.from.addr.starts_with("notifications@")
3626 || mime_parser.from.addr.starts_with("newsletter@")
3627 || listid.ends_with(".xt.local"))
3628 && let Some(display_name) = &mime_parser.from.display_name
3629 {
3630 name.clone_from(display_name);
3631 }
3632
3633 if name.is_empty() {
3636 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3638 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3639 if let Some(cap) = PREFIX_32_CHARS_HEX
3640 .captures(listid)
3641 .and_then(|caps| caps.get(2))
3642 {
3643 name = cap.as_str().to_string();
3644 } else {
3645 name = listid.to_string();
3646 }
3647 }
3648
3649 sanitize_single_line(&name)
3650}
3651
3652async fn apply_mailinglist_changes(
3656 context: &Context,
3657 mime_parser: &MimeMessage,
3658 chat_id: ChatId,
3659) -> Result<()> {
3660 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3661 return Ok(());
3662 };
3663
3664 let mut chat = Chat::load_from_db(context, chat_id).await?;
3665 if chat.typ != Chattype::Mailinglist {
3666 return Ok(());
3667 }
3668 let listid = &chat.grpid;
3669
3670 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3671 if chat.name != new_name
3672 && chat_id
3673 .update_timestamp(
3674 context,
3675 Param::GroupNameTimestamp,
3676 mime_parser.timestamp_sent,
3677 )
3678 .await?
3679 {
3680 info!(context, "Updating listname for chat {chat_id}.");
3681 context
3682 .sql
3683 .execute(
3684 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3685 (&new_name, normalize_text(&new_name), chat_id),
3686 )
3687 .await?;
3688 context.emit_event(EventType::ChatModified(chat_id));
3689 }
3690
3691 let Some(list_post) = &mime_parser.list_post else {
3692 return Ok(());
3693 };
3694
3695 let list_post = match ContactAddress::new(list_post) {
3696 Ok(list_post) => list_post,
3697 Err(err) => {
3698 warn!(context, "Invalid List-Post: {:#}.", err);
3699 return Ok(());
3700 }
3701 };
3702 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3703 let mut contact = Contact::get_by_id(context, contact_id).await?;
3704 if contact.param.get(Param::ListId) != Some(listid) {
3705 contact.param.set(Param::ListId, listid);
3706 contact.update_param(context).await?;
3707 }
3708
3709 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3710 if list_post.as_ref() != old_list_post {
3711 chat.param.remove(Param::ListPost);
3714 chat.update_param(context).await?;
3715 }
3716 } else {
3717 chat.param.set(Param::ListPost, list_post);
3718 chat.update_param(context).await?;
3719 }
3720
3721 Ok(())
3722}
3723
3724async fn apply_out_broadcast_changes(
3725 context: &Context,
3726 mime_parser: &MimeMessage,
3727 chat: &mut Chat,
3728 from_id: ContactId,
3729) -> Result<GroupChangesInfo> {
3730 ensure!(chat.typ == Chattype::OutBroadcast);
3731
3732 let mut send_event_chat_modified = false;
3733 let mut better_msg = None;
3734 let mut added_removed_id: Option<ContactId> = None;
3735
3736 if from_id == ContactId::SELF {
3737 let is_from_in_chat = true;
3738 apply_chat_name_avatar_and_description_changes(
3739 context,
3740 mime_parser,
3741 from_id,
3742 is_from_in_chat,
3743 chat,
3744 &mut send_event_chat_modified,
3745 &mut better_msg,
3746 )
3747 .await?;
3748 }
3749
3750 if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3751 if from_id == ContactId::SELF {
3752 let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3753 if let Some(added_id) = added_id {
3754 if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3755 info!(context, "No-op broadcast addition (TRASH)");
3756 better_msg.get_or_insert("".to_string());
3757 } else {
3758 chat::add_to_chat_contacts_table(
3759 context,
3760 mime_parser.timestamp_sent,
3761 chat.id,
3762 &[added_id],
3763 )
3764 .await?;
3765 let msg =
3766 stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3767 .await;
3768 better_msg.get_or_insert(msg);
3769 added_removed_id = Some(added_id);
3770 send_event_chat_modified = true;
3771 }
3772 } else {
3773 warn!(context, "Failed to find contact with fpr {added_fpr}");
3774 }
3775 }
3776 } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3777 send_event_chat_modified = true;
3778 let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3779 if removed_id == Some(from_id) {
3780 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3783 info!(context, "Broadcast leave message (TRASH)");
3784 better_msg = Some("".to_string());
3785 } else if from_id == ContactId::SELF
3786 && let Some(removed_id) = removed_id
3787 {
3788 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3789 .await?;
3790
3791 better_msg.get_or_insert(
3792 stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3793 );
3794 added_removed_id = Some(removed_id);
3795 }
3796 }
3797
3798 if send_event_chat_modified {
3799 context.emit_event(EventType::ChatModified(chat.id));
3800 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3801 }
3802 Ok(GroupChangesInfo {
3803 better_msg,
3804 added_removed_id,
3805 silent: false,
3806 extra_msgs: vec![],
3807 })
3808}
3809
3810async fn apply_in_broadcast_changes(
3811 context: &Context,
3812 mime_parser: &MimeMessage,
3813 chat: &mut Chat,
3814 from_id: ContactId,
3815) -> Result<GroupChangesInfo> {
3816 ensure!(chat.typ == Chattype::InBroadcast);
3817
3818 if let Some(part) = mime_parser.parts.first()
3819 && let Some(error) = &part.error
3820 {
3821 warn!(
3822 context,
3823 "Not applying broadcast changes from message with error: {error}"
3824 );
3825 return Ok(GroupChangesInfo::default());
3826 }
3827
3828 let mut send_event_chat_modified = false;
3829 let mut better_msg = None;
3830
3831 let is_from_in_chat = is_contact_in_chat(context, chat.id, from_id).await?;
3832 apply_chat_name_avatar_and_description_changes(
3833 context,
3834 mime_parser,
3835 from_id,
3836 is_from_in_chat,
3837 chat,
3838 &mut send_event_chat_modified,
3839 &mut better_msg,
3840 )
3841 .await?;
3842
3843 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3844 && context.is_self_addr(added_addr).await?
3845 {
3846 let msg = if chat.is_self_in_chat(context).await? {
3847 info!(context, "No-op broadcast 'Member added' message (TRASH)");
3851 "".to_string()
3852 } else {
3853 stock_str::msg_you_joined_broadcast(context)
3854 };
3855
3856 better_msg.get_or_insert(msg);
3857 send_event_chat_modified = true;
3858 }
3859
3860 if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3861 if removed_fpr != self_fingerprint(context).await? {
3863 logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3864 return Ok(GroupChangesInfo::default());
3865 }
3866 chat::delete_broadcast_secret(context, chat.id).await?;
3867
3868 if from_id == ContactId::SELF {
3869 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
3870 } else {
3871 better_msg.get_or_insert(
3872 stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3873 );
3874 }
3875
3876 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3877 .await?;
3878 send_event_chat_modified = true;
3879 } else if !chat.is_self_in_chat(context).await? {
3880 chat::add_to_chat_contacts_table(
3881 context,
3882 mime_parser.timestamp_sent,
3883 chat.id,
3884 &[ContactId::SELF],
3885 )
3886 .await?;
3887 send_event_chat_modified = true;
3888 }
3889
3890 if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3891 if validate_broadcast_secret(secret) {
3892 save_broadcast_secret(context, chat.id, secret).await?;
3893 } else {
3894 warn!(context, "Not saving invalid broadcast secret");
3895 }
3896 }
3897
3898 if send_event_chat_modified {
3899 context.emit_event(EventType::ChatModified(chat.id));
3900 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3901 }
3902 Ok(GroupChangesInfo {
3903 better_msg,
3904 added_removed_id: None,
3905 silent: false,
3906 extra_msgs: vec![],
3907 })
3908}
3909
3910async fn create_adhoc_group(
3912 context: &Context,
3913 mime_parser: &MimeMessage,
3914 create_blocked: Blocked,
3915 from_id: ContactId,
3916 to_ids: &[ContactId],
3917 grpname: &str,
3918) -> Result<Option<(ChatId, Blocked)>> {
3919 let mut member_ids: Vec<ContactId> = to_ids
3920 .iter()
3921 .copied()
3922 .filter(|&id| id != ContactId::SELF)
3923 .collect();
3924 if from_id != ContactId::SELF && !member_ids.contains(&from_id) {
3925 member_ids.push(from_id);
3926 }
3927 if !mime_parser.was_encrypted() {
3928 member_ids.push(ContactId::SELF);
3929 }
3930
3931 if mime_parser.is_mailinglist_message() {
3932 return Ok(None);
3933 }
3934 if mime_parser
3935 .get_header(HeaderDef::ChatGroupMemberRemoved)
3936 .is_some()
3937 {
3938 info!(
3939 context,
3940 "Message removes member from unknown ad-hoc group (TRASH)."
3941 );
3942 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3943 }
3944
3945 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3946 context,
3947 Chattype::Group,
3948 "", grpname,
3950 create_blocked,
3951 None,
3952 mime_parser.timestamp_sent,
3953 )
3954 .await?;
3955
3956 info!(
3957 context,
3958 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3959 );
3960 chat::add_to_chat_contacts_table(
3961 context,
3962 mime_parser.timestamp_sent,
3963 new_chat_id,
3964 &member_ids,
3965 )
3966 .await?;
3967
3968 context.emit_event(EventType::ChatModified(new_chat_id));
3969 chatlist_events::emit_chatlist_changed(context);
3970 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3971
3972 Ok(Some((new_chat_id, create_blocked)))
3973}
3974
3975#[derive(Debug, PartialEq, Eq)]
3976enum VerifiedEncryption {
3977 Verified,
3978 NotVerified(String), }
3980
3981async fn has_verified_encryption(
3985 context: &Context,
3986 mimeparser: &MimeMessage,
3987 from_id: ContactId,
3988) -> Result<VerifiedEncryption> {
3989 use VerifiedEncryption::*;
3990
3991 if !mimeparser.was_encrypted() {
3992 return Ok(NotVerified("This message is not encrypted".to_string()));
3993 };
3994
3995 if from_id == ContactId::SELF {
3996 return Ok(Verified);
3997 }
3998
3999 let from_contact = Contact::get_by_id(context, from_id).await?;
4000
4001 let Some(fingerprint) = from_contact.fingerprint() else {
4002 return Ok(NotVerified(
4003 "The message was sent without encryption".to_string(),
4004 ));
4005 };
4006
4007 if from_contact.get_verifier_id(context).await?.is_none() {
4008 return Ok(NotVerified(
4009 "The message was sent by non-verified contact".to_string(),
4010 ));
4011 }
4012
4013 let signed_with_verified_key = mimeparser
4014 .signature
4015 .as_ref()
4016 .is_some_and(|(signature, _)| *signature == fingerprint);
4017 if signed_with_verified_key {
4018 Ok(Verified)
4019 } else {
4020 Ok(NotVerified(
4021 "The message was sent with non-verified encryption".to_string(),
4022 ))
4023 }
4024}
4025
4026async fn mark_recipients_as_verified(
4027 context: &Context,
4028 from_id: ContactId,
4029 mimeparser: &MimeMessage,
4030) -> Result<()> {
4031 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
4032
4033 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
4037
4038 for gossiped_key in mimeparser
4039 .gossiped_keys
4040 .values()
4041 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
4042 {
4043 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
4044 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
4045 continue;
4046 };
4047
4048 if to_id == ContactId::SELF || to_id == from_id {
4049 continue;
4050 }
4051
4052 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
4053 }
4054
4055 Ok(())
4056}
4057
4058async fn get_previous_message(
4062 context: &Context,
4063 mime_parser: &MimeMessage,
4064) -> Result<Option<Message>> {
4065 if let Some(field) = mime_parser.get_header(HeaderDef::References)
4066 && let Some(rfc724mid) = parse_message_ids(field).last()
4067 && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
4068 {
4069 return Message::load_from_db_optional(context, msg_id).await;
4070 }
4071 Ok(None)
4072}
4073
4074async fn get_parent_message(
4079 context: &Context,
4080 references: Option<&str>,
4081 in_reply_to: Option<&str>,
4082) -> Result<Option<Message>> {
4083 let mut mids = Vec::new();
4084 if let Some(field) = in_reply_to {
4085 mids = parse_message_ids(field);
4086 }
4087 if let Some(field) = references {
4088 mids.append(&mut parse_message_ids(field));
4089 }
4090 message::get_by_rfc724_mids(context, &mids).await
4091}
4092
4093pub(crate) async fn get_prefetch_parent_message(
4094 context: &Context,
4095 headers: &[mailparse::MailHeader<'_>],
4096) -> Result<Option<Message>> {
4097 get_parent_message(
4098 context,
4099 headers.get_header_value(HeaderDef::References).as_deref(),
4100 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
4101 )
4102 .await
4103}
4104
4105async fn add_or_lookup_contacts_by_address_list(
4107 context: &Context,
4108 address_list: &[SingleInfo],
4109 origin: Origin,
4110) -> Result<Vec<Option<ContactId>>> {
4111 let mut contact_ids = Vec::new();
4112 for info in address_list {
4113 let addr = &info.addr;
4114 if !may_be_valid_addr(addr) {
4115 contact_ids.push(None);
4116 continue;
4117 }
4118 let display_name = info.display_name.as_deref();
4119 if let Ok(addr) = ContactAddress::new(addr) {
4120 let (contact_id, _) =
4121 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
4122 .await?;
4123 contact_ids.push(Some(contact_id));
4124 } else {
4125 warn!(context, "Contact with address {:?} cannot exist.", addr);
4126 contact_ids.push(None);
4127 }
4128 }
4129
4130 Ok(contact_ids)
4131}
4132
4133async fn add_or_lookup_key_contacts(
4135 context: &Context,
4136 address_list: &[SingleInfo],
4137 gossiped_keys: &BTreeMap<String, GossipedKey>,
4138 fingerprints: &[Fingerprint],
4139 origin: Origin,
4140) -> Result<Vec<Option<ContactId>>> {
4141 let mut contact_ids = Vec::new();
4142 let mut fingerprint_iter = fingerprints.iter();
4143 for info in address_list {
4144 let fp = fingerprint_iter.next();
4145 let addr = &info.addr;
4146 if !may_be_valid_addr(addr) {
4147 contact_ids.push(None);
4148 continue;
4149 }
4150 let fingerprint: String = if let Some(fp) = fp {
4151 fp.hex()
4153 } else if let Some(key) = gossiped_keys.get(addr) {
4154 key.public_key.dc_fingerprint().hex()
4155 } else if context.is_self_addr(addr).await? {
4156 contact_ids.push(Some(ContactId::SELF));
4157 continue;
4158 } else {
4159 contact_ids.push(None);
4160 continue;
4161 };
4162 let display_name = info.display_name.as_deref();
4163 if let Ok(addr) = ContactAddress::new(addr) {
4164 let (contact_id, _) = Contact::add_or_lookup_ex(
4165 context,
4166 display_name.unwrap_or_default(),
4167 &addr,
4168 &fingerprint,
4169 origin,
4170 )
4171 .await?;
4172 contact_ids.push(Some(contact_id));
4173 } else {
4174 warn!(context, "Contact with address {:?} cannot exist.", addr);
4175 contact_ids.push(None);
4176 }
4177 }
4178
4179 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
4180 Ok(contact_ids)
4181}
4182
4183async fn lookup_key_contact_by_address(
4188 context: &Context,
4189 addr: &str,
4190 chat_id: Option<ChatId>,
4191) -> Result<Option<ContactId>> {
4192 if context.is_self_addr(addr).await? {
4193 if chat_id.is_none() {
4194 return Ok(Some(ContactId::SELF));
4195 }
4196 let is_self_in_chat = context
4197 .sql
4198 .exists(
4199 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
4200 (chat_id,),
4201 )
4202 .await?;
4203 if is_self_in_chat {
4204 return Ok(Some(ContactId::SELF));
4205 }
4206 }
4207 let contact_id: Option<ContactId> = match chat_id {
4208 Some(chat_id) => {
4209 context
4210 .sql
4211 .query_row_optional(
4212 "SELECT id FROM contacts
4213 WHERE contacts.addr=?
4214 AND EXISTS (SELECT 1 FROM chats_contacts
4215 WHERE contact_id=contacts.id
4216 AND chat_id=?)
4217 AND fingerprint<>'' -- Should always be true
4218 ",
4219 (addr, chat_id),
4220 |row| {
4221 let contact_id: ContactId = row.get(0)?;
4222 Ok(contact_id)
4223 },
4224 )
4225 .await?
4226 }
4227 None => {
4228 context
4229 .sql
4230 .query_row_optional(
4231 "SELECT id FROM contacts
4232 WHERE addr=?
4233 AND fingerprint<>''
4234 ORDER BY
4235 (
4236 SELECT COUNT(*) FROM chats c
4237 INNER JOIN chats_contacts cc
4238 ON c.id=cc.chat_id
4239 WHERE c.type=?
4240 AND c.id>?
4241 AND c.blocked=?
4242 AND cc.contact_id=contacts.id
4243 ) DESC,
4244 last_seen DESC, id DESC
4245 ",
4246 (
4247 addr,
4248 Chattype::Single,
4249 constants::DC_CHAT_ID_LAST_SPECIAL,
4250 Blocked::Not,
4251 ),
4252 |row| {
4253 let contact_id: ContactId = row.get(0)?;
4254 Ok(contact_id)
4255 },
4256 )
4257 .await?
4258 }
4259 };
4260 Ok(contact_id)
4261}
4262
4263async fn lookup_key_contact_by_fingerprint(
4264 context: &Context,
4265 fingerprint: &str,
4266) -> Result<Option<ContactId>> {
4267 logged_debug_assert!(
4268 context,
4269 !fingerprint.is_empty(),
4270 "lookup_key_contact_by_fingerprint: fingerprint is empty."
4271 );
4272 if fingerprint.is_empty() {
4273 return Ok(None);
4275 }
4276 if let Some(contact_id) = context
4277 .sql
4278 .query_row_optional(
4279 "SELECT id FROM contacts
4280 WHERE fingerprint=? AND fingerprint!=''",
4281 (fingerprint,),
4282 |row| {
4283 let contact_id: ContactId = row.get(0)?;
4284 Ok(contact_id)
4285 },
4286 )
4287 .await?
4288 {
4289 Ok(Some(contact_id))
4290 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4291 if self_fp == fingerprint {
4292 Ok(Some(ContactId::SELF))
4293 } else {
4294 Ok(None)
4295 }
4296 } else {
4297 Ok(None)
4298 }
4299}
4300
4301async fn lookup_key_contacts_fallback_to_chat(
4317 context: &Context,
4318 address_list: &[SingleInfo],
4319 fingerprints: &[Fingerprint],
4320 chat_id: Option<ChatId>,
4321) -> Result<Vec<Option<ContactId>>> {
4322 let mut contact_ids = Vec::new();
4323 let mut fingerprint_iter = fingerprints.iter();
4324 for info in address_list {
4325 let fp = fingerprint_iter.next();
4326 let addr = &info.addr;
4327 if !may_be_valid_addr(addr) {
4328 contact_ids.push(None);
4329 continue;
4330 }
4331
4332 if let Some(fp) = fp {
4333 let display_name = info.display_name.as_deref();
4335 let fingerprint: String = fp.hex();
4336
4337 if let Ok(addr) = ContactAddress::new(addr) {
4338 let (contact_id, _) = Contact::add_or_lookup_ex(
4339 context,
4340 display_name.unwrap_or_default(),
4341 &addr,
4342 &fingerprint,
4343 Origin::Hidden,
4344 )
4345 .await?;
4346 contact_ids.push(Some(contact_id));
4347 } else {
4348 warn!(context, "Contact with address {:?} cannot exist.", addr);
4349 contact_ids.push(None);
4350 }
4351 } else {
4352 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4353 contact_ids.push(contact_id);
4354 }
4355 }
4356 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4357 Ok(contact_ids)
4358}
4359
4360fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4363 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4364 || mime_parser.get_header(HeaderDef::Sender).is_some()
4365}
4366
4367#[cfg(test)]
4368mod receive_imf_tests;