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