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