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