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