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