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
3336 .get_or_insert(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
3337 }
3338 }
3339
3340 if let Some(new_description) = mime_parser
3343 .get_header(HeaderDef::ChatGroupDescription)
3344 .map(|d| d.trim())
3345 {
3346 let new_description = sanitize_bidi_characters(new_description.trim());
3347 let old_description = chat::get_chat_description(context, chat.id).await?;
3348
3349 let old_timestamp = chat
3350 .param
3351 .get_i64(Param::GroupDescriptionTimestamp)
3352 .unwrap_or(0);
3353 let timestamp_in_header = mime_parser
3354 .get_header(HeaderDef::ChatGroupDescriptionTimestamp)
3355 .and_then(|s| s.parse::<i64>().ok());
3356
3357 let new_timestamp = timestamp_in_header.unwrap_or(mime_parser.timestamp_sent);
3358 if (old_timestamp, &old_description) < (new_timestamp, &new_description)
3360 && chat
3361 .id
3362 .update_timestamp(context, Param::GroupDescriptionTimestamp, new_timestamp)
3363 .await?
3364 && new_description != old_description
3365 {
3366 info!(context, "Updating description for chat {}.", chat.id);
3367 context
3368 .sql
3369 .execute(
3370 "INSERT OR REPLACE INTO chats_descriptions(chat_id, description) VALUES(?, ?)",
3371 (chat.id, &new_description),
3372 )
3373 .await?;
3374 *send_event_chat_modified = true;
3375 }
3376 if mime_parser
3377 .get_header(HeaderDef::ChatGroupDescriptionChanged)
3378 .is_some()
3379 {
3380 better_msg
3381 .get_or_insert(stock_str::msg_chat_description_changed(context, from_id).await);
3382 }
3383 }
3384
3385 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3388 && value == "group-avatar-changed"
3389 && let Some(avatar_action) = &mime_parser.group_avatar
3390 {
3391 better_msg.get_or_insert(match avatar_action {
3394 AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
3395 AvatarAction::Change(_) => stock_str::msg_grp_img_changed(context, from_id).await,
3396 });
3397 }
3398
3399 if let Some(avatar_action) = &mime_parser.group_avatar {
3400 info!(context, "Group-avatar change for {}.", chat.id);
3401 if chat
3402 .param
3403 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3404 {
3405 match avatar_action {
3406 AvatarAction::Change(profile_image) => {
3407 chat.param.set(Param::ProfileImage, profile_image);
3408 }
3409 AvatarAction::Delete => {
3410 chat.param.remove(Param::ProfileImage);
3411 }
3412 };
3413 chat.update_param(context).await?;
3414 *send_event_chat_modified = true;
3415 }
3416 }
3417
3418 Ok(())
3419}
3420
3421#[expect(clippy::arithmetic_side_effects)]
3423async fn group_changes_msgs(
3424 context: &Context,
3425 added_ids: &HashSet<ContactId>,
3426 removed_ids: &HashSet<ContactId>,
3427 chat_id: ChatId,
3428) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3429 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3430 if !added_ids.is_empty() {
3431 warn!(
3432 context,
3433 "Implicit addition of {added_ids:?} to chat {chat_id}."
3434 );
3435 }
3436 if !removed_ids.is_empty() {
3437 warn!(
3438 context,
3439 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3440 );
3441 }
3442 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3443 for contact_id in added_ids {
3444 group_changes_msgs.push((
3445 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3446 SystemMessage::MemberAddedToGroup,
3447 Some(*contact_id),
3448 ));
3449 }
3450 for contact_id in removed_ids {
3451 group_changes_msgs.push((
3452 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3453 SystemMessage::MemberRemovedFromGroup,
3454 Some(*contact_id),
3455 ));
3456 }
3457
3458 Ok(group_changes_msgs)
3459}
3460
3461static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3462
3463fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3464 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3465 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3466 None => list_id_header
3467 .trim()
3468 .trim_start_matches('<')
3469 .trim_end_matches('>'),
3470 }
3471 .to_string())
3472}
3473
3474async fn create_or_lookup_mailinglist_or_broadcast(
3489 context: &Context,
3490 allow_creation: bool,
3491 create_blocked: Blocked,
3492 list_id_header: &str,
3493 from_id: ContactId,
3494 mime_parser: &MimeMessage,
3495) -> Result<Option<(ChatId, Blocked, bool)>> {
3496 let listid = mailinglist_header_listid(list_id_header)?;
3497
3498 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3499 return Ok(Some((chat_id, blocked, false)));
3500 }
3501
3502 let chattype = if mime_parser.was_encrypted() {
3503 Chattype::InBroadcast
3504 } else {
3505 Chattype::Mailinglist
3506 };
3507
3508 let name = if chattype == Chattype::InBroadcast {
3509 mime_parser
3510 .get_header(HeaderDef::ChatGroupName)
3511 .unwrap_or("Broadcast Channel")
3512 } else {
3513 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3514 };
3515
3516 if allow_creation {
3517 let param = mime_parser.list_post.as_ref().map(|list_post| {
3519 let mut p = Params::new();
3520 p.set(Param::ListPost, list_post);
3521 p.to_string()
3522 });
3523
3524 let chat_id = ChatId::create_multiuser_record(
3525 context,
3526 chattype,
3527 &listid,
3528 name,
3529 create_blocked,
3530 param,
3531 mime_parser.timestamp_sent,
3532 )
3533 .await
3534 .with_context(|| {
3535 format!(
3536 "failed to create mailinglist '{}' for grpid={}",
3537 &name, &listid
3538 )
3539 })?;
3540
3541 if chattype == Chattype::InBroadcast {
3542 chat::add_to_chat_contacts_table(
3543 context,
3544 mime_parser.timestamp_sent,
3545 chat_id,
3546 &[from_id],
3547 )
3548 .await?;
3549 }
3550
3551 context.emit_event(EventType::ChatModified(chat_id));
3552 chatlist_events::emit_chatlist_changed(context);
3553 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3554
3555 Ok(Some((chat_id, create_blocked, true)))
3556 } else {
3557 info!(context, "Creating list forbidden by caller.");
3558 Ok(None)
3559 }
3560}
3561
3562fn compute_mailinglist_name(
3563 list_id_header: &str,
3564 listid: &str,
3565 mime_parser: &MimeMessage,
3566) -> String {
3567 let mut name = match LIST_ID_REGEX
3568 .captures(list_id_header)
3569 .and_then(|caps| caps.get(1))
3570 {
3571 Some(cap) => cap.as_str().trim().to_string(),
3572 None => "".to_string(),
3573 };
3574
3575 if listid.ends_with(".list-id.mcsv.net")
3579 && let Some(display_name) = &mime_parser.from.display_name
3580 {
3581 name.clone_from(display_name);
3582 }
3583
3584 let subject = mime_parser.get_subject().unwrap_or_default();
3588 static SUBJECT: LazyLock<Regex> =
3589 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3591 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3592 }
3593
3594 if name.is_empty()
3601 && (mime_parser.from.addr.contains("noreply")
3602 || mime_parser.from.addr.contains("no-reply")
3603 || mime_parser.from.addr.starts_with("notifications@")
3604 || mime_parser.from.addr.starts_with("newsletter@")
3605 || listid.ends_with(".xt.local"))
3606 && let Some(display_name) = &mime_parser.from.display_name
3607 {
3608 name.clone_from(display_name);
3609 }
3610
3611 if name.is_empty() {
3614 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3616 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3617 if let Some(cap) = PREFIX_32_CHARS_HEX
3618 .captures(listid)
3619 .and_then(|caps| caps.get(2))
3620 {
3621 name = cap.as_str().to_string();
3622 } else {
3623 name = listid.to_string();
3624 }
3625 }
3626
3627 sanitize_single_line(&name)
3628}
3629
3630async fn apply_mailinglist_changes(
3634 context: &Context,
3635 mime_parser: &MimeMessage,
3636 chat_id: ChatId,
3637) -> Result<()> {
3638 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3639 return Ok(());
3640 };
3641
3642 let mut chat = Chat::load_from_db(context, chat_id).await?;
3643 if chat.typ != Chattype::Mailinglist {
3644 return Ok(());
3645 }
3646 let listid = &chat.grpid;
3647
3648 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3649 if chat.name != new_name
3650 && chat_id
3651 .update_timestamp(
3652 context,
3653 Param::GroupNameTimestamp,
3654 mime_parser.timestamp_sent,
3655 )
3656 .await?
3657 {
3658 info!(context, "Updating listname for chat {chat_id}.");
3659 context
3660 .sql
3661 .execute(
3662 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3663 (&new_name, normalize_text(&new_name), chat_id),
3664 )
3665 .await?;
3666 context.emit_event(EventType::ChatModified(chat_id));
3667 }
3668
3669 let Some(list_post) = &mime_parser.list_post else {
3670 return Ok(());
3671 };
3672
3673 let list_post = match ContactAddress::new(list_post) {
3674 Ok(list_post) => list_post,
3675 Err(err) => {
3676 warn!(context, "Invalid List-Post: {:#}.", err);
3677 return Ok(());
3678 }
3679 };
3680 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3681 let mut contact = Contact::get_by_id(context, contact_id).await?;
3682 if contact.param.get(Param::ListId) != Some(listid) {
3683 contact.param.set(Param::ListId, listid);
3684 contact.update_param(context).await?;
3685 }
3686
3687 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3688 if list_post.as_ref() != old_list_post {
3689 chat.param.remove(Param::ListPost);
3692 chat.update_param(context).await?;
3693 }
3694 } else {
3695 chat.param.set(Param::ListPost, list_post);
3696 chat.update_param(context).await?;
3697 }
3698
3699 Ok(())
3700}
3701
3702async fn apply_out_broadcast_changes(
3703 context: &Context,
3704 mime_parser: &MimeMessage,
3705 chat: &mut Chat,
3706 from_id: ContactId,
3707) -> Result<GroupChangesInfo> {
3708 ensure!(chat.typ == Chattype::OutBroadcast);
3709
3710 let mut send_event_chat_modified = false;
3711 let mut better_msg = None;
3712 let mut added_removed_id: Option<ContactId> = None;
3713
3714 if from_id == ContactId::SELF {
3715 apply_chat_name_avatar_and_description_changes(
3716 context,
3717 mime_parser,
3718 from_id,
3719 chat,
3720 &mut send_event_chat_modified,
3721 &mut better_msg,
3722 )
3723 .await?;
3724 }
3725
3726 if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3727 if from_id == ContactId::SELF {
3728 let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3729 if let Some(added_id) = added_id {
3730 if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3731 info!(context, "No-op broadcast addition (TRASH)");
3732 better_msg.get_or_insert("".to_string());
3733 } else {
3734 chat::add_to_chat_contacts_table(
3735 context,
3736 mime_parser.timestamp_sent,
3737 chat.id,
3738 &[added_id],
3739 )
3740 .await?;
3741 let msg =
3742 stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3743 .await;
3744 better_msg.get_or_insert(msg);
3745 added_removed_id = Some(added_id);
3746 send_event_chat_modified = true;
3747 }
3748 } else {
3749 warn!(context, "Failed to find contact with fpr {added_fpr}");
3750 }
3751 }
3752 } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3753 send_event_chat_modified = true;
3754 let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3755 if removed_id == Some(from_id) {
3756 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3759 info!(context, "Broadcast leave message (TRASH)");
3760 better_msg = Some("".to_string());
3761 } else if from_id == ContactId::SELF
3762 && let Some(removed_id) = removed_id
3763 {
3764 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3765 .await?;
3766
3767 better_msg.get_or_insert(
3768 stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3769 );
3770 added_removed_id = Some(removed_id);
3771 }
3772 }
3773
3774 if send_event_chat_modified {
3775 context.emit_event(EventType::ChatModified(chat.id));
3776 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3777 }
3778 Ok(GroupChangesInfo {
3779 better_msg,
3780 added_removed_id,
3781 silent: false,
3782 extra_msgs: vec![],
3783 })
3784}
3785
3786async fn apply_in_broadcast_changes(
3787 context: &Context,
3788 mime_parser: &MimeMessage,
3789 chat: &mut Chat,
3790 from_id: ContactId,
3791) -> Result<GroupChangesInfo> {
3792 ensure!(chat.typ == Chattype::InBroadcast);
3793
3794 if let Some(part) = mime_parser.parts.first()
3795 && let Some(error) = &part.error
3796 {
3797 warn!(
3798 context,
3799 "Not applying broadcast changes from message with error: {error}"
3800 );
3801 return Ok(GroupChangesInfo::default());
3802 }
3803
3804 let mut send_event_chat_modified = false;
3805 let mut better_msg = None;
3806
3807 apply_chat_name_avatar_and_description_changes(
3808 context,
3809 mime_parser,
3810 from_id,
3811 chat,
3812 &mut send_event_chat_modified,
3813 &mut better_msg,
3814 )
3815 .await?;
3816
3817 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3818 && context.is_self_addr(added_addr).await?
3819 {
3820 let msg = if chat.is_self_in_chat(context).await? {
3821 info!(context, "No-op broadcast 'Member added' message (TRASH)");
3825 "".to_string()
3826 } else {
3827 stock_str::msg_you_joined_broadcast(context).await
3828 };
3829
3830 better_msg.get_or_insert(msg);
3831 send_event_chat_modified = true;
3832 }
3833
3834 if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3835 if removed_fpr != self_fingerprint(context).await? {
3837 logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3838 return Ok(GroupChangesInfo::default());
3839 }
3840 chat::delete_broadcast_secret(context, chat.id).await?;
3841
3842 if from_id == ContactId::SELF {
3843 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
3844 } else {
3845 better_msg.get_or_insert(
3846 stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3847 );
3848 }
3849
3850 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3851 .await?;
3852 send_event_chat_modified = true;
3853 } else if !chat.is_self_in_chat(context).await? {
3854 chat::add_to_chat_contacts_table(
3855 context,
3856 mime_parser.timestamp_sent,
3857 chat.id,
3858 &[ContactId::SELF],
3859 )
3860 .await?;
3861 send_event_chat_modified = true;
3862 }
3863
3864 if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3865 if validate_broadcast_secret(secret) {
3866 save_broadcast_secret(context, chat.id, secret).await?;
3867 } else {
3868 warn!(context, "Not saving invalid broadcast secret");
3869 }
3870 }
3871
3872 if send_event_chat_modified {
3873 context.emit_event(EventType::ChatModified(chat.id));
3874 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3875 }
3876 Ok(GroupChangesInfo {
3877 better_msg,
3878 added_removed_id: None,
3879 silent: false,
3880 extra_msgs: vec![],
3881 })
3882}
3883
3884async fn create_adhoc_group(
3886 context: &Context,
3887 mime_parser: &MimeMessage,
3888 create_blocked: Blocked,
3889 from_id: ContactId,
3890 to_ids: &[ContactId],
3891 grpname: &str,
3892) -> Result<Option<(ChatId, Blocked)>> {
3893 let mut member_ids: Vec<ContactId> = to_ids
3894 .iter()
3895 .copied()
3896 .filter(|&id| id != ContactId::SELF)
3897 .collect();
3898 if from_id != ContactId::SELF && !member_ids.contains(&from_id) {
3899 member_ids.push(from_id);
3900 }
3901 if !mime_parser.was_encrypted() {
3902 member_ids.push(ContactId::SELF);
3903 }
3904
3905 if mime_parser.is_mailinglist_message() {
3906 return Ok(None);
3907 }
3908 if mime_parser
3909 .get_header(HeaderDef::ChatGroupMemberRemoved)
3910 .is_some()
3911 {
3912 info!(
3913 context,
3914 "Message removes member from unknown ad-hoc group (TRASH)."
3915 );
3916 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3917 }
3918
3919 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3920 context,
3921 Chattype::Group,
3922 "", grpname,
3924 create_blocked,
3925 None,
3926 mime_parser.timestamp_sent,
3927 )
3928 .await?;
3929
3930 info!(
3931 context,
3932 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3933 );
3934 chat::add_to_chat_contacts_table(
3935 context,
3936 mime_parser.timestamp_sent,
3937 new_chat_id,
3938 &member_ids,
3939 )
3940 .await?;
3941
3942 context.emit_event(EventType::ChatModified(new_chat_id));
3943 chatlist_events::emit_chatlist_changed(context);
3944 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3945
3946 Ok(Some((new_chat_id, create_blocked)))
3947}
3948
3949#[derive(Debug, PartialEq, Eq)]
3950enum VerifiedEncryption {
3951 Verified,
3952 NotVerified(String), }
3954
3955async fn has_verified_encryption(
3959 context: &Context,
3960 mimeparser: &MimeMessage,
3961 from_id: ContactId,
3962) -> Result<VerifiedEncryption> {
3963 use VerifiedEncryption::*;
3964
3965 if !mimeparser.was_encrypted() {
3966 return Ok(NotVerified("This message is not encrypted".to_string()));
3967 };
3968
3969 if from_id == ContactId::SELF {
3970 return Ok(Verified);
3971 }
3972
3973 let from_contact = Contact::get_by_id(context, from_id).await?;
3974
3975 let Some(fingerprint) = from_contact.fingerprint() else {
3976 return Ok(NotVerified(
3977 "The message was sent without encryption".to_string(),
3978 ));
3979 };
3980
3981 if from_contact.get_verifier_id(context).await?.is_none() {
3982 return Ok(NotVerified(
3983 "The message was sent by non-verified contact".to_string(),
3984 ));
3985 }
3986
3987 let signed_with_verified_key = mimeparser
3988 .signature
3989 .as_ref()
3990 .is_some_and(|(signature, _)| *signature == fingerprint);
3991 if signed_with_verified_key {
3992 Ok(Verified)
3993 } else {
3994 Ok(NotVerified(
3995 "The message was sent with non-verified encryption".to_string(),
3996 ))
3997 }
3998}
3999
4000async fn mark_recipients_as_verified(
4001 context: &Context,
4002 from_id: ContactId,
4003 mimeparser: &MimeMessage,
4004) -> Result<()> {
4005 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
4006
4007 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
4011
4012 for gossiped_key in mimeparser
4013 .gossiped_keys
4014 .values()
4015 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
4016 {
4017 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
4018 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
4019 continue;
4020 };
4021
4022 if to_id == ContactId::SELF || to_id == from_id {
4023 continue;
4024 }
4025
4026 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
4027 }
4028
4029 Ok(())
4030}
4031
4032async fn get_previous_message(
4036 context: &Context,
4037 mime_parser: &MimeMessage,
4038) -> Result<Option<Message>> {
4039 if let Some(field) = mime_parser.get_header(HeaderDef::References)
4040 && let Some(rfc724mid) = parse_message_ids(field).last()
4041 && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
4042 {
4043 return Message::load_from_db_optional(context, msg_id).await;
4044 }
4045 Ok(None)
4046}
4047
4048async fn get_parent_message(
4053 context: &Context,
4054 references: Option<&str>,
4055 in_reply_to: Option<&str>,
4056) -> Result<Option<Message>> {
4057 let mut mids = Vec::new();
4058 if let Some(field) = in_reply_to {
4059 mids = parse_message_ids(field);
4060 }
4061 if let Some(field) = references {
4062 mids.append(&mut parse_message_ids(field));
4063 }
4064 message::get_by_rfc724_mids(context, &mids).await
4065}
4066
4067pub(crate) async fn get_prefetch_parent_message(
4068 context: &Context,
4069 headers: &[mailparse::MailHeader<'_>],
4070) -> Result<Option<Message>> {
4071 get_parent_message(
4072 context,
4073 headers.get_header_value(HeaderDef::References).as_deref(),
4074 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
4075 )
4076 .await
4077}
4078
4079async fn add_or_lookup_contacts_by_address_list(
4081 context: &Context,
4082 address_list: &[SingleInfo],
4083 origin: Origin,
4084) -> Result<Vec<Option<ContactId>>> {
4085 let mut contact_ids = Vec::new();
4086 for info in address_list {
4087 let addr = &info.addr;
4088 if !may_be_valid_addr(addr) {
4089 contact_ids.push(None);
4090 continue;
4091 }
4092 let display_name = info.display_name.as_deref();
4093 if let Ok(addr) = ContactAddress::new(addr) {
4094 let (contact_id, _) =
4095 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
4096 .await?;
4097 contact_ids.push(Some(contact_id));
4098 } else {
4099 warn!(context, "Contact with address {:?} cannot exist.", addr);
4100 contact_ids.push(None);
4101 }
4102 }
4103
4104 Ok(contact_ids)
4105}
4106
4107async fn add_or_lookup_key_contacts(
4109 context: &Context,
4110 address_list: &[SingleInfo],
4111 gossiped_keys: &BTreeMap<String, GossipedKey>,
4112 fingerprints: &[Fingerprint],
4113 origin: Origin,
4114) -> Result<Vec<Option<ContactId>>> {
4115 let mut contact_ids = Vec::new();
4116 let mut fingerprint_iter = fingerprints.iter();
4117 for info in address_list {
4118 let fp = fingerprint_iter.next();
4119 let addr = &info.addr;
4120 if !may_be_valid_addr(addr) {
4121 contact_ids.push(None);
4122 continue;
4123 }
4124 let fingerprint: String = if let Some(fp) = fp {
4125 fp.hex()
4127 } else if let Some(key) = gossiped_keys.get(addr) {
4128 key.public_key.dc_fingerprint().hex()
4129 } else if context.is_self_addr(addr).await? {
4130 contact_ids.push(Some(ContactId::SELF));
4131 continue;
4132 } else {
4133 contact_ids.push(None);
4134 continue;
4135 };
4136 let display_name = info.display_name.as_deref();
4137 if let Ok(addr) = ContactAddress::new(addr) {
4138 let (contact_id, _) = Contact::add_or_lookup_ex(
4139 context,
4140 display_name.unwrap_or_default(),
4141 &addr,
4142 &fingerprint,
4143 origin,
4144 )
4145 .await?;
4146 contact_ids.push(Some(contact_id));
4147 } else {
4148 warn!(context, "Contact with address {:?} cannot exist.", addr);
4149 contact_ids.push(None);
4150 }
4151 }
4152
4153 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
4154 Ok(contact_ids)
4155}
4156
4157async fn lookup_key_contact_by_address(
4162 context: &Context,
4163 addr: &str,
4164 chat_id: Option<ChatId>,
4165) -> Result<Option<ContactId>> {
4166 if context.is_self_addr(addr).await? {
4167 if chat_id.is_none() {
4168 return Ok(Some(ContactId::SELF));
4169 }
4170 let is_self_in_chat = context
4171 .sql
4172 .exists(
4173 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
4174 (chat_id,),
4175 )
4176 .await?;
4177 if is_self_in_chat {
4178 return Ok(Some(ContactId::SELF));
4179 }
4180 }
4181 let contact_id: Option<ContactId> = match chat_id {
4182 Some(chat_id) => {
4183 context
4184 .sql
4185 .query_row_optional(
4186 "SELECT id FROM contacts
4187 WHERE contacts.addr=?
4188 AND EXISTS (SELECT 1 FROM chats_contacts
4189 WHERE contact_id=contacts.id
4190 AND chat_id=?)
4191 AND fingerprint<>'' -- Should always be true
4192 ",
4193 (addr, chat_id),
4194 |row| {
4195 let contact_id: ContactId = row.get(0)?;
4196 Ok(contact_id)
4197 },
4198 )
4199 .await?
4200 }
4201 None => {
4202 context
4203 .sql
4204 .query_row_optional(
4205 "SELECT id FROM contacts
4206 WHERE addr=?
4207 AND fingerprint<>''
4208 ORDER BY
4209 (
4210 SELECT COUNT(*) FROM chats c
4211 INNER JOIN chats_contacts cc
4212 ON c.id=cc.chat_id
4213 WHERE c.type=?
4214 AND c.id>?
4215 AND c.blocked=?
4216 AND cc.contact_id=contacts.id
4217 ) DESC,
4218 last_seen DESC, id DESC
4219 ",
4220 (
4221 addr,
4222 Chattype::Single,
4223 constants::DC_CHAT_ID_LAST_SPECIAL,
4224 Blocked::Not,
4225 ),
4226 |row| {
4227 let contact_id: ContactId = row.get(0)?;
4228 Ok(contact_id)
4229 },
4230 )
4231 .await?
4232 }
4233 };
4234 Ok(contact_id)
4235}
4236
4237async fn lookup_key_contact_by_fingerprint(
4238 context: &Context,
4239 fingerprint: &str,
4240) -> Result<Option<ContactId>> {
4241 logged_debug_assert!(
4242 context,
4243 !fingerprint.is_empty(),
4244 "lookup_key_contact_by_fingerprint: fingerprint is empty."
4245 );
4246 if fingerprint.is_empty() {
4247 return Ok(None);
4249 }
4250 if let Some(contact_id) = context
4251 .sql
4252 .query_row_optional(
4253 "SELECT id FROM contacts
4254 WHERE fingerprint=? AND fingerprint!=''",
4255 (fingerprint,),
4256 |row| {
4257 let contact_id: ContactId = row.get(0)?;
4258 Ok(contact_id)
4259 },
4260 )
4261 .await?
4262 {
4263 Ok(Some(contact_id))
4264 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4265 if self_fp == fingerprint {
4266 Ok(Some(ContactId::SELF))
4267 } else {
4268 Ok(None)
4269 }
4270 } else {
4271 Ok(None)
4272 }
4273}
4274
4275async fn lookup_key_contacts_fallback_to_chat(
4291 context: &Context,
4292 address_list: &[SingleInfo],
4293 fingerprints: &[Fingerprint],
4294 chat_id: Option<ChatId>,
4295) -> Result<Vec<Option<ContactId>>> {
4296 let mut contact_ids = Vec::new();
4297 let mut fingerprint_iter = fingerprints.iter();
4298 for info in address_list {
4299 let fp = fingerprint_iter.next();
4300 let addr = &info.addr;
4301 if !may_be_valid_addr(addr) {
4302 contact_ids.push(None);
4303 continue;
4304 }
4305
4306 if let Some(fp) = fp {
4307 let display_name = info.display_name.as_deref();
4309 let fingerprint: String = fp.hex();
4310
4311 if let Ok(addr) = ContactAddress::new(addr) {
4312 let (contact_id, _) = Contact::add_or_lookup_ex(
4313 context,
4314 display_name.unwrap_or_default(),
4315 &addr,
4316 &fingerprint,
4317 Origin::Hidden,
4318 )
4319 .await?;
4320 contact_ids.push(Some(contact_id));
4321 } else {
4322 warn!(context, "Contact with address {:?} cannot exist.", addr);
4323 contact_ids.push(None);
4324 }
4325 } else {
4326 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4327 contact_ids.push(contact_id);
4328 }
4329 }
4330 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4331 Ok(contact_ids)
4332}
4333
4334fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4337 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4338 || mime_parser.get_header(HeaderDef::Sender).is_some()
4339}
4340
4341#[cfg(test)]
4342mod receive_imf_tests;