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 && !matches!(mime_parser.pre_message, PreMessageMode::Pre { .. })
831 {
832 let can_info_msg;
833 let instance = if mime_parser
834 .parts
835 .first()
836 .is_some_and(|part| part.typ == Viewtype::Webxdc)
837 {
838 can_info_msg = false;
839 if mime_parser.pre_message == PreMessageMode::Post
840 && let Some(msg) =
841 Message::load_by_rfc724_mid_optional(context, rfc724_mid_orig).await?
842 {
843 Some(msg)
846 } else {
847 Message::load_from_db_optional(context, insert_msg_id)
848 .await
849 .context("Failed to load just created webxdc instance")?
850 }
851 } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
852 if let Some(instance) =
853 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
854 {
855 can_info_msg = instance.download_state() == DownloadState::Done;
856 Some(instance)
857 } else {
858 can_info_msg = false;
859 None
860 }
861 } else {
862 can_info_msg = false;
863 None
864 };
865
866 if let Some(instance) = instance {
867 if let Err(err) = context
868 .receive_status_update(
869 from_id,
870 &instance,
871 received_msg.sort_timestamp,
872 can_info_msg,
873 status_update,
874 )
875 .await
876 {
877 warn!(context, "receive_imf cannot update status: {err:#}.");
878 }
879 } else {
880 warn!(
881 context,
882 "Received webxdc update, but cannot assign it to message."
883 );
884 }
885 }
886
887 if let Some(avatar_action) = &mime_parser.user_avatar
888 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
889 && context
890 .update_contacts_timestamp(from_id, Param::AvatarTimestamp, mime_parser.timestamp_sent)
891 .await?
892 && let Err(err) = contact::set_profile_image(context, from_id, avatar_action).await
893 {
894 warn!(context, "receive_imf cannot update profile image: {err:#}.");
895 };
896
897 if let Some(footer) = &mime_parser.footer
899 && !mime_parser.is_mailinglist_message()
900 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
901 && context
902 .update_contacts_timestamp(from_id, Param::StatusTimestamp, mime_parser.timestamp_sent)
903 .await?
904 && let Err(err) = contact::set_status(context, from_id, footer.to_string()).await
905 {
906 warn!(context, "Cannot update contact status: {err:#}.");
907 }
908
909 if !received_msg.msg_ids.is_empty() {
911 let target = if received_msg.needs_delete_job {
912 Some("".to_string())
913 } else {
914 None
915 };
916 if target.is_some() || rfc724_mid_orig != rfc724_mid {
917 let target_subst = match &target {
918 Some(_) => "target=?1,",
919 None => "",
920 };
921 context
922 .sql
923 .execute(
924 &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
925 (
926 target.as_deref().unwrap_or_default(),
927 rfc724_mid_orig,
928 rfc724_mid,
929 ),
930 )
931 .await?;
932 context.scheduler.interrupt_inbox().await;
933 }
934 if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
935 {
936 markseen_on_imap_table(context, rfc724_mid_orig).await?;
938 }
939 if !mime_parser.incoming && !context.get_config_bool(Config::TeamProfile).await? {
940 let mut updated_chats = BTreeMap::new();
941 let mut archived_chats_maybe_noticed = false;
942 for report in &mime_parser.mdn_reports {
943 for msg_rfc724_mid in report
944 .original_message_id
945 .iter()
946 .chain(&report.additional_message_ids)
947 {
948 let Some(msg_id) = rfc724_mid_exists(context, msg_rfc724_mid).await? else {
949 continue;
950 };
951 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
952 continue;
953 };
954 if msg.state < MessageState::InFresh || msg.state >= MessageState::InSeen {
955 continue;
956 }
957 if !mime_parser.was_encrypted() && msg.get_showpadlock() {
958 warn!(context, "MDN: Not encrypted. Ignoring.");
959 continue;
960 }
961 message::update_msg_state(context, msg_id, MessageState::InSeen).await?;
962 if let Err(e) = msg_id.start_ephemeral_timer(context).await {
963 error!(context, "start_ephemeral_timer for {msg_id}: {e:#}.");
964 }
965 if !mime_parser.has_chat_version() {
966 continue;
967 }
968 archived_chats_maybe_noticed |= msg.state < MessageState::InNoticed
969 && msg.chat_visibility == ChatVisibility::Archived;
970 updated_chats
971 .entry(msg.chat_id)
972 .and_modify(|pos| *pos = cmp::max(*pos, (msg.timestamp_sort, msg.id)))
973 .or_insert((msg.timestamp_sort, msg.id));
974 }
975 }
976 for (chat_id, (timestamp_sort, msg_id)) in updated_chats {
977 context
978 .sql
979 .execute(
980 "
981UPDATE msgs SET state=? WHERE
982 state=? AND
983 hidden=0 AND
984 chat_id=? AND
985 (timestamp,id)<(?,?)",
986 (
987 MessageState::InNoticed,
988 MessageState::InFresh,
989 chat_id,
990 timestamp_sort,
991 msg_id,
992 ),
993 )
994 .await
995 .context("UPDATE msgs.state")?;
996 if chat_id.get_fresh_msg_cnt(context).await? == 0 {
997 context.emit_event(EventType::MsgsNoticed(chat_id));
999 } else {
1000 context.emit_msgs_changed_without_msg_id(chat_id);
1001 }
1002 chatlist_events::emit_chatlist_item_changed(context, chat_id);
1003 }
1004 if archived_chats_maybe_noticed {
1005 context.on_archived_chats_maybe_noticed();
1006 }
1007 }
1008 }
1009
1010 if mime_parser.is_call() {
1011 context
1012 .handle_call_msg(insert_msg_id, &mime_parser, from_id)
1013 .await?;
1014 } else if received_msg.hidden {
1015 } else if !chat_id.is_trash() {
1017 let fresh = received_msg.state == MessageState::InFresh
1018 && mime_parser.is_system_message != SystemMessage::CallAccepted
1019 && mime_parser.is_system_message != SystemMessage::CallEnded;
1020 let is_bot = context.get_config_bool(Config::Bot).await?;
1021 let is_pre_message = matches!(mime_parser.pre_message, PreMessageMode::Pre { .. });
1022 let skip_bot_notify = is_bot && is_pre_message;
1023 let is_empty = !is_pre_message
1024 && mime_parser.parts.first().is_none_or(|p| {
1025 p.typ == Viewtype::Text && p.msg.is_empty() && p.param.get(Param::Quote).is_none()
1026 });
1027 let important = mime_parser.incoming
1028 && !is_empty
1029 && fresh
1030 && !is_old_contact_request
1031 && !skip_bot_notify;
1032
1033 for msg_id in &received_msg.msg_ids {
1034 chat_id.emit_msg_event(context, *msg_id, important);
1035 }
1036 }
1037 context.new_msgs_notify.notify_one();
1038
1039 mime_parser
1040 .handle_reports(context, from_id, &mime_parser.parts)
1041 .await;
1042
1043 if let Some(is_bot) = mime_parser.is_bot {
1044 if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
1047 from_id.mark_bot(context, is_bot).await?;
1048 }
1049 }
1050
1051 Ok(Some(received_msg))
1052}
1053
1054pub async fn from_field_to_contact_id(
1072 context: &Context,
1073 from: &SingleInfo,
1074 fingerprint: Option<&Fingerprint>,
1075 prevent_rename: bool,
1076 find_key_contact_by_addr: bool,
1077) -> Result<Option<(ContactId, bool, Origin)>> {
1078 let fingerprint = fingerprint.as_ref().map(|fp| fp.hex()).unwrap_or_default();
1079 let display_name = if prevent_rename {
1080 Some("")
1081 } else {
1082 from.display_name.as_deref()
1083 };
1084 let from_addr = match ContactAddress::new(&from.addr) {
1085 Ok(from_addr) => from_addr,
1086 Err(err) => {
1087 warn!(
1088 context,
1089 "Cannot create a contact for the given From field: {err:#}."
1090 );
1091 return Ok(None);
1092 }
1093 };
1094
1095 if fingerprint.is_empty() && find_key_contact_by_addr {
1096 let addr_normalized = addr_normalize(&from_addr);
1097
1098 if let Some((from_id, origin)) = context
1100 .sql
1101 .query_row_optional(
1102 "SELECT id, origin FROM contacts
1103 WHERE addr=?1 COLLATE NOCASE
1104 AND fingerprint<>'' -- Only key-contacts
1105 AND id>?2 AND origin>=?3 AND blocked=?4
1106 ORDER BY last_seen DESC
1107 LIMIT 1",
1108 (
1109 &addr_normalized,
1110 ContactId::LAST_SPECIAL,
1111 Origin::IncomingUnknownFrom,
1112 Blocked::Not,
1113 ),
1114 |row| {
1115 let id: ContactId = row.get(0)?;
1116 let origin: Origin = row.get(1)?;
1117 Ok((id, origin))
1118 },
1119 )
1120 .await?
1121 {
1122 return Ok(Some((from_id, false, origin)));
1123 }
1124 }
1125
1126 let (from_id, _) = Contact::add_or_lookup_ex(
1127 context,
1128 display_name.unwrap_or_default(),
1129 &from_addr,
1130 &fingerprint,
1131 Origin::IncomingUnknownFrom,
1132 )
1133 .await?;
1134
1135 if from_id == ContactId::SELF {
1136 Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
1137 } else {
1138 let contact = Contact::get_by_id(context, from_id).await?;
1139 let from_id_blocked = contact.blocked;
1140 let incoming_origin = contact.origin;
1141
1142 context
1143 .sql
1144 .execute(
1145 "UPDATE contacts SET addr=? WHERE id=?",
1146 (from_addr, from_id),
1147 )
1148 .await?;
1149
1150 Ok(Some((from_id, from_id_blocked, incoming_origin)))
1151 }
1152}
1153
1154#[expect(clippy::arithmetic_side_effects)]
1155async fn decide_chat_assignment(
1156 context: &Context,
1157 mime_parser: &MimeMessage,
1158 parent_message: &Option<Message>,
1159 rfc724_mid: &str,
1160 from_id: ContactId,
1161) -> Result<ChatAssignment> {
1162 let mut should_trash = if !mime_parser.mdn_reports.is_empty() {
1163 info!(context, "Message is an MDN (TRASH).");
1164 true
1165 } else if mime_parser.delivery_report.is_some() {
1166 info!(context, "Message is a DSN (TRASH).");
1167 markseen_on_imap_table(context, rfc724_mid).await.ok();
1168 true
1169 } else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
1170 || mime_parser.get_header(HeaderDef::ChatDelete).is_some()
1171 || mime_parser.get_header(HeaderDef::IrohNodeAddr).is_some()
1172 || mime_parser.sync_items.is_some()
1173 {
1174 info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
1175 true
1176 } else if mime_parser.is_system_message == SystemMessage::CallAccepted
1177 || mime_parser.is_system_message == SystemMessage::CallEnded
1178 {
1179 info!(context, "Call state changed (TRASH).");
1180 true
1181 } else if let Some(ref decryption_error) = mime_parser.decryption_error
1182 && !mime_parser.incoming
1183 {
1184 let last_time = context
1186 .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1187 .await?;
1188 let now = tools::time();
1189 let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1190 let txt = format!(
1191 "⚠️ 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})."
1192 );
1193 let mut msg = Message::new_text(txt.to_string());
1194 chat::add_device_msg(context, None, Some(&mut msg))
1195 .await
1196 .log_err(context)
1197 .ok();
1198 true
1199 } else {
1200 last_time > now
1201 };
1202 if update_config {
1203 context
1204 .set_config_internal(Config::LastCantDecryptOutgoingMsgs, Some(&now.to_string()))
1205 .await?;
1206 }
1207 info!(context, "Outgoing undecryptable message (TRASH).");
1208 true
1209 } else if mime_parser
1210 .get_header(HeaderDef::XMozillaDraftInfo)
1211 .is_some()
1212 {
1213 info!(context, "Email is probably just a draft (TRASH).");
1219 true
1220 } else if matches!(mime_parser.pre_message, PreMessageMode::Pre { .. }) {
1221 false
1222 } else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1223 if let Some(part) = mime_parser.parts.first() {
1224 if part.typ == Viewtype::Text && part.msg.is_empty() {
1225 info!(context, "Message is a status update only (TRASH).");
1226 markseen_on_imap_table(context, rfc724_mid).await.ok();
1227 true
1228 } else {
1229 false
1230 }
1231 } else {
1232 false
1233 }
1234 } else {
1235 false
1236 };
1237
1238 should_trash |= if mime_parser.pre_message == PreMessageMode::Post {
1239 let pre_message_exists = msg_is_downloaded_for(context, rfc724_mid).await?;
1241 info!(
1242 context,
1243 "Message {rfc724_mid} is a post-message ({}).",
1244 if pre_message_exists {
1245 "pre-message exists already, so trash after replacing attachment"
1246 } else {
1247 "no pre-message -> Keep"
1248 }
1249 );
1250 pre_message_exists
1251 } else if let PreMessageMode::Pre {
1252 post_msg_rfc724_mid,
1253 ..
1254 } = &mime_parser.pre_message
1255 {
1256 let post_msg_exists = if let Some((msg_id, not_downloaded)) =
1257 message::rfc724_mid_exists_ex(context, post_msg_rfc724_mid, "download_state<>0").await?
1258 {
1259 context
1260 .sql
1261 .execute(
1262 "UPDATE msgs SET pre_rfc724_mid=? WHERE id=?",
1263 (rfc724_mid, msg_id),
1264 )
1265 .await?;
1266 !not_downloaded
1267 } else {
1268 false
1269 };
1270 info!(
1271 context,
1272 "Message {rfc724_mid} is a pre-message for {post_msg_rfc724_mid} (post_msg_exists:{post_msg_exists})."
1273 );
1274 post_msg_exists
1275 } else {
1276 false
1277 };
1278
1279 let mut num_recipients = 0;
1284 let mut has_self_addr = false;
1285
1286 if let Some((sender_fingerprint, intended_recipient_fingerprints)) = mime_parser
1287 .signature
1288 .as_ref()
1289 .filter(|(_sender_fingerprint, fps)| !fps.is_empty())
1290 {
1291 has_self_addr = true;
1296
1297 num_recipients = intended_recipient_fingerprints
1298 .iter()
1299 .filter(|fp| *fp != sender_fingerprint)
1300 .count();
1301 } else {
1302 for recipient in &mime_parser.recipients {
1305 has_self_addr |= context.is_self_addr(&recipient.addr).await?;
1306 if addr_cmp(&recipient.addr, &mime_parser.from.addr) {
1307 continue;
1308 }
1309 num_recipients += 1;
1310 }
1311 if from_id != ContactId::SELF && !has_self_addr {
1312 num_recipients += 1;
1313 }
1314 }
1315 let mut can_be_11_chat_log = String::new();
1316 let mut l = |cond: bool, s: String| {
1317 can_be_11_chat_log += &s;
1318 cond
1319 };
1320 let can_be_11_chat = l(
1321 num_recipients <= 1,
1322 format!("num_recipients={num_recipients}."),
1323 ) && (l(from_id != ContactId::SELF, format!(" from_id={from_id}."))
1324 || !(l(
1325 mime_parser.recipients.is_empty(),
1326 format!(" Raw recipients len={}.", mime_parser.recipients.len()),
1327 ) || l(has_self_addr, format!(" has_self_addr={has_self_addr}.")))
1328 || l(
1329 mime_parser.was_encrypted(),
1330 format!(" was_encrypted={}.", mime_parser.was_encrypted()),
1331 ));
1332
1333 let chat_assignment_log;
1334 let chat_assignment = if should_trash {
1335 chat_assignment_log = "".to_string();
1336 ChatAssignment::Trash
1337 } else if mime_parser.get_mailinglist_header().is_some() {
1338 chat_assignment_log = "Mailing list header found.".to_string();
1339 ChatAssignment::MailingListOrBroadcast
1340 } else if let Some(grpid) = mime_parser.get_chat_group_id() {
1341 if mime_parser.was_encrypted() {
1342 chat_assignment_log = "Encrypted group message.".to_string();
1343 ChatAssignment::GroupChat {
1344 grpid: grpid.to_string(),
1345 }
1346 } else if let Some(parent) = &parent_message {
1347 if let Some((chat_id, chat_id_blocked)) =
1348 lookup_chat_by_reply(context, mime_parser, parent).await?
1349 {
1350 chat_assignment_log = "Unencrypted group reply.".to_string();
1352 ChatAssignment::ExistingChat {
1353 chat_id,
1354 chat_id_blocked,
1355 }
1356 } else {
1357 chat_assignment_log = "Unencrypted group reply.".to_string();
1358 ChatAssignment::AdHocGroup
1359 }
1360 } else {
1361 chat_assignment_log = "Unencrypted group message, no parent.".to_string();
1369 ChatAssignment::AdHocGroup
1370 }
1371 } else if let Some(parent) = &parent_message {
1372 if let Some((chat_id, chat_id_blocked)) =
1373 lookup_chat_by_reply(context, mime_parser, parent).await?
1374 {
1375 chat_assignment_log = "Reply w/o grpid.".to_string();
1377 ChatAssignment::ExistingChat {
1378 chat_id,
1379 chat_id_blocked,
1380 }
1381 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1382 chat_assignment_log = "Reply with Chat-Group-Name.".to_string();
1383 ChatAssignment::AdHocGroup
1384 } else if can_be_11_chat {
1385 chat_assignment_log = format!("Non-group reply. {can_be_11_chat_log}");
1386 ChatAssignment::OneOneChat
1387 } else {
1388 chat_assignment_log = format!("Non-group reply. {can_be_11_chat_log}");
1389 ChatAssignment::AdHocGroup
1390 }
1391 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1392 chat_assignment_log = "Message with Chat-Group-Name, no parent.".to_string();
1393 ChatAssignment::AdHocGroup
1394 } else if can_be_11_chat {
1395 chat_assignment_log = format!("Non-group message, no parent. {can_be_11_chat_log}");
1396 ChatAssignment::OneOneChat
1397 } else {
1398 chat_assignment_log = format!("Non-group message, no parent. {can_be_11_chat_log}");
1399 ChatAssignment::AdHocGroup
1400 };
1401
1402 if !chat_assignment_log.is_empty() {
1403 info!(
1404 context,
1405 "{chat_assignment_log} Chat assignment = {chat_assignment:?}."
1406 );
1407 }
1408 Ok(chat_assignment)
1409}
1410
1411#[expect(clippy::too_many_arguments)]
1420async fn do_chat_assignment(
1421 context: &Context,
1422 chat_assignment: &ChatAssignment,
1423 from_id: ContactId,
1424 to_ids: &[Option<ContactId>],
1425 past_ids: &[Option<ContactId>],
1426 to_id: ContactId,
1427 allow_creation: bool,
1428 mime_parser: &mut MimeMessage,
1429 parent_message: Option<Message>,
1430) -> Result<(ChatId, Blocked, bool)> {
1431 let is_bot = context.get_config_bool(Config::Bot).await?;
1432
1433 let mut chat_id = None;
1434 let mut chat_id_blocked = Blocked::Not;
1435 let mut chat_created = false;
1436
1437 if mime_parser.incoming {
1438 let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
1439
1440 let create_blocked_default = if is_bot {
1441 Blocked::Not
1442 } else {
1443 Blocked::Request
1444 };
1445 let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
1446 match blocked {
1447 Blocked::Request => create_blocked_default,
1448 Blocked::Not => Blocked::Not,
1449 Blocked::Yes => {
1450 if Contact::is_blocked_load(context, from_id).await? {
1451 Blocked::Yes
1454 } else {
1455 create_blocked_default
1459 }
1460 }
1461 }
1462 } else {
1463 create_blocked_default
1464 };
1465
1466 match &chat_assignment {
1467 ChatAssignment::Trash => {
1468 chat_id = Some(DC_CHAT_ID_TRASH);
1469 }
1470 ChatAssignment::GroupChat { grpid } => {
1471 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1473 chat_id = Some(id);
1474 chat_id_blocked = blocked;
1475 } else if (allow_creation || test_normal_chat.is_some())
1476 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1477 context,
1478 mime_parser,
1479 create_blocked,
1480 from_id,
1481 to_ids,
1482 past_ids,
1483 grpid,
1484 )
1485 .await?
1486 {
1487 chat_id = Some(new_chat_id);
1488 chat_id_blocked = new_chat_id_blocked;
1489 chat_created = true;
1490 }
1491 }
1492 ChatAssignment::MailingListOrBroadcast => {
1493 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header()
1494 && let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1495 create_or_lookup_mailinglist_or_broadcast(
1496 context,
1497 allow_creation,
1498 create_blocked,
1499 mailinglist_header,
1500 from_id,
1501 mime_parser,
1502 )
1503 .await?
1504 {
1505 chat_id = Some(new_chat_id);
1506 chat_id_blocked = new_chat_id_blocked;
1507 chat_created = new_chat_created;
1508
1509 apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
1510 }
1511 }
1512 ChatAssignment::ExistingChat {
1513 chat_id: new_chat_id,
1514 chat_id_blocked: new_chat_id_blocked,
1515 } => {
1516 chat_id = Some(*new_chat_id);
1517 chat_id_blocked = *new_chat_id_blocked;
1518 }
1519 ChatAssignment::AdHocGroup => {
1520 if let Some((new_chat_id, new_chat_id_blocked, new_created)) =
1521 lookup_or_create_adhoc_group(
1522 context,
1523 mime_parser,
1524 to_ids,
1525 allow_creation || test_normal_chat.is_some(),
1526 create_blocked,
1527 )
1528 .await?
1529 {
1530 chat_id = Some(new_chat_id);
1531 chat_id_blocked = new_chat_id_blocked;
1532 chat_created = new_created;
1533 }
1534 }
1535 ChatAssignment::OneOneChat => {}
1536 }
1537
1538 if chat_id_blocked != Blocked::Not
1541 && create_blocked != Blocked::Yes
1542 && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
1543 && let Some(chat_id) = chat_id
1544 {
1545 chat_id.set_blocked(context, create_blocked).await?;
1546 chat_id_blocked = create_blocked;
1547 }
1548
1549 if chat_id.is_none() {
1550 let contact = Contact::get_by_id(context, from_id).await?;
1552 let create_blocked = match contact.is_blocked() {
1553 true => Blocked::Yes,
1554 false if is_bot => Blocked::Not,
1555 false => Blocked::Request,
1556 };
1557
1558 if let Some(chat) = test_normal_chat {
1559 chat_id = Some(chat.id);
1560 chat_id_blocked = chat.blocked;
1561 } else if allow_creation {
1562 let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
1563 .await
1564 .context("Failed to get (new) chat for contact")?;
1565 chat_id = Some(chat.id);
1566 chat_id_blocked = chat.blocked;
1567 chat_created = true;
1568 }
1569
1570 if let Some(chat_id) = chat_id
1571 && chat_id_blocked != Blocked::Not
1572 {
1573 if chat_id_blocked != create_blocked {
1574 chat_id.set_blocked(context, create_blocked).await?;
1575 }
1576 if create_blocked == Blocked::Request && parent_message.is_some() {
1577 ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo).await?;
1580 info!(
1581 context,
1582 "Message is a reply to a known message, mark sender as known.",
1583 );
1584 }
1585 }
1586 }
1587 } else {
1588 let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1595
1596 match &chat_assignment {
1597 ChatAssignment::Trash => {
1598 chat_id = Some(DC_CHAT_ID_TRASH);
1599 }
1600 ChatAssignment::GroupChat { grpid } => {
1601 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1602 chat_id = Some(id);
1603 chat_id_blocked = blocked;
1604 } else if allow_creation
1605 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1606 context,
1607 mime_parser,
1608 Blocked::Not,
1609 from_id,
1610 to_ids,
1611 past_ids,
1612 grpid,
1613 )
1614 .await?
1615 {
1616 chat_id = Some(new_chat_id);
1617 chat_id_blocked = new_chat_id_blocked;
1618 chat_created = true;
1619 }
1620 }
1621 ChatAssignment::ExistingChat {
1622 chat_id: new_chat_id,
1623 chat_id_blocked: new_chat_id_blocked,
1624 } => {
1625 chat_id = Some(*new_chat_id);
1626 chat_id_blocked = *new_chat_id_blocked;
1627 }
1628 ChatAssignment::MailingListOrBroadcast => {
1629 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1632 let listid = mailinglist_header_listid(mailinglist_header)?;
1633 if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await? {
1634 chat_id = Some(id);
1635 } else {
1636 let name =
1638 compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1639 if let Some(secret) = mime_parser
1640 .get_header(HeaderDef::ChatBroadcastSecret)
1641 .filter(|s| validate_broadcast_secret(s))
1642 {
1643 chat_created = true;
1644 chat_id = Some(
1645 chat::create_out_broadcast_ex(
1646 context,
1647 Nosync,
1648 listid,
1649 name,
1650 secret.to_string(),
1651 )
1652 .await?,
1653 );
1654 } else {
1655 warn!(
1656 context,
1657 "Not creating outgoing broadcast with id {listid}, because secret is unknown"
1658 );
1659 }
1660 }
1661 }
1662 }
1663 ChatAssignment::AdHocGroup => {
1664 if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1665 lookup_or_create_adhoc_group(
1666 context,
1667 mime_parser,
1668 to_ids,
1669 allow_creation,
1670 Blocked::Not,
1671 )
1672 .await?
1673 {
1674 chat_id = Some(new_chat_id);
1675 chat_id_blocked = new_chat_id_blocked;
1676 chat_created = new_chat_created;
1677 }
1678 }
1679 ChatAssignment::OneOneChat => {}
1680 }
1681
1682 if !to_ids.is_empty() {
1683 if chat_id.is_none() && allow_creation {
1684 let to_contact = Contact::get_by_id(context, to_id).await?;
1685 if let Some(list_id) = to_contact.param.get(Param::ListId) {
1686 if let Some((id, blocked)) =
1687 chat::get_chat_id_by_grpid(context, list_id).await?
1688 {
1689 chat_id = Some(id);
1690 chat_id_blocked = blocked;
1691 }
1692 } else {
1693 let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1694 chat_id = Some(chat.id);
1695 chat_id_blocked = chat.blocked;
1696 chat_created = true;
1697 }
1698 }
1699 if chat_id.is_none()
1700 && mime_parser.has_chat_version()
1701 && let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await?
1702 {
1703 chat_id = Some(chat.id);
1704 chat_id_blocked = chat.blocked;
1705 }
1706 }
1707
1708 if chat_id.is_none() && self_sent {
1709 let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1712 .await
1713 .context("Failed to get (new) chat for contact")?;
1714
1715 chat_id = Some(chat.id);
1716 chat_id_blocked = chat.blocked;
1717
1718 if Blocked::Not != chat.blocked {
1719 chat.id.unblock_ex(context, Nosync).await?;
1720 }
1721 }
1722
1723 if chat_id_blocked != Blocked::Not
1725 && let Some(chat_id) = chat_id
1726 {
1727 chat_id.unblock_ex(context, Nosync).await?;
1728 chat_id_blocked = Blocked::Not;
1729 }
1730 }
1731 let chat_id = chat_id.unwrap_or_else(|| {
1732 info!(context, "No chat id for message (TRASH).");
1733 DC_CHAT_ID_TRASH
1734 });
1735 Ok((chat_id, chat_id_blocked, chat_created))
1736}
1737
1738#[expect(clippy::too_many_arguments)]
1742async fn add_parts(
1743 context: &Context,
1744 mime_parser: &mut MimeMessage,
1745 imf_raw: &[u8],
1746 to_ids: &[Option<ContactId>],
1747 past_ids: &[Option<ContactId>],
1748 rfc724_mid: &str,
1749 from_id: ContactId,
1750 seen: bool,
1751 prevent_rename: bool,
1752 mut chat_id: ChatId,
1753 mut chat_id_blocked: Blocked,
1754 is_dc_message: MessengerMessage,
1755 is_chat_created: bool,
1756) -> Result<ReceivedMsg> {
1757 let to_id = if mime_parser.incoming {
1758 ContactId::SELF
1759 } else {
1760 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
1761 };
1762
1763 if prevent_rename && let Some(name) = &mime_parser.from.display_name {
1766 for part in &mut mime_parser.parts {
1767 part.param.set(Param::OverrideSenderDisplayname, name);
1768 }
1769 }
1770
1771 let mut chat = Chat::load_from_db(context, chat_id).await?;
1772
1773 if mime_parser.incoming && !chat_id.is_trash() {
1774 if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
1777 let from = &mime_parser.from;
1781 let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
1782 for part in &mut mime_parser.parts {
1783 part.param.set(Param::OverrideSenderDisplayname, name);
1784 }
1785
1786 if chat.typ == Chattype::InBroadcast {
1787 warn!(
1788 context,
1789 "Not assigning msg '{rfc724_mid}' to broadcast {chat_id}: wrong sender: {from_id}."
1790 );
1791 let direct_chat =
1792 ChatIdBlocked::get_for_contact(context, from_id, Blocked::Request).await?;
1793 chat_id = direct_chat.id;
1794 chat_id_blocked = direct_chat.blocked;
1795 chat = Chat::load_from_db(context, chat_id).await?;
1796 }
1797 }
1798 }
1799
1800 let sort_to_bottom = !chat.is_self_in_chat(context).await?;
1809
1810 let is_location_kml = mime_parser.location_kml.is_some();
1811 let mut group_changes = match chat.typ {
1812 _ if chat.id.is_special() => GroupChangesInfo::default(),
1813 Chattype::Single => GroupChangesInfo::default(),
1814 Chattype::Mailinglist => GroupChangesInfo::default(),
1815 Chattype::OutBroadcast => {
1816 apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1817 }
1818 Chattype::Group => {
1819 apply_group_changes(
1820 context,
1821 mime_parser,
1822 &mut chat,
1823 from_id,
1824 to_ids,
1825 past_ids,
1826 is_chat_created,
1827 )
1828 .await?
1829 }
1830 Chattype::InBroadcast => {
1831 apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1832 }
1833 };
1834
1835 let rfc724_mid_orig = &mime_parser
1836 .get_rfc724_mid()
1837 .unwrap_or(rfc724_mid.to_string());
1838
1839 let mut ephemeral_timer = if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer)
1841 {
1842 match EphemeralTimer::from_str(value) {
1843 Ok(timer) => timer,
1844 Err(err) => {
1845 warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1846 EphemeralTimer::Disabled
1847 }
1848 }
1849 } else {
1850 EphemeralTimer::Disabled
1851 };
1852
1853 let state = if !mime_parser.incoming {
1854 MessageState::OutDelivered
1855 } else if seen
1856 || !mime_parser.mdn_reports.is_empty()
1857 || chat_id_blocked == Blocked::Yes
1858 || group_changes.silent
1859 {
1861 MessageState::InSeen
1862 } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL {
1863 MessageState::InNoticed
1864 } else {
1865 MessageState::InFresh
1866 };
1867 let in_fresh = state == MessageState::InFresh;
1868
1869 let sort_timestamp = chat_id
1870 .calc_sort_timestamp(context, mime_parser.timestamp_sent, sort_to_bottom)
1871 .await?;
1872
1873 if !chat_id.is_special()
1879 && !mime_parser.parts.is_empty()
1880 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1881 {
1882 let chat_contacts =
1883 BTreeSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1884 let is_from_in_chat =
1885 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1886
1887 info!(
1888 context,
1889 "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1890 );
1891 if !is_from_in_chat {
1892 warn!(
1893 context,
1894 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1895 );
1896 } else if is_dc_message == MessengerMessage::Yes
1897 && get_previous_message(context, mime_parser)
1898 .await?
1899 .map(|p| p.ephemeral_timer)
1900 == Some(ephemeral_timer)
1901 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1902 {
1903 warn!(
1910 context,
1911 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1912 );
1913 } else if chat_id
1914 .update_timestamp(
1915 context,
1916 Param::EphemeralSettingsTimestamp,
1917 mime_parser.timestamp_sent,
1918 )
1919 .await?
1920 {
1921 if let Err(err) = chat_id
1922 .inner_set_ephemeral_timer(context, ephemeral_timer)
1923 .await
1924 {
1925 warn!(
1926 context,
1927 "Failed to modify timer for chat {chat_id}: {err:#}."
1928 );
1929 } else {
1930 info!(
1931 context,
1932 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1933 );
1934 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1935 chat::add_info_msg_with_cmd(
1936 context,
1937 chat_id,
1938 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1939 SystemMessage::Unknown,
1940 Some(sort_timestamp),
1941 mime_parser.timestamp_sent,
1942 None,
1943 None,
1944 None,
1945 )
1946 .await?;
1947 }
1948 }
1949 } else {
1950 warn!(
1951 context,
1952 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1953 );
1954 }
1955 }
1956
1957 let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1958 {
1959 Some(stock_str::msg_location_enabled_by(context, from_id).await)
1960 } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1961 let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1962
1963 ephemeral_timer = EphemeralTimer::Disabled;
1970
1971 Some(better_msg)
1972 } else {
1973 None
1974 };
1975
1976 drop(chat); let sort_timestamp = tweak_sort_timestamp(
1979 context,
1980 mime_parser,
1981 group_changes.silent,
1982 chat_id,
1983 sort_timestamp,
1984 )
1985 .await?;
1986
1987 let mime_in_reply_to = mime_parser
1988 .get_header(HeaderDef::InReplyTo)
1989 .unwrap_or_default();
1990 let mime_references = mime_parser
1991 .get_header(HeaderDef::References)
1992 .unwrap_or_default();
1993
1994 let icnt = mime_parser.parts.len();
1999
2000 let subject = mime_parser.get_subject().unwrap_or_default();
2001
2002 let is_system_message = mime_parser.is_system_message;
2003
2004 let mut save_mime_modified = false;
2011
2012 let mime_headers = if mime_parser.is_mime_modified {
2013 let headers = if !mime_parser.decoded_data.is_empty() {
2014 mime_parser.decoded_data.clone()
2015 } else {
2016 imf_raw.to_vec()
2017 };
2018 tokio::task::block_in_place(move || buf_compress(&headers))?
2019 } else {
2020 Vec::new()
2021 };
2022
2023 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
2024
2025 if let Some(m) = group_changes.better_msg {
2026 match &better_msg {
2027 None => better_msg = Some(m),
2028 Some(_) => {
2029 if !m.is_empty() {
2030 group_changes.extra_msgs.push((m, is_system_message, None))
2031 }
2032 }
2033 }
2034 }
2035
2036 let chat_id = if better_msg
2037 .as_ref()
2038 .is_some_and(|better_msg| better_msg.is_empty())
2039 {
2040 DC_CHAT_ID_TRASH
2041 } else {
2042 chat_id
2043 };
2044
2045 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
2046 chat::add_info_msg_with_cmd(
2047 context,
2048 chat_id,
2049 &group_changes_msg,
2050 cmd,
2051 Some(sort_timestamp),
2052 mime_parser.timestamp_sent,
2053 None,
2054 None,
2055 added_removed_id,
2056 )
2057 .await?;
2058 }
2059
2060 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
2061 match mime_parser.get_header(HeaderDef::InReplyTo) {
2062 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
2063 Some(instance_id) => {
2064 if let Err(err) =
2065 add_gossip_peer_from_header(context, instance_id, node_addr).await
2066 {
2067 warn!(context, "Failed to add iroh peer from header: {err:#}.");
2068 }
2069 }
2070 None => {
2071 warn!(
2072 context,
2073 "Cannot add iroh peer because WebXDC instance does not exist."
2074 );
2075 }
2076 },
2077 None => {
2078 warn!(
2079 context,
2080 "Cannot add iroh peer because the message has no In-Reply-To."
2081 );
2082 }
2083 }
2084 }
2085
2086 handle_edit_delete(context, mime_parser, from_id).await?;
2087 handle_post_message(context, mime_parser, from_id, state).await?;
2088
2089 if mime_parser.is_system_message == SystemMessage::CallAccepted
2090 || mime_parser.is_system_message == SystemMessage::CallEnded
2091 {
2092 if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
2093 if let Some(call) =
2094 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
2095 {
2096 context
2097 .handle_call_msg(call.get_id(), mime_parser, from_id)
2098 .await?;
2099 } else {
2100 warn!(context, "Call: Cannot load parent.")
2101 }
2102 } else {
2103 warn!(context, "Call: Not a reply.")
2104 }
2105 }
2106
2107 let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
2108 let mut parts = mime_parser.parts.iter().peekable();
2109 while let Some(part) = parts.next() {
2110 let hidden = part.is_reaction;
2111 if part.is_reaction {
2112 let reaction_str = simplify::remove_footers(part.msg.as_str());
2113 let is_incoming_fresh = mime_parser.incoming && !seen;
2114 set_msg_reaction(
2115 context,
2116 mime_in_reply_to,
2117 chat_id,
2118 from_id,
2119 sort_timestamp,
2120 Reaction::new(reaction_str.as_str()),
2121 is_incoming_fresh,
2122 )
2123 .await?;
2124 }
2125
2126 let mut param = part.param.clone();
2127 if is_system_message != SystemMessage::Unknown {
2128 param.set_int(Param::Cmd, is_system_message as i32);
2129 }
2130
2131 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
2132 (better_msg, Viewtype::Text)
2133 } else {
2134 (&part.msg, part.typ)
2135 };
2136 let part_is_empty =
2137 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
2138
2139 if let Some(contact_id) = group_changes.added_removed_id {
2140 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
2141 }
2142
2143 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
2144 let save_mime_modified = save_mime_modified && parts.peek().is_none();
2145
2146 let ephemeral_timestamp = if in_fresh {
2147 0
2148 } else {
2149 match ephemeral_timer {
2150 EphemeralTimer::Disabled => 0,
2151 EphemeralTimer::Enabled { duration } => {
2152 mime_parser.timestamp_rcvd.saturating_add(duration.into())
2153 }
2154 }
2155 };
2156
2157 if let PreMessageMode::Pre {
2158 metadata: Some(metadata),
2159 ..
2160 } = &mime_parser.pre_message
2161 {
2162 param.apply_post_msg_metadata(metadata);
2163 };
2164
2165 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2168
2169 let row_id = context
2170 .sql
2171 .call_write(|conn| {
2172 let mut stmt = conn.prepare_cached(
2173 "
2174INSERT INTO msgs
2175 (
2176 rfc724_mid, pre_rfc724_mid, chat_id,
2177 from_id, to_id, timestamp, timestamp_sent,
2178 timestamp_rcvd, type, state, msgrmsg,
2179 txt, txt_normalized, subject, param, hidden,
2180 bytes, mime_headers, mime_compressed, mime_in_reply_to,
2181 mime_references, mime_modified, error, ephemeral_timer,
2182 ephemeral_timestamp, download_state, hop_info
2183 )
2184 VALUES (
2185 ?, ?, ?, ?, ?,
2186 ?, ?, ?, ?,
2187 ?, ?, ?, ?,
2188 ?, ?, ?, ?, ?, 1,
2189 ?, ?, ?, ?,
2190 ?, ?, ?, ?
2191 )",
2192 )?;
2193 let params = params![
2194 if let PreMessageMode::Pre {
2195 post_msg_rfc724_mid,
2196 ..
2197 } = &mime_parser.pre_message
2198 {
2199 post_msg_rfc724_mid
2200 } else {
2201 rfc724_mid_orig
2202 },
2203 if let PreMessageMode::Pre { .. } = &mime_parser.pre_message {
2204 rfc724_mid_orig
2205 } else {
2206 ""
2207 },
2208 if trash { DC_CHAT_ID_TRASH } else { chat_id },
2209 if trash { ContactId::UNDEFINED } else { from_id },
2210 if trash { ContactId::UNDEFINED } else { to_id },
2211 sort_timestamp,
2212 if trash { 0 } else { mime_parser.timestamp_sent },
2213 if trash { 0 } else { mime_parser.timestamp_rcvd },
2214 if trash {
2215 Viewtype::Unknown
2216 } else if let PreMessageMode::Pre { .. } = mime_parser.pre_message {
2217 Viewtype::Text
2218 } else {
2219 typ
2220 },
2221 if trash {
2222 MessageState::Undefined
2223 } else {
2224 state
2225 },
2226 if trash {
2227 MessengerMessage::No
2228 } else {
2229 is_dc_message
2230 },
2231 if trash || hidden { "" } else { msg },
2232 if trash || hidden {
2233 None
2234 } else {
2235 normalize_text(msg)
2236 },
2237 if trash || hidden { "" } else { &subject },
2238 if trash {
2239 "".to_string()
2240 } else {
2241 param.to_string()
2242 },
2243 !trash && hidden,
2244 if trash { 0 } else { part.bytes as isize },
2245 if save_mime_modified && !(trash || hidden) {
2246 mime_headers.clone()
2247 } else {
2248 Vec::new()
2249 },
2250 if trash { "" } else { mime_in_reply_to },
2251 if trash { "" } else { mime_references },
2252 !trash && save_mime_modified,
2253 if trash {
2254 ""
2255 } else {
2256 part.error.as_deref().unwrap_or_default()
2257 },
2258 if trash { 0 } else { ephemeral_timer.to_u32() },
2259 if trash { 0 } else { ephemeral_timestamp },
2260 if trash {
2261 DownloadState::Done
2262 } else if mime_parser.decryption_error.is_some() {
2263 DownloadState::Undecipherable
2264 } else if let PreMessageMode::Pre { .. } = mime_parser.pre_message {
2265 DownloadState::Available
2266 } else {
2267 DownloadState::Done
2268 },
2269 if trash { "" } else { &mime_parser.hop_info },
2270 ];
2271 let row_id = MsgId::new(stmt.insert(params)?.try_into()?);
2272 Ok(row_id)
2273 })
2274 .await?;
2275 ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2276 created_db_entries.push(row_id);
2277 }
2278
2279 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2281 if mime_parser.pre_message != PreMessageMode::Post
2282 && part.typ == Viewtype::Webxdc
2283 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2284 {
2285 let topic = iroh_topic_from_str(topic)?;
2286 insert_topic_stub(context, *msg_id, topic).await?;
2287 }
2288
2289 maybe_set_logging_xdc_inner(
2290 context,
2291 part.typ,
2292 chat_id,
2293 part.param.get(Param::Filename),
2294 *msg_id,
2295 )
2296 .await?;
2297 }
2298
2299 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2300 Some(addr) => context.is_self_addr(addr).await?,
2301 None => true,
2302 };
2303 if unarchive {
2304 chat_id.unarchive_if_not_muted(context, state).await?;
2305 }
2306
2307 info!(
2308 context,
2309 "Message has {icnt} parts and is assigned to chat #{chat_id}, timestamp={sort_timestamp}."
2310 );
2311
2312 if !chat_id.is_trash() && !hidden {
2313 let mut chat = Chat::load_from_db(context, chat_id).await?;
2314 let mut update_param = false;
2315
2316 if chat
2320 .param
2321 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2322 {
2323 let subject = mime_parser.get_subject().unwrap_or_default();
2326
2327 chat.param.set(Param::LastSubject, subject);
2328 update_param = true;
2329 }
2330
2331 if chat.is_unpromoted() {
2332 chat.param.remove(Param::Unpromoted);
2333 update_param = true;
2334 }
2335 if update_param {
2336 chat.update_param(context).await?;
2337 }
2338 }
2339
2340 Ok(ReceivedMsg {
2341 chat_id,
2342 state,
2343 hidden,
2344 sort_timestamp,
2345 msg_ids: created_db_entries,
2346 needs_delete_job: false,
2347 })
2348}
2349
2350async fn handle_edit_delete(
2353 context: &Context,
2354 mime_parser: &MimeMessage,
2355 from_id: ContactId,
2356) -> Result<()> {
2357 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2358 let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? else {
2359 warn!(
2360 context,
2361 "Edit message: rfc724_mid {rfc724_mid:?} not found."
2362 );
2363 return Ok(());
2364 };
2365 let Some(mut original_msg) =
2366 Message::load_from_db_optional(context, original_msg_id).await?
2367 else {
2368 warn!(context, "Edit message: Database entry does not exist.");
2369 return Ok(());
2370 };
2371 if original_msg.from_id != from_id {
2372 warn!(context, "Edit message: Bad sender.");
2373 return Ok(());
2374 }
2375 let Some(part) = mime_parser.parts.first() else {
2376 return Ok(());
2377 };
2378
2379 let edit_msg_showpadlock = part
2380 .param
2381 .get_bool(Param::GuaranteeE2ee)
2382 .unwrap_or_default();
2383 if !edit_msg_showpadlock && original_msg.get_showpadlock() {
2384 warn!(context, "Edit message: Not encrypted.");
2385 return Ok(());
2386 }
2387
2388 let new_text = part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2389 chat::save_text_edit_to_db(context, &mut original_msg, new_text).await?;
2390 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
2391 && let Some(part) = mime_parser.parts.first()
2392 {
2393 if part.param.get_bool(Param::GuaranteeE2ee) != Some(true) {
2396 warn!(context, "Delete message: Not encrypted.");
2397 return Ok(());
2398 }
2399
2400 let mut modified_chat_ids = BTreeSet::new();
2401 let mut msg_ids = Vec::new();
2402
2403 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2404 for rfc724_mid in rfc724_mid_vec {
2405 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2406 let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? else {
2407 warn!(context, "Delete message: {rfc724_mid:?} not found.");
2408 insert_tombstone(context, rfc724_mid).await?;
2410 continue;
2411 };
2412
2413 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2414 warn!(context, "Delete message: Database entry does not exist.");
2415 continue;
2416 };
2417 if msg.from_id != from_id {
2418 warn!(context, "Delete message: Bad sender.");
2419 continue;
2420 }
2421
2422 message::delete_msg_locally(context, &msg).await?;
2423 msg_ids.push(msg.id);
2424 modified_chat_ids.insert(msg.chat_id);
2425 }
2426 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2427 }
2428 Ok(())
2429}
2430
2431async fn handle_post_message(
2432 context: &Context,
2433 mime_parser: &MimeMessage,
2434 from_id: ContactId,
2435 state: MessageState,
2436) -> Result<()> {
2437 let PreMessageMode::Post = &mime_parser.pre_message else {
2438 return Ok(());
2439 };
2440 let rfc724_mid = mime_parser
2443 .get_rfc724_mid()
2444 .context("expected Post-Message to have a message id")?;
2445
2446 let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
2447 warn!(
2448 context,
2449 "handle_post_message: {rfc724_mid}: Database entry does not exist."
2450 );
2451 return Ok(());
2452 };
2453 let Some(original_msg) = Message::load_from_db_optional(context, msg_id).await? else {
2454 warn!(
2456 context,
2457 "handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
2458 );
2459 return Ok(());
2460 };
2461 let Some(part) = mime_parser.parts.first() else {
2462 return Ok(());
2463 };
2464
2465 if from_id != original_msg.from_id {
2468 warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
2469 return Ok(());
2470 }
2471 let post_msg_showpadlock = part
2472 .param
2473 .get_bool(Param::GuaranteeE2ee)
2474 .unwrap_or_default();
2475 if !post_msg_showpadlock && original_msg.get_showpadlock() {
2476 warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
2477 return Ok(());
2478 }
2479
2480 if !part.typ.has_file() {
2481 warn!(
2482 context,
2483 "handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
2484 );
2485 return Ok(());
2486 }
2487
2488 if part.typ == Viewtype::Webxdc
2489 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2490 {
2491 let topic = iroh_topic_from_str(topic)?;
2492 insert_topic_stub(context, msg_id, topic).await?;
2493 }
2494
2495 let mut new_params = original_msg.param.clone();
2496 new_params
2497 .merge_in_params(part.param.clone())
2498 .remove(Param::PostMessageFileBytes)
2499 .remove(Param::PostMessageViewtype);
2500 context
2503 .sql
2504 .execute(
2505 "
2506UPDATE msgs SET param=?, type=?, bytes=?, error=?, state=max(state,?), download_state=?
2507WHERE id=?
2508 ",
2509 (
2510 new_params.to_string(),
2511 part.typ,
2512 part.bytes as isize,
2513 part.error.as_deref().unwrap_or_default(),
2514 state,
2515 DownloadState::Done as u32,
2516 original_msg.id,
2517 ),
2518 )
2519 .await?;
2520
2521 if context.get_config_bool(Config::Bot).await? {
2522 if original_msg.hidden {
2523 } else if !original_msg.chat_id.is_trash() {
2525 let fresh = original_msg.state == MessageState::InFresh;
2526 let important = mime_parser.incoming && fresh;
2527
2528 original_msg
2529 .chat_id
2530 .emit_msg_event(context, original_msg.id, important);
2531 context.new_msgs_notify.notify_one();
2532 }
2533 } else {
2534 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2535 }
2536
2537 Ok(())
2538}
2539
2540async fn tweak_sort_timestamp(
2541 context: &Context,
2542 mime_parser: &mut MimeMessage,
2543 silent: bool,
2544 chat_id: ChatId,
2545 sort_timestamp: i64,
2546) -> Result<i64> {
2547 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2556 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2557 std::cmp::max(sort_timestamp, parent_timestamp)
2558 });
2559
2560 if silent {
2564 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2565 t
2566 } else {
2567 chat_id.created_timestamp(context).await?
2568 };
2569 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2570 }
2571 Ok(sort_timestamp)
2572}
2573
2574async fn save_locations(
2578 context: &Context,
2579 mime_parser: &MimeMessage,
2580 chat_id: ChatId,
2581 from_id: ContactId,
2582 msg_id: MsgId,
2583) -> Result<()> {
2584 if chat_id.is_special() {
2585 return Ok(());
2587 }
2588
2589 let mut send_event = false;
2590
2591 if let Some(message_kml) = &mime_parser.message_kml
2592 && let Some(newest_location_id) =
2593 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2594 {
2595 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2596 send_event = true;
2597 }
2598
2599 if let Some(location_kml) = &mime_parser.location_kml
2600 && let Some(addr) = &location_kml.addr
2601 {
2602 let contact = Contact::get_by_id(context, from_id).await?;
2603 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2604 if location::save(context, chat_id, from_id, &location_kml.locations, false)
2605 .await?
2606 .is_some()
2607 {
2608 send_event = true;
2609 }
2610 } else {
2611 warn!(
2612 context,
2613 "Address in location.kml {:?} is not the same as the sender address {:?}.",
2614 addr,
2615 contact.get_addr()
2616 );
2617 }
2618 }
2619 if send_event {
2620 context.emit_location_changed(Some(from_id)).await?;
2621 }
2622 Ok(())
2623}
2624
2625async fn lookup_chat_by_reply(
2626 context: &Context,
2627 mime_parser: &MimeMessage,
2628 parent: &Message,
2629) -> Result<Option<(ChatId, Blocked)>> {
2630 ensure_and_debug_assert!(
2635 mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2636 "Encrypted message has group ID {}",
2637 mime_parser.get_chat_group_id().unwrap_or_default(),
2638 );
2639
2640 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2642 return Ok(None);
2643 };
2644
2645 if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2648 return Ok(None);
2649 }
2650
2651 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2655 if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2656 return Ok(None);
2657 }
2658
2659 if parent_chat.is_encrypted(context).await? && !mime_parser.was_encrypted() {
2661 return Ok(None);
2662 }
2663
2664 info!(
2665 context,
2666 "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2667 );
2668 Ok(Some((parent_chat.id, parent_chat.blocked)))
2669}
2670
2671async fn lookup_or_create_adhoc_group(
2672 context: &Context,
2673 mime_parser: &MimeMessage,
2674 to_ids: &[Option<ContactId>],
2675 allow_creation: bool,
2676 create_blocked: Blocked,
2677) -> Result<Option<(ChatId, Blocked, bool)>> {
2678 if mime_parser.decryption_error.is_some() {
2679 warn!(
2680 context,
2681 "Not creating ad-hoc group for message that cannot be decrypted."
2682 );
2683 return Ok(None);
2684 }
2685
2686 let fingerprint = None;
2688 let find_key_contact_by_addr = false;
2689 let prevent_rename = should_prevent_rename(mime_parser);
2690 let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2691 context,
2692 &mime_parser.from,
2693 fingerprint,
2694 prevent_rename,
2695 find_key_contact_by_addr,
2696 )
2697 .await?
2698 .context("Cannot lookup address-contact by the From field")?;
2699
2700 let grpname = mime_parser
2701 .get_header(HeaderDef::ChatGroupName)
2702 .map(|s| s.to_string())
2703 .unwrap_or_else(|| {
2704 mime_parser
2705 .get_subject()
2706 .map(|s| remove_subject_prefix(&s))
2707 .unwrap_or_else(|| "👥📧".to_string())
2708 });
2709 let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2710 let mut contact_ids = BTreeSet::<ContactId>::from_iter(to_ids.iter().copied());
2711 contact_ids.insert(from_id);
2712 if mime_parser.was_encrypted() {
2713 contact_ids.remove(&ContactId::SELF);
2714 }
2715 let trans_fn = |t: &mut rusqlite::Transaction| {
2716 t.pragma_update(None, "query_only", "0")?;
2717 t.execute(
2718 "CREATE TEMP TABLE temp.contacts (
2719 id INTEGER PRIMARY KEY
2720 ) STRICT",
2721 (),
2722 )
2723 .context("CREATE TEMP TABLE temp.contacts")?;
2724 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2725 for &id in &contact_ids {
2726 stmt.execute((id,)).context("INSERT INTO temp.contacts")?;
2727 }
2728 let val = t
2729 .query_row(
2730 "SELECT c.id, c.blocked
2731 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2732 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2733 AND (SELECT COUNT(*) FROM chats_contacts
2734 WHERE chat_id=c.id
2735 AND add_timestamp >= remove_timestamp)=?
2736 AND (SELECT COUNT(*) FROM chats_contacts
2737 WHERE chat_id=c.id
2738 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2739 AND add_timestamp >= remove_timestamp)=0
2740 ORDER BY m.timestamp DESC",
2741 (&grpname, contact_ids.len()),
2742 |row| {
2743 let id: ChatId = row.get(0)?;
2744 let blocked: Blocked = row.get(1)?;
2745 Ok((id, blocked))
2746 },
2747 )
2748 .optional()
2749 .context("Select chat with matching name and members")?;
2750 t.execute("DROP TABLE temp.contacts", ())
2751 .context("DROP TABLE temp.contacts")?;
2752 Ok(val)
2753 };
2754 let query_only = true;
2755 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2756 info!(
2757 context,
2758 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2759 );
2760 return Ok(Some((chat_id, blocked, false)));
2761 }
2762 if !allow_creation {
2763 return Ok(None);
2764 }
2765 Ok(create_adhoc_group(
2766 context,
2767 mime_parser,
2768 create_blocked,
2769 from_id,
2770 &to_ids,
2771 &grpname,
2772 )
2773 .await
2774 .context("Could not create ad hoc group")?
2775 .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2776}
2777
2778async fn is_probably_private_reply(
2781 context: &Context,
2782 mime_parser: &MimeMessage,
2783 parent_chat_id: ChatId,
2784) -> Result<bool> {
2785 if mime_parser.get_chat_group_id().is_some() {
2787 return Ok(false);
2788 }
2789
2790 if mime_parser.recipients.len() != 1 {
2798 return Ok(false);
2799 }
2800
2801 if !mime_parser.has_chat_version() {
2802 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2803 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2804 return Ok(false);
2805 }
2806 }
2807
2808 Ok(true)
2809}
2810
2811async fn create_group(
2817 context: &Context,
2818 mime_parser: &mut MimeMessage,
2819 create_blocked: Blocked,
2820 from_id: ContactId,
2821 to_ids: &[Option<ContactId>],
2822 past_ids: &[Option<ContactId>],
2823 grpid: &str,
2824) -> Result<Option<(ChatId, Blocked)>> {
2825 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2826 let mut chat_id = None;
2827 let mut chat_id_blocked = Default::default();
2828
2829 if !mime_parser.is_mailinglist_message()
2830 && !grpid.is_empty()
2831 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2832 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2834 {
2835 let grpname = mime_parser
2837 .get_header(HeaderDef::ChatGroupName)
2838 .context("Chat-Group-Name vanished")?
2839 .trim();
2843 let new_chat_id = ChatId::create_multiuser_record(
2844 context,
2845 Chattype::Group,
2846 grpid,
2847 grpname,
2848 create_blocked,
2849 None,
2850 mime_parser.timestamp_sent,
2851 )
2852 .await
2853 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2854
2855 chat_id = Some(new_chat_id);
2856 chat_id_blocked = create_blocked;
2857
2858 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2860 let mut new_to_ids = to_ids.to_vec();
2861 if !new_to_ids.contains(&Some(from_id)) {
2862 new_to_ids.insert(0, Some(from_id));
2863 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2864 }
2865
2866 update_chats_contacts_timestamps(
2867 context,
2868 new_chat_id,
2869 None,
2870 &new_to_ids,
2871 past_ids,
2872 &chat_group_member_timestamps,
2873 )
2874 .await?;
2875 } else {
2876 let mut members = vec![ContactId::SELF];
2877 if !from_id.is_special() {
2878 members.push(from_id);
2879 }
2880 members.extend(to_ids_flat);
2881
2882 let timestamp = 0;
2888
2889 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2890 }
2891
2892 context.emit_event(EventType::ChatModified(new_chat_id));
2893 chatlist_events::emit_chatlist_changed(context);
2894 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2895 }
2896
2897 if let Some(chat_id) = chat_id {
2898 Ok(Some((chat_id, chat_id_blocked)))
2899 } else if mime_parser.decryption_error.is_some() {
2900 Ok(None)
2907 } else {
2908 info!(context, "Message belongs to unwanted group (TRASH).");
2911 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2912 }
2913}
2914
2915#[expect(clippy::arithmetic_side_effects)]
2916async fn update_chats_contacts_timestamps(
2917 context: &Context,
2918 chat_id: ChatId,
2919 ignored_id: Option<ContactId>,
2920 to_ids: &[Option<ContactId>],
2921 past_ids: &[Option<ContactId>],
2922 chat_group_member_timestamps: &[i64],
2923) -> Result<bool> {
2924 let expected_timestamps_count = to_ids.len() + past_ids.len();
2925
2926 if chat_group_member_timestamps.len() != expected_timestamps_count {
2927 warn!(
2928 context,
2929 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2930 chat_group_member_timestamps.len(),
2931 expected_timestamps_count
2932 );
2933 return Ok(false);
2934 }
2935
2936 let mut modified = false;
2937
2938 context
2939 .sql
2940 .transaction(|transaction| {
2941 let mut add_statement = transaction.prepare(
2942 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2943 VALUES (?1, ?2, ?3)
2944 ON CONFLICT (chat_id, contact_id)
2945 DO
2946 UPDATE SET add_timestamp=?3
2947 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2948 )?;
2949
2950 for (contact_id, ts) in iter::zip(
2951 to_ids.iter(),
2952 chat_group_member_timestamps.iter().take(to_ids.len()),
2953 ) {
2954 if let Some(contact_id) = contact_id
2955 && Some(*contact_id) != ignored_id
2956 {
2957 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2961 }
2962 }
2963
2964 let mut remove_statement = transaction.prepare(
2965 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2966 VALUES (?1, ?2, ?3)
2967 ON CONFLICT (chat_id, contact_id)
2968 DO
2969 UPDATE SET remove_timestamp=?3
2970 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2971 )?;
2972
2973 for (contact_id, ts) in iter::zip(
2974 past_ids.iter(),
2975 chat_group_member_timestamps.iter().skip(to_ids.len()),
2976 ) {
2977 if let Some(contact_id) = contact_id {
2978 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2982 }
2983 }
2984
2985 Ok(())
2986 })
2987 .await?;
2988
2989 Ok(modified)
2990}
2991
2992#[derive(Default)]
2996struct GroupChangesInfo {
2997 better_msg: Option<String>,
3000 added_removed_id: Option<ContactId>,
3002 silent: bool,
3004 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
3006}
3007
3008async fn apply_group_changes(
3015 context: &Context,
3016 mime_parser: &mut MimeMessage,
3017 chat: &mut Chat,
3018 from_id: ContactId,
3019 to_ids: &[Option<ContactId>],
3020 past_ids: &[Option<ContactId>],
3021 is_chat_created: bool,
3022) -> Result<GroupChangesInfo> {
3023 let from_is_key_contact = Contact::get_by_id(context, from_id).await?.is_key_contact();
3024 ensure!(from_is_key_contact || chat.grpid.is_empty());
3025 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
3026 ensure!(chat.typ == Chattype::Group);
3027 ensure!(!chat.id.is_special());
3028
3029 let mut send_event_chat_modified = false;
3030 let (mut removed_id, mut added_id) = (None, None);
3031 let mut better_msg = None;
3032 let mut silent = false;
3033 let chat_contacts =
3034 BTreeSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
3035 let is_from_in_chat =
3036 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
3037
3038 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3039 if !is_from_in_chat {
3040 better_msg = Some(String::new());
3041 } else if let Some(removed_fpr) =
3042 mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr)
3043 {
3044 removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3045 } else {
3046 removed_id =
3048 lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?;
3049 }
3050 if let Some(id) = removed_id {
3051 better_msg = if id == from_id {
3052 silent = true;
3053 Some(stock_str::msg_group_left_local(context, from_id).await)
3054 } else {
3055 Some(stock_str::msg_del_member_local(context, id, from_id).await)
3056 };
3057 } else {
3058 warn!(context, "Removed {removed_addr:?} has no contact id.")
3059 }
3060 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3061 if !is_from_in_chat {
3062 better_msg = Some(String::new());
3063 } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
3064 if !chat_contacts.contains(&from_id) {
3065 chat::add_to_chat_contacts_table(
3066 context,
3067 mime_parser.timestamp_sent,
3068 chat.id,
3069 &[from_id],
3070 )
3071 .await?;
3072 }
3073
3074 let fingerprint = key.public_key.dc_fingerprint().hex();
3081 if let Some(contact_id) =
3082 lookup_key_contact_by_fingerprint(context, &fingerprint).await?
3083 {
3084 added_id = Some(contact_id);
3085 better_msg =
3086 Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
3087 } else {
3088 warn!(context, "Added {added_addr:?} has no contact id.");
3089 }
3090 } else {
3091 warn!(context, "Added {added_addr:?} has no gossiped key.");
3092 }
3093 }
3094
3095 apply_chat_name_avatar_and_description_changes(
3096 context,
3097 mime_parser,
3098 from_id,
3099 is_from_in_chat,
3100 chat,
3101 &mut send_event_chat_modified,
3102 &mut better_msg,
3103 )
3104 .await?;
3105
3106 if is_from_in_chat {
3107 if from_is_key_contact != chat.grpid.is_empty()
3109 && chat.member_list_is_stale(context).await?
3110 {
3111 info!(context, "Member list is stale.");
3112 let mut new_members: BTreeSet<ContactId> =
3113 BTreeSet::from_iter(to_ids_flat.iter().copied());
3114 new_members.insert(ContactId::SELF);
3115 if !from_id.is_special() {
3116 new_members.insert(from_id);
3117 }
3118 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3119 new_members.remove(&ContactId::SELF);
3120 }
3121 context
3122 .sql
3123 .transaction(|transaction| {
3124 transaction.execute(
3126 "DELETE FROM chats_contacts
3127 WHERE chat_id=?",
3128 (chat.id,),
3129 )?;
3130
3131 let mut statement = transaction.prepare(
3133 "INSERT INTO chats_contacts (chat_id, contact_id)
3134 VALUES (?, ?)",
3135 )?;
3136 for contact_id in &new_members {
3137 statement.execute((chat.id, contact_id))?;
3138 }
3139
3140 Ok(())
3141 })
3142 .await?;
3143 send_event_chat_modified = true;
3144 } else if let Some(ref chat_group_member_timestamps) =
3145 mime_parser.chat_group_member_timestamps()
3146 {
3147 send_event_chat_modified |= update_chats_contacts_timestamps(
3148 context,
3149 chat.id,
3150 Some(from_id),
3151 to_ids,
3152 past_ids,
3153 chat_group_member_timestamps,
3154 )
3155 .await?;
3156 } else {
3157 let mut new_members: BTreeSet<ContactId>;
3158 let self_added =
3161 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3162 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
3163 && !chat_contacts.contains(&ContactId::SELF)
3164 } else {
3165 false
3166 };
3167 if self_added {
3168 new_members = BTreeSet::from_iter(to_ids_flat.iter().copied());
3169 new_members.insert(ContactId::SELF);
3170 if !from_id.is_special() && from_is_key_contact != chat.grpid.is_empty() {
3171 new_members.insert(from_id);
3172 }
3173 } else {
3174 new_members = chat_contacts.clone();
3175 }
3176
3177 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
3179 new_members.extend(to_ids_flat.iter());
3182 }
3183
3184 if let Some(added_id) = added_id {
3186 new_members.insert(added_id);
3187 }
3188
3189 if let Some(removed_id) = removed_id {
3191 new_members.remove(&removed_id);
3192 }
3193
3194 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3195 new_members.remove(&ContactId::SELF);
3196 }
3197
3198 if new_members != chat_contacts {
3199 chat::update_chat_contacts_table(
3200 context,
3201 mime_parser.timestamp_sent,
3202 chat.id,
3203 &new_members,
3204 )
3205 .await?;
3206 send_event_chat_modified = true;
3207 }
3208 }
3209
3210 chat.id
3211 .update_timestamp(
3212 context,
3213 Param::MemberListTimestamp,
3214 mime_parser.timestamp_sent,
3215 )
3216 .await?;
3217 }
3218
3219 let new_chat_contacts = BTreeSet::<ContactId>::from_iter(
3220 chat::get_chat_contacts(context, chat.id)
3221 .await?
3222 .iter()
3223 .copied(),
3224 );
3225
3226 let mut added_ids: BTreeSet<ContactId> = new_chat_contacts
3228 .difference(&chat_contacts)
3229 .copied()
3230 .collect();
3231 let mut removed_ids: BTreeSet<ContactId> = chat_contacts
3232 .difference(&new_chat_contacts)
3233 .copied()
3234 .collect();
3235 let id_was_already_added = if let Some(added_id) = added_id {
3236 !added_ids.remove(&added_id)
3237 } else {
3238 false
3239 };
3240 if let Some(removed_id) = removed_id {
3241 removed_ids.remove(&removed_id);
3242 }
3243
3244 let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
3245 && new_chat_contacts.contains(&ContactId::SELF)
3246 {
3247 Vec::new()
3248 } else {
3249 group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
3250 };
3251
3252 if id_was_already_added && group_changes_msgs.is_empty() && !is_chat_created {
3253 info!(context, "No-op 'Member added' message (TRASH)");
3254 better_msg = Some(String::new());
3255 }
3256
3257 if send_event_chat_modified {
3258 context.emit_event(EventType::ChatModified(chat.id));
3259 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3260 }
3261 Ok(GroupChangesInfo {
3262 better_msg,
3263 added_removed_id: if added_id.is_some() {
3264 added_id
3265 } else {
3266 removed_id
3267 },
3268 silent,
3269 extra_msgs: group_changes_msgs,
3270 })
3271}
3272
3273async fn apply_chat_name_avatar_and_description_changes(
3278 context: &Context,
3279 mime_parser: &MimeMessage,
3280 from_id: ContactId,
3281 is_from_in_chat: bool,
3282 chat: &mut Chat,
3283 send_event_chat_modified: &mut bool,
3284 better_msg: &mut Option<String>,
3285) -> Result<()> {
3286 let group_name_timestamp = mime_parser
3289 .get_header(HeaderDef::ChatGroupNameTimestamp)
3290 .and_then(|s| s.parse::<i64>().ok());
3291
3292 if let Some(old_name) = mime_parser
3293 .get_header(HeaderDef::ChatGroupNameChanged)
3294 .map(|s| s.trim())
3295 .or(match group_name_timestamp {
3296 Some(0) => None,
3297 Some(_) => Some(chat.name.as_str()),
3298 None => None,
3299 })
3300 && let Some(grpname) = mime_parser
3301 .get_header(HeaderDef::ChatGroupName)
3302 .map(|grpname| grpname.trim())
3303 .filter(|grpname| grpname.len() < 200)
3304 {
3305 let grpname = &sanitize_single_line(grpname);
3306
3307 let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3308 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3309 if is_from_in_chat
3311 && (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3312 && chat
3313 .id
3314 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3315 .await?
3316 && grpname != &chat.name
3317 {
3318 info!(context, "Updating grpname for chat {}.", chat.id);
3319 context
3320 .sql
3321 .execute(
3322 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3323 (grpname, normalize_text(grpname), chat.id),
3324 )
3325 .await?;
3326 *send_event_chat_modified = true;
3327 }
3328 if mime_parser
3329 .get_header(HeaderDef::ChatGroupNameChanged)
3330 .is_some()
3331 {
3332 if is_from_in_chat {
3333 let old_name = &sanitize_single_line(old_name);
3334 better_msg.get_or_insert(
3335 if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
3336 stock_str::msg_broadcast_name_changed(context, old_name, grpname)
3337 } else {
3338 stock_str::msg_grp_name(context, old_name, grpname, from_id).await
3339 },
3340 );
3341 } else {
3342 *better_msg = Some(String::new());
3344 }
3345 }
3346 }
3347
3348 if let Some(new_description) = mime_parser
3351 .get_header(HeaderDef::ChatGroupDescription)
3352 .map(|d| d.trim())
3353 {
3354 let new_description = sanitize_bidi_characters(new_description.trim());
3355 let old_description = chat::get_chat_description(context, chat.id).await?;
3356
3357 let old_timestamp = chat
3358 .param
3359 .get_i64(Param::GroupDescriptionTimestamp)
3360 .unwrap_or(0);
3361 let timestamp_in_header = mime_parser
3362 .get_header(HeaderDef::ChatGroupDescriptionTimestamp)
3363 .and_then(|s| s.parse::<i64>().ok());
3364
3365 let new_timestamp = timestamp_in_header.unwrap_or(mime_parser.timestamp_sent);
3366 if is_from_in_chat
3368 && (old_timestamp, &old_description) < (new_timestamp, &new_description)
3369 && chat
3370 .id
3371 .update_timestamp(context, Param::GroupDescriptionTimestamp, new_timestamp)
3372 .await?
3373 && new_description != old_description
3374 {
3375 info!(context, "Updating description for chat {}.", chat.id);
3376 context
3377 .sql
3378 .execute(
3379 "INSERT OR REPLACE INTO chats_descriptions(chat_id, description) VALUES(?, ?)",
3380 (chat.id, &new_description),
3381 )
3382 .await?;
3383 *send_event_chat_modified = true;
3384 }
3385 if mime_parser
3386 .get_header(HeaderDef::ChatGroupDescriptionChanged)
3387 .is_some()
3388 {
3389 if is_from_in_chat {
3390 better_msg
3391 .get_or_insert(stock_str::msg_chat_description_changed(context, from_id).await);
3392 } else {
3393 *better_msg = Some(String::new());
3395 }
3396 }
3397 }
3398
3399 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3402 && value == "group-avatar-changed"
3403 && let Some(avatar_action) = &mime_parser.group_avatar
3404 {
3405 if is_from_in_chat {
3406 better_msg.get_or_insert(
3409 if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
3410 stock_str::msg_broadcast_img_changed(context)
3411 } else {
3412 match avatar_action {
3413 AvatarAction::Delete => {
3414 stock_str::msg_grp_img_deleted(context, from_id).await
3415 }
3416 AvatarAction::Change(_) => {
3417 stock_str::msg_grp_img_changed(context, from_id).await
3418 }
3419 }
3420 },
3421 );
3422 } else {
3423 *better_msg = Some(String::new());
3425 }
3426 }
3427
3428 if let Some(avatar_action) = &mime_parser.group_avatar
3429 && is_from_in_chat
3430 && chat
3431 .param
3432 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3433 {
3434 info!(context, "Group-avatar change for {}.", chat.id);
3435 match avatar_action {
3436 AvatarAction::Change(profile_image) => {
3437 chat.param.set(Param::ProfileImage, profile_image);
3438 }
3439 AvatarAction::Delete => {
3440 chat.param.remove(Param::ProfileImage);
3441 }
3442 };
3443 chat.update_param(context).await?;
3444 *send_event_chat_modified = true;
3445 }
3446
3447 Ok(())
3448}
3449
3450#[expect(clippy::arithmetic_side_effects)]
3452async fn group_changes_msgs(
3453 context: &Context,
3454 added_ids: &BTreeSet<ContactId>,
3455 removed_ids: &BTreeSet<ContactId>,
3456 chat_id: ChatId,
3457) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3458 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3459 if !added_ids.is_empty() {
3460 warn!(
3461 context,
3462 "Implicit addition of {added_ids:?} to chat {chat_id}."
3463 );
3464 }
3465 if !removed_ids.is_empty() {
3466 warn!(
3467 context,
3468 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3469 );
3470 }
3471 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3472 for contact_id in added_ids {
3473 group_changes_msgs.push((
3474 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3475 SystemMessage::MemberAddedToGroup,
3476 Some(*contact_id),
3477 ));
3478 }
3479 for contact_id in removed_ids {
3480 group_changes_msgs.push((
3481 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3482 SystemMessage::MemberRemovedFromGroup,
3483 Some(*contact_id),
3484 ));
3485 }
3486
3487 Ok(group_changes_msgs)
3488}
3489
3490static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3491
3492fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3493 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3494 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3495 None => list_id_header
3496 .trim()
3497 .trim_start_matches('<')
3498 .trim_end_matches('>'),
3499 }
3500 .to_string())
3501}
3502
3503async fn create_or_lookup_mailinglist_or_broadcast(
3518 context: &Context,
3519 allow_creation: bool,
3520 create_blocked: Blocked,
3521 list_id_header: &str,
3522 from_id: ContactId,
3523 mime_parser: &MimeMessage,
3524) -> Result<Option<(ChatId, Blocked, bool)>> {
3525 let listid = mailinglist_header_listid(list_id_header)?;
3526
3527 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3528 return Ok(Some((chat_id, blocked, false)));
3529 }
3530
3531 let chattype = if mime_parser.was_encrypted() {
3532 Chattype::InBroadcast
3533 } else {
3534 Chattype::Mailinglist
3535 };
3536
3537 let name = if chattype == Chattype::InBroadcast {
3538 mime_parser
3539 .get_header(HeaderDef::ChatGroupName)
3540 .unwrap_or("Broadcast Channel")
3541 } else {
3542 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3543 };
3544
3545 if allow_creation {
3546 let param = mime_parser.list_post.as_ref().map(|list_post| {
3548 let mut p = Params::new();
3549 p.set(Param::ListPost, list_post);
3550 p.to_string()
3551 });
3552
3553 let chat_id = ChatId::create_multiuser_record(
3554 context,
3555 chattype,
3556 &listid,
3557 name,
3558 if chattype == Chattype::InBroadcast {
3559 Blocked::Not
3563 } else {
3564 create_blocked
3565 },
3566 param,
3567 mime_parser.timestamp_sent,
3568 )
3569 .await
3570 .with_context(|| format!("failed to create mailinglist '{name}' for grpid={listid}",))?;
3571
3572 if chattype == Chattype::InBroadcast {
3573 chat::add_to_chat_contacts_table(
3574 context,
3575 mime_parser.timestamp_sent,
3576 chat_id,
3577 &[from_id],
3578 )
3579 .await?;
3580 }
3581
3582 context.emit_event(EventType::ChatModified(chat_id));
3583 chatlist_events::emit_chatlist_changed(context);
3584 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3585
3586 Ok(Some((chat_id, create_blocked, true)))
3587 } else {
3588 info!(context, "Creating list forbidden by caller.");
3589 Ok(None)
3590 }
3591}
3592
3593fn compute_mailinglist_name(
3594 list_id_header: &str,
3595 listid: &str,
3596 mime_parser: &MimeMessage,
3597) -> String {
3598 let mut name = match LIST_ID_REGEX
3599 .captures(list_id_header)
3600 .and_then(|caps| caps.get(1))
3601 {
3602 Some(cap) => cap.as_str().trim().to_string(),
3603 None => "".to_string(),
3604 };
3605
3606 if listid.ends_with(".list-id.mcsv.net")
3610 && let Some(display_name) = &mime_parser.from.display_name
3611 {
3612 name.clone_from(display_name);
3613 }
3614
3615 let subject = mime_parser.get_subject().unwrap_or_default();
3619 static SUBJECT: LazyLock<Regex> =
3620 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3622 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3623 }
3624
3625 if name.is_empty()
3632 && (mime_parser.from.addr.contains("noreply")
3633 || mime_parser.from.addr.contains("no-reply")
3634 || mime_parser.from.addr.starts_with("notifications@")
3635 || mime_parser.from.addr.starts_with("newsletter@")
3636 || listid.ends_with(".xt.local"))
3637 && let Some(display_name) = &mime_parser.from.display_name
3638 {
3639 name.clone_from(display_name);
3640 }
3641
3642 if name.is_empty() {
3645 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3647 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3648 if let Some(cap) = PREFIX_32_CHARS_HEX
3649 .captures(listid)
3650 .and_then(|caps| caps.get(2))
3651 {
3652 name = cap.as_str().to_string();
3653 } else {
3654 name = listid.to_string();
3655 }
3656 }
3657
3658 sanitize_single_line(&name)
3659}
3660
3661async fn apply_mailinglist_changes(
3665 context: &Context,
3666 mime_parser: &MimeMessage,
3667 chat_id: ChatId,
3668) -> Result<()> {
3669 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3670 return Ok(());
3671 };
3672
3673 let mut chat = Chat::load_from_db(context, chat_id).await?;
3674 if chat.typ != Chattype::Mailinglist {
3675 return Ok(());
3676 }
3677 let listid = &chat.grpid;
3678
3679 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3680 if chat.name != new_name
3681 && chat_id
3682 .update_timestamp(
3683 context,
3684 Param::GroupNameTimestamp,
3685 mime_parser.timestamp_sent,
3686 )
3687 .await?
3688 {
3689 info!(context, "Updating listname for chat {chat_id}.");
3690 context
3691 .sql
3692 .execute(
3693 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3694 (&new_name, normalize_text(&new_name), chat_id),
3695 )
3696 .await?;
3697 context.emit_event(EventType::ChatModified(chat_id));
3698 }
3699
3700 let Some(list_post) = &mime_parser.list_post else {
3701 return Ok(());
3702 };
3703
3704 let list_post = match ContactAddress::new(list_post) {
3705 Ok(list_post) => list_post,
3706 Err(err) => {
3707 warn!(context, "Invalid List-Post: {:#}.", err);
3708 return Ok(());
3709 }
3710 };
3711 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3712 let mut contact = Contact::get_by_id(context, contact_id).await?;
3713 if contact.param.get(Param::ListId) != Some(listid) {
3714 contact.param.set(Param::ListId, listid);
3715 contact.update_param(context).await?;
3716 }
3717
3718 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3719 if list_post.as_ref() != old_list_post {
3720 chat.param.remove(Param::ListPost);
3723 chat.update_param(context).await?;
3724 }
3725 } else {
3726 chat.param.set(Param::ListPost, list_post);
3727 chat.update_param(context).await?;
3728 }
3729
3730 Ok(())
3731}
3732
3733async fn apply_out_broadcast_changes(
3734 context: &Context,
3735 mime_parser: &MimeMessage,
3736 chat: &mut Chat,
3737 from_id: ContactId,
3738) -> Result<GroupChangesInfo> {
3739 ensure!(chat.typ == Chattype::OutBroadcast);
3740
3741 let mut send_event_chat_modified = false;
3742 let mut better_msg = None;
3743 let mut added_removed_id: Option<ContactId> = None;
3744
3745 if from_id == ContactId::SELF {
3746 let is_from_in_chat = true;
3747 apply_chat_name_avatar_and_description_changes(
3748 context,
3749 mime_parser,
3750 from_id,
3751 is_from_in_chat,
3752 chat,
3753 &mut send_event_chat_modified,
3754 &mut better_msg,
3755 )
3756 .await?;
3757 }
3758
3759 if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3760 if from_id == ContactId::SELF {
3761 let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3762 if let Some(added_id) = added_id {
3763 if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3764 info!(context, "No-op broadcast addition (TRASH)");
3765 better_msg.get_or_insert("".to_string());
3766 } else {
3767 chat::add_to_chat_contacts_table(
3768 context,
3769 mime_parser.timestamp_sent,
3770 chat.id,
3771 &[added_id],
3772 )
3773 .await?;
3774 let msg =
3775 stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3776 .await;
3777 better_msg.get_or_insert(msg);
3778 added_removed_id = Some(added_id);
3779 send_event_chat_modified = true;
3780 }
3781 } else {
3782 warn!(context, "Failed to find contact with fpr {added_fpr}");
3783 }
3784 }
3785 } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3786 send_event_chat_modified = true;
3787 let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3788 if removed_id == Some(from_id) {
3789 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3792 info!(context, "Broadcast leave message (TRASH)");
3793 better_msg = Some("".to_string());
3794 } else if from_id == ContactId::SELF
3795 && let Some(removed_id) = removed_id
3796 {
3797 if chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3798 .await?
3799 {
3800 better_msg.get_or_insert(
3801 stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3802 );
3803 added_removed_id = Some(removed_id);
3804 } else {
3805 info!(context, "No-op broadcast member removal message (TRASH).");
3806 better_msg = Some("".to_string());
3807 }
3808 }
3809 }
3810
3811 if send_event_chat_modified {
3812 context.emit_event(EventType::ChatModified(chat.id));
3813 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3814 }
3815 Ok(GroupChangesInfo {
3816 better_msg,
3817 added_removed_id,
3818 silent: false,
3819 extra_msgs: vec![],
3820 })
3821}
3822
3823async fn apply_in_broadcast_changes(
3824 context: &Context,
3825 mime_parser: &MimeMessage,
3826 chat: &mut Chat,
3827 from_id: ContactId,
3828) -> Result<GroupChangesInfo> {
3829 ensure!(chat.typ == Chattype::InBroadcast);
3830
3831 if let Some(part) = mime_parser.parts.first()
3832 && let Some(error) = &part.error
3833 {
3834 warn!(
3835 context,
3836 "Not applying broadcast changes from message with error: {error}"
3837 );
3838 return Ok(GroupChangesInfo::default());
3839 }
3840
3841 let mut send_event_chat_modified = false;
3842 let mut better_msg = None;
3843
3844 let is_from_in_chat = is_contact_in_chat(context, chat.id, from_id).await?;
3845 apply_chat_name_avatar_and_description_changes(
3846 context,
3847 mime_parser,
3848 from_id,
3849 is_from_in_chat,
3850 chat,
3851 &mut send_event_chat_modified,
3852 &mut better_msg,
3853 )
3854 .await?;
3855
3856 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3857 && context.is_self_addr(added_addr).await?
3858 {
3859 let msg = if chat.is_self_in_chat(context).await? {
3860 info!(context, "No-op broadcast 'Member added' message (TRASH)");
3864 "".to_string()
3865 } else {
3866 stock_str::msg_you_joined_broadcast(context)
3867 };
3868
3869 better_msg.get_or_insert(msg);
3870 send_event_chat_modified = true;
3871 }
3872
3873 if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3874 if removed_fpr != self_fingerprint(context).await? {
3876 logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3877 return Ok(GroupChangesInfo::default());
3878 }
3879 chat::delete_broadcast_secret(context, chat.id).await?;
3880
3881 let removed =
3882 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3883 .await?;
3884 if !removed {
3885 info!(context, "No-op broadcast SELF-removal message (TRASH).");
3886 better_msg = Some("".to_string());
3887 } else if from_id == ContactId::SELF {
3888 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
3889 } else {
3890 better_msg.get_or_insert(
3891 stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3892 );
3893 }
3894 send_event_chat_modified |= removed;
3895 } else if !chat.is_self_in_chat(context).await? {
3896 chat::add_to_chat_contacts_table(
3897 context,
3898 mime_parser.timestamp_sent,
3899 chat.id,
3900 &[ContactId::SELF],
3901 )
3902 .await?;
3903 send_event_chat_modified = true;
3904 }
3905
3906 if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3907 if validate_broadcast_secret(secret) {
3908 save_broadcast_secret(context, chat.id, secret).await?;
3909 } else {
3910 warn!(context, "Not saving invalid broadcast secret");
3911 }
3912 }
3913
3914 if send_event_chat_modified {
3915 context.emit_event(EventType::ChatModified(chat.id));
3916 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3917 }
3918 Ok(GroupChangesInfo {
3919 better_msg,
3920 added_removed_id: None,
3921 silent: false,
3922 extra_msgs: vec![],
3923 })
3924}
3925
3926async fn create_adhoc_group(
3928 context: &Context,
3929 mime_parser: &MimeMessage,
3930 create_blocked: Blocked,
3931 from_id: ContactId,
3932 to_ids: &[ContactId],
3933 grpname: &str,
3934) -> Result<Option<(ChatId, Blocked)>> {
3935 let mut member_ids: Vec<ContactId> = to_ids
3936 .iter()
3937 .copied()
3938 .filter(|&id| id != ContactId::SELF)
3939 .collect();
3940 if from_id != ContactId::SELF && !member_ids.contains(&from_id) {
3941 member_ids.push(from_id);
3942 }
3943 if !mime_parser.was_encrypted() {
3944 member_ids.push(ContactId::SELF);
3945 }
3946
3947 if mime_parser.is_mailinglist_message() {
3948 return Ok(None);
3949 }
3950 if mime_parser
3951 .get_header(HeaderDef::ChatGroupMemberRemoved)
3952 .is_some()
3953 {
3954 info!(
3955 context,
3956 "Message removes member from unknown ad-hoc group (TRASH)."
3957 );
3958 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3959 }
3960
3961 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3962 context,
3963 Chattype::Group,
3964 "", grpname,
3966 create_blocked,
3967 None,
3968 mime_parser.timestamp_sent,
3969 )
3970 .await?;
3971
3972 info!(
3973 context,
3974 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3975 );
3976 chat::add_to_chat_contacts_table(
3977 context,
3978 mime_parser.timestamp_sent,
3979 new_chat_id,
3980 &member_ids,
3981 )
3982 .await?;
3983
3984 context.emit_event(EventType::ChatModified(new_chat_id));
3985 chatlist_events::emit_chatlist_changed(context);
3986 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3987
3988 Ok(Some((new_chat_id, create_blocked)))
3989}
3990
3991#[derive(Debug, PartialEq, Eq)]
3992enum VerifiedEncryption {
3993 Verified,
3994 NotVerified(String), }
3996
3997async fn has_verified_encryption(
4001 context: &Context,
4002 mimeparser: &MimeMessage,
4003 from_id: ContactId,
4004) -> Result<VerifiedEncryption> {
4005 use VerifiedEncryption::*;
4006
4007 if !mimeparser.was_encrypted() {
4008 return Ok(NotVerified("This message is not encrypted".to_string()));
4009 };
4010
4011 if from_id == ContactId::SELF {
4012 return Ok(Verified);
4013 }
4014
4015 let from_contact = Contact::get_by_id(context, from_id).await?;
4016
4017 let Some(fingerprint) = from_contact.fingerprint() else {
4018 return Ok(NotVerified(
4019 "The message was sent without encryption".to_string(),
4020 ));
4021 };
4022
4023 if from_contact.get_verifier_id(context).await?.is_none() {
4024 return Ok(NotVerified(
4025 "The message was sent by non-verified contact".to_string(),
4026 ));
4027 }
4028
4029 let signed_with_verified_key = mimeparser
4030 .signature
4031 .as_ref()
4032 .is_some_and(|(signature, _)| *signature == fingerprint);
4033 if signed_with_verified_key {
4034 Ok(Verified)
4035 } else {
4036 Ok(NotVerified(
4037 "The message was sent with non-verified encryption".to_string(),
4038 ))
4039 }
4040}
4041
4042async fn mark_recipients_as_verified(
4043 context: &Context,
4044 from_id: ContactId,
4045 mimeparser: &MimeMessage,
4046) -> Result<()> {
4047 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
4048
4049 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
4053
4054 for gossiped_key in mimeparser
4055 .gossiped_keys
4056 .values()
4057 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
4058 {
4059 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
4060 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
4061 continue;
4062 };
4063
4064 if to_id == ContactId::SELF || to_id == from_id {
4065 continue;
4066 }
4067
4068 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
4069 }
4070
4071 Ok(())
4072}
4073
4074async fn get_previous_message(
4078 context: &Context,
4079 mime_parser: &MimeMessage,
4080) -> Result<Option<Message>> {
4081 if let Some(field) = mime_parser.get_header(HeaderDef::References)
4082 && let Some(rfc724mid) = parse_message_ids(field).last()
4083 && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
4084 {
4085 return Message::load_from_db_optional(context, msg_id).await;
4086 }
4087 Ok(None)
4088}
4089
4090async fn get_parent_message(
4095 context: &Context,
4096 references: Option<&str>,
4097 in_reply_to: Option<&str>,
4098) -> Result<Option<Message>> {
4099 let mut mids = Vec::new();
4100 if let Some(field) = in_reply_to {
4101 mids = parse_message_ids(field);
4102 }
4103 if let Some(field) = references {
4104 mids.append(&mut parse_message_ids(field));
4105 }
4106 message::get_by_rfc724_mids(context, &mids).await
4107}
4108
4109pub(crate) async fn get_prefetch_parent_message(
4110 context: &Context,
4111 headers: &[mailparse::MailHeader<'_>],
4112) -> Result<Option<Message>> {
4113 get_parent_message(
4114 context,
4115 headers.get_header_value(HeaderDef::References).as_deref(),
4116 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
4117 )
4118 .await
4119}
4120
4121async fn add_or_lookup_contacts_by_address_list(
4123 context: &Context,
4124 address_list: &[SingleInfo],
4125 origin: Origin,
4126) -> Result<Vec<Option<ContactId>>> {
4127 let mut contact_ids = Vec::new();
4128 for info in address_list {
4129 let addr = &info.addr;
4130 if !may_be_valid_addr(addr) {
4131 contact_ids.push(None);
4132 continue;
4133 }
4134 let display_name = info.display_name.as_deref();
4135 if let Ok(addr) = ContactAddress::new(addr) {
4136 let (contact_id, _) =
4137 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
4138 .await?;
4139 contact_ids.push(Some(contact_id));
4140 } else {
4141 warn!(context, "Contact with address {:?} cannot exist.", addr);
4142 contact_ids.push(None);
4143 }
4144 }
4145
4146 Ok(contact_ids)
4147}
4148
4149async fn add_or_lookup_key_contacts(
4151 context: &Context,
4152 address_list: &[SingleInfo],
4153 gossiped_keys: &BTreeMap<String, GossipedKey>,
4154 fingerprints: &[Fingerprint],
4155 origin: Origin,
4156) -> Result<Vec<Option<ContactId>>> {
4157 let mut contact_ids = Vec::new();
4158 let mut fingerprint_iter = fingerprints.iter();
4159 for info in address_list {
4160 let fp = fingerprint_iter.next();
4161 let addr = &info.addr;
4162 if !may_be_valid_addr(addr) {
4163 contact_ids.push(None);
4164 continue;
4165 }
4166 let fingerprint: String = if let Some(fp) = fp {
4167 fp.hex()
4169 } else if let Some(key) = gossiped_keys.get(addr) {
4170 key.public_key.dc_fingerprint().hex()
4171 } else if context.is_self_addr(addr).await? {
4172 contact_ids.push(Some(ContactId::SELF));
4173 continue;
4174 } else {
4175 contact_ids.push(None);
4176 continue;
4177 };
4178 let display_name = info.display_name.as_deref();
4179 if let Ok(addr) = ContactAddress::new(addr) {
4180 let (contact_id, _) = Contact::add_or_lookup_ex(
4181 context,
4182 display_name.unwrap_or_default(),
4183 &addr,
4184 &fingerprint,
4185 origin,
4186 )
4187 .await?;
4188 contact_ids.push(Some(contact_id));
4189 } else {
4190 warn!(context, "Contact with address {:?} cannot exist.", addr);
4191 contact_ids.push(None);
4192 }
4193 }
4194
4195 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
4196 Ok(contact_ids)
4197}
4198
4199async fn lookup_key_contact_by_address(
4204 context: &Context,
4205 addr: &str,
4206 chat_id: Option<ChatId>,
4207) -> Result<Option<ContactId>> {
4208 if context.is_self_addr(addr).await? {
4209 if chat_id.is_none() {
4210 return Ok(Some(ContactId::SELF));
4211 }
4212 let is_self_in_chat = context
4213 .sql
4214 .exists(
4215 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
4216 (chat_id,),
4217 )
4218 .await?;
4219 if is_self_in_chat {
4220 return Ok(Some(ContactId::SELF));
4221 }
4222 }
4223 let contact_id: Option<ContactId> = match chat_id {
4224 Some(chat_id) => {
4225 context
4226 .sql
4227 .query_row_optional(
4228 "SELECT id FROM contacts
4229 WHERE contacts.addr=?
4230 AND EXISTS (SELECT 1 FROM chats_contacts
4231 WHERE contact_id=contacts.id
4232 AND chat_id=?)
4233 AND fingerprint<>'' -- Should always be true
4234 ",
4235 (addr, chat_id),
4236 |row| {
4237 let contact_id: ContactId = row.get(0)?;
4238 Ok(contact_id)
4239 },
4240 )
4241 .await?
4242 }
4243 None => {
4244 context
4245 .sql
4246 .query_row_optional(
4247 "SELECT id FROM contacts
4248 WHERE addr=?
4249 AND fingerprint<>''
4250 ORDER BY
4251 (
4252 SELECT COUNT(*) FROM chats c
4253 INNER JOIN chats_contacts cc
4254 ON c.id=cc.chat_id
4255 WHERE c.type=?
4256 AND c.id>?
4257 AND c.blocked=?
4258 AND cc.contact_id=contacts.id
4259 ) DESC,
4260 last_seen DESC, id DESC
4261 ",
4262 (
4263 addr,
4264 Chattype::Single,
4265 constants::DC_CHAT_ID_LAST_SPECIAL,
4266 Blocked::Not,
4267 ),
4268 |row| {
4269 let contact_id: ContactId = row.get(0)?;
4270 Ok(contact_id)
4271 },
4272 )
4273 .await?
4274 }
4275 };
4276 Ok(contact_id)
4277}
4278
4279async fn lookup_key_contact_by_fingerprint(
4280 context: &Context,
4281 fingerprint: &str,
4282) -> Result<Option<ContactId>> {
4283 logged_debug_assert!(
4284 context,
4285 !fingerprint.is_empty(),
4286 "lookup_key_contact_by_fingerprint: fingerprint is empty."
4287 );
4288 if fingerprint.is_empty() {
4289 return Ok(None);
4291 }
4292 if let Some(contact_id) = context
4293 .sql
4294 .query_row_optional(
4295 "SELECT id FROM contacts
4296 WHERE fingerprint=? AND fingerprint!=''",
4297 (fingerprint,),
4298 |row| {
4299 let contact_id: ContactId = row.get(0)?;
4300 Ok(contact_id)
4301 },
4302 )
4303 .await?
4304 {
4305 Ok(Some(contact_id))
4306 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4307 if self_fp == fingerprint {
4308 Ok(Some(ContactId::SELF))
4309 } else {
4310 Ok(None)
4311 }
4312 } else {
4313 Ok(None)
4314 }
4315}
4316
4317async fn lookup_key_contacts_fallback_to_chat(
4333 context: &Context,
4334 address_list: &[SingleInfo],
4335 fingerprints: &[Fingerprint],
4336 chat_id: Option<ChatId>,
4337) -> Result<Vec<Option<ContactId>>> {
4338 let mut contact_ids = Vec::new();
4339 let mut fingerprint_iter = fingerprints.iter();
4340 for info in address_list {
4341 let fp = fingerprint_iter.next();
4342 let addr = &info.addr;
4343 if !may_be_valid_addr(addr) {
4344 contact_ids.push(None);
4345 continue;
4346 }
4347
4348 if let Some(fp) = fp {
4349 let display_name = info.display_name.as_deref();
4351 let fingerprint: String = fp.hex();
4352
4353 if let Ok(addr) = ContactAddress::new(addr) {
4354 let (contact_id, _) = Contact::add_or_lookup_ex(
4355 context,
4356 display_name.unwrap_or_default(),
4357 &addr,
4358 &fingerprint,
4359 Origin::Hidden,
4360 )
4361 .await?;
4362 contact_ids.push(Some(contact_id));
4363 } else {
4364 warn!(context, "Contact with address {:?} cannot exist.", addr);
4365 contact_ids.push(None);
4366 }
4367 } else {
4368 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4369 contact_ids.push(contact_id);
4370 }
4371 }
4372 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4373 Ok(contact_ids)
4374}
4375
4376fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4379 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4380 || mime_parser.get_header(HeaderDef::Sender).is_some()
4381}
4382
4383#[cfg(test)]
4384mod receive_imf_tests;