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 || !mime_parser.mdn_reports.is_empty() || chat_id_blocked == Blocked::Yes
1856 {
1858 MessageState::InSeen
1859 } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL || group_changes.silent {
1860 MessageState::InNoticed
1861 } else {
1862 MessageState::InFresh
1863 };
1864 let in_fresh = state == MessageState::InFresh;
1865
1866 let sort_timestamp = chat_id
1867 .calc_sort_timestamp(context, mime_parser.timestamp_sent, sort_to_bottom)
1868 .await?;
1869
1870 if !chat_id.is_special()
1876 && !mime_parser.parts.is_empty()
1877 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1878 {
1879 let chat_contacts =
1880 BTreeSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1881 let is_from_in_chat =
1882 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1883
1884 info!(
1885 context,
1886 "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1887 );
1888 if !is_from_in_chat {
1889 warn!(
1890 context,
1891 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1892 );
1893 } else if is_dc_message == MessengerMessage::Yes
1894 && get_previous_message(context, mime_parser)
1895 .await?
1896 .map(|p| p.ephemeral_timer)
1897 == Some(ephemeral_timer)
1898 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1899 {
1900 warn!(
1907 context,
1908 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1909 );
1910 } else if chat_id
1911 .update_timestamp(
1912 context,
1913 Param::EphemeralSettingsTimestamp,
1914 mime_parser.timestamp_sent,
1915 )
1916 .await?
1917 {
1918 if let Err(err) = chat_id
1919 .inner_set_ephemeral_timer(context, ephemeral_timer)
1920 .await
1921 {
1922 warn!(
1923 context,
1924 "Failed to modify timer for chat {chat_id}: {err:#}."
1925 );
1926 } else {
1927 info!(
1928 context,
1929 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1930 );
1931 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1932 chat::add_info_msg_with_cmd(
1933 context,
1934 chat_id,
1935 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1936 SystemMessage::Unknown,
1937 Some(sort_timestamp),
1938 mime_parser.timestamp_sent,
1939 None,
1940 None,
1941 None,
1942 )
1943 .await?;
1944 }
1945 }
1946 } else {
1947 warn!(
1948 context,
1949 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1950 );
1951 }
1952 }
1953
1954 let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1955 {
1956 Some(stock_str::msg_location_enabled_by(context, from_id).await)
1957 } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1958 let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1959
1960 ephemeral_timer = EphemeralTimer::Disabled;
1967
1968 Some(better_msg)
1969 } else {
1970 None
1971 };
1972
1973 drop(chat); let sort_timestamp = tweak_sort_timestamp(
1976 context,
1977 mime_parser,
1978 group_changes.silent,
1979 chat_id,
1980 sort_timestamp,
1981 )
1982 .await?;
1983
1984 let mime_in_reply_to = mime_parser
1985 .get_header(HeaderDef::InReplyTo)
1986 .unwrap_or_default();
1987 let mime_references = mime_parser
1988 .get_header(HeaderDef::References)
1989 .unwrap_or_default();
1990
1991 let icnt = mime_parser.parts.len();
1996
1997 let subject = mime_parser.get_subject().unwrap_or_default();
1998
1999 let is_system_message = mime_parser.is_system_message;
2000
2001 let mut save_mime_modified = false;
2008
2009 let mime_headers = if mime_parser.is_mime_modified {
2010 let headers = if !mime_parser.decoded_data.is_empty() {
2011 mime_parser.decoded_data.clone()
2012 } else {
2013 imf_raw.to_vec()
2014 };
2015 tokio::task::block_in_place(move || buf_compress(&headers))?
2016 } else {
2017 Vec::new()
2018 };
2019
2020 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
2021
2022 if let Some(m) = group_changes.better_msg {
2023 match &better_msg {
2024 None => better_msg = Some(m),
2025 Some(_) => {
2026 if !m.is_empty() {
2027 group_changes.extra_msgs.push((m, is_system_message, None))
2028 }
2029 }
2030 }
2031 }
2032
2033 let chat_id = if better_msg
2034 .as_ref()
2035 .is_some_and(|better_msg| better_msg.is_empty())
2036 {
2037 DC_CHAT_ID_TRASH
2038 } else {
2039 chat_id
2040 };
2041
2042 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
2043 chat::add_info_msg_with_cmd(
2044 context,
2045 chat_id,
2046 &group_changes_msg,
2047 cmd,
2048 Some(sort_timestamp),
2049 mime_parser.timestamp_sent,
2050 None,
2051 None,
2052 added_removed_id,
2053 )
2054 .await?;
2055 }
2056
2057 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
2058 match mime_parser.get_header(HeaderDef::InReplyTo) {
2059 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
2060 Some(instance_id) => {
2061 if let Err(err) =
2062 add_gossip_peer_from_header(context, instance_id, node_addr).await
2063 {
2064 warn!(context, "Failed to add iroh peer from header: {err:#}.");
2065 }
2066 }
2067 None => {
2068 warn!(
2069 context,
2070 "Cannot add iroh peer because WebXDC instance does not exist."
2071 );
2072 }
2073 },
2074 None => {
2075 warn!(
2076 context,
2077 "Cannot add iroh peer because the message has no In-Reply-To."
2078 );
2079 }
2080 }
2081 }
2082
2083 handle_edit_delete(context, mime_parser, from_id, &mime_headers).await?;
2084 handle_post_message(context, mime_parser, from_id, state).await?;
2085
2086 if mime_parser.is_system_message == SystemMessage::CallAccepted
2087 || mime_parser.is_system_message == SystemMessage::CallEnded
2088 {
2089 if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
2090 if let Some(call) =
2091 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
2092 {
2093 context
2094 .handle_call_msg(call.get_id(), mime_parser, from_id)
2095 .await?;
2096 } else {
2097 warn!(context, "Call: Cannot load parent.")
2098 }
2099 } else {
2100 warn!(context, "Call: Not a reply.")
2101 }
2102 }
2103
2104 let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
2105 let mut parts = mime_parser.parts.iter().peekable();
2106 while let Some(part) = parts.next() {
2107 let hidden = part.is_reaction;
2108 if part.is_reaction {
2109 let reaction_str = simplify::remove_footers(part.msg.as_str());
2110 let is_incoming_fresh = mime_parser.incoming && !seen;
2111 set_msg_reaction(
2112 context,
2113 mime_in_reply_to,
2114 chat_id,
2115 from_id,
2116 sort_timestamp,
2117 Reaction::new(reaction_str.as_str()),
2118 is_incoming_fresh,
2119 )
2120 .await?;
2121 }
2122
2123 let mut param = part.param.clone();
2124 if is_system_message != SystemMessage::Unknown {
2125 param.set_int(Param::Cmd, is_system_message as i32);
2126 }
2127
2128 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
2129 (better_msg, Viewtype::Text)
2130 } else {
2131 (&part.msg, part.typ)
2132 };
2133 let part_is_empty =
2134 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
2135
2136 if let Some(contact_id) = group_changes.added_removed_id {
2137 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
2138 }
2139
2140 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
2141 let save_mime_modified = save_mime_modified && parts.peek().is_none();
2142
2143 let ephemeral_timestamp = if in_fresh {
2144 0
2145 } else {
2146 match ephemeral_timer {
2147 EphemeralTimer::Disabled => 0,
2148 EphemeralTimer::Enabled { duration } => {
2149 mime_parser.timestamp_rcvd.saturating_add(duration.into())
2150 }
2151 }
2152 };
2153
2154 if let PreMessageMode::Pre {
2155 metadata: Some(metadata),
2156 ..
2157 } = &mime_parser.pre_message
2158 {
2159 param.apply_post_msg_metadata(metadata);
2160 };
2161
2162 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2165
2166 let row_id = context
2167 .sql
2168 .call_write(|conn| {
2169 let mut stmt = conn.prepare_cached(
2170 "
2171INSERT INTO msgs
2172 (
2173 rfc724_mid, pre_rfc724_mid, chat_id,
2174 from_id, to_id, timestamp, timestamp_sent,
2175 timestamp_rcvd, type, state, msgrmsg,
2176 txt, txt_normalized, subject, param, hidden,
2177 bytes, mime_headers, mime_compressed, mime_in_reply_to,
2178 mime_references, mime_modified, error, ephemeral_timer,
2179 ephemeral_timestamp, download_state, hop_info
2180 )
2181 VALUES (
2182 ?, ?, ?, ?, ?,
2183 ?, ?, ?, ?,
2184 ?, ?, ?, ?,
2185 ?, ?, ?, ?, ?, 1,
2186 ?, ?, ?, ?,
2187 ?, ?, ?, ?
2188 )",
2189 )?;
2190 let params = params![
2191 if let PreMessageMode::Pre {
2192 post_msg_rfc724_mid,
2193 ..
2194 } = &mime_parser.pre_message
2195 {
2196 post_msg_rfc724_mid
2197 } else {
2198 rfc724_mid_orig
2199 },
2200 if let PreMessageMode::Pre { .. } = &mime_parser.pre_message {
2201 rfc724_mid_orig
2202 } else {
2203 ""
2204 },
2205 if trash { DC_CHAT_ID_TRASH } else { chat_id },
2206 if trash { ContactId::UNDEFINED } else { from_id },
2207 if trash { ContactId::UNDEFINED } else { to_id },
2208 sort_timestamp,
2209 if trash { 0 } else { mime_parser.timestamp_sent },
2210 if trash { 0 } else { mime_parser.timestamp_rcvd },
2211 if trash {
2212 Viewtype::Unknown
2213 } else if let PreMessageMode::Pre { .. } = mime_parser.pre_message {
2214 Viewtype::Text
2215 } else {
2216 typ
2217 },
2218 if trash {
2219 MessageState::Undefined
2220 } else {
2221 state
2222 },
2223 if trash {
2224 MessengerMessage::No
2225 } else {
2226 is_dc_message
2227 },
2228 if trash || hidden { "" } else { msg },
2229 if trash || hidden {
2230 None
2231 } else {
2232 normalize_text(msg)
2233 },
2234 if trash || hidden { "" } else { &subject },
2235 if trash {
2236 "".to_string()
2237 } else {
2238 param.to_string()
2239 },
2240 !trash && hidden,
2241 if trash { 0 } else { part.bytes as isize },
2242 if save_mime_modified && !(trash || hidden) {
2243 mime_headers.clone()
2244 } else {
2245 Vec::new()
2246 },
2247 if trash { "" } else { mime_in_reply_to },
2248 if trash { "" } else { mime_references },
2249 !trash && save_mime_modified,
2250 if trash {
2251 ""
2252 } else {
2253 part.error.as_deref().unwrap_or_default()
2254 },
2255 if trash { 0 } else { ephemeral_timer.to_u32() },
2256 if trash { 0 } else { ephemeral_timestamp },
2257 if trash {
2258 DownloadState::Done
2259 } else if mime_parser.decryption_error.is_some() {
2260 DownloadState::Undecipherable
2261 } else if let PreMessageMode::Pre { .. } = mime_parser.pre_message {
2262 DownloadState::Available
2263 } else {
2264 DownloadState::Done
2265 },
2266 if trash { "" } else { &mime_parser.hop_info },
2267 ];
2268 let row_id = MsgId::new(stmt.insert(params)?.try_into()?);
2269 Ok(row_id)
2270 })
2271 .await?;
2272 ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2273 created_db_entries.push(row_id);
2274 }
2275
2276 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2278 if mime_parser.pre_message != PreMessageMode::Post
2279 && part.typ == Viewtype::Webxdc
2280 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2281 {
2282 let topic = iroh_topic_from_str(topic)?;
2283 insert_topic_stub(context, *msg_id, topic).await?;
2284 }
2285
2286 maybe_set_logging_xdc_inner(
2287 context,
2288 part.typ,
2289 chat_id,
2290 part.param.get(Param::Filename),
2291 *msg_id,
2292 )
2293 .await?;
2294 }
2295
2296 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2297 Some(addr) => context.is_self_addr(addr).await?,
2298 None => true,
2299 };
2300 if unarchive {
2301 chat_id.unarchive_if_not_muted(context, state).await?;
2302 }
2303
2304 info!(
2305 context,
2306 "Message has {icnt} parts and is assigned to chat #{chat_id}, timestamp={sort_timestamp}."
2307 );
2308
2309 if !chat_id.is_trash() && !hidden {
2310 let mut chat = Chat::load_from_db(context, chat_id).await?;
2311 let mut update_param = false;
2312
2313 if chat
2317 .param
2318 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2319 {
2320 let subject = mime_parser.get_subject().unwrap_or_default();
2323
2324 chat.param.set(Param::LastSubject, subject);
2325 update_param = true;
2326 }
2327
2328 if chat.is_unpromoted() {
2329 chat.param.remove(Param::Unpromoted);
2330 update_param = true;
2331 }
2332 if update_param {
2333 chat.update_param(context).await?;
2334 }
2335 }
2336
2337 Ok(ReceivedMsg {
2338 chat_id,
2339 state,
2340 hidden,
2341 sort_timestamp,
2342 msg_ids: created_db_entries,
2343 needs_delete_job: false,
2344 })
2345}
2346
2347async fn handle_edit_delete(
2350 context: &Context,
2351 mime_parser: &MimeMessage,
2352 from_id: ContactId,
2353 mime_headers: &[u8],
2354) -> Result<()> {
2355 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2356 let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? else {
2357 warn!(
2358 context,
2359 "Edit message: rfc724_mid {rfc724_mid:?} not found."
2360 );
2361 return Ok(());
2362 };
2363 let Some(mut original_msg) =
2364 Message::load_from_db_optional(context, original_msg_id).await?
2365 else {
2366 warn!(context, "Edit message: Database entry does not exist.");
2367 return Ok(());
2368 };
2369 if original_msg.from_id != from_id {
2370 warn!(context, "Edit message: Bad sender.");
2371 return Ok(());
2372 }
2373 let Some(part) = mime_parser.parts.first() else {
2374 return Ok(());
2375 };
2376
2377 let edit_msg_showpadlock = part
2378 .param
2379 .get_bool(Param::GuaranteeE2ee)
2380 .unwrap_or_default();
2381 if !edit_msg_showpadlock && original_msg.get_showpadlock() {
2382 warn!(context, "Edit message: Not encrypted.");
2383 return Ok(());
2384 }
2385
2386 let new_text = part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2387 chat::save_text_edit_to_db(context, &mut original_msg, new_text, mime_headers).await?;
2388 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
2389 && let Some(part) = mime_parser.parts.first()
2390 {
2391 if part.param.get_bool(Param::GuaranteeE2ee) != Some(true) {
2394 warn!(context, "Delete message: Not encrypted.");
2395 return Ok(());
2396 }
2397
2398 let mut modified_chat_ids = BTreeSet::new();
2399 let mut msg_ids = Vec::new();
2400
2401 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2402 for rfc724_mid in rfc724_mid_vec {
2403 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2404 let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? else {
2405 warn!(context, "Delete message: {rfc724_mid:?} not found.");
2406 insert_tombstone(context, rfc724_mid).await?;
2408 continue;
2409 };
2410
2411 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2412 warn!(context, "Delete message: Database entry does not exist.");
2413 continue;
2414 };
2415 if msg.from_id != from_id {
2416 warn!(context, "Delete message: Bad sender.");
2417 continue;
2418 }
2419
2420 message::delete_msg_locally(context, &msg).await?;
2421 msg_ids.push(msg.id);
2422 modified_chat_ids.insert(msg.chat_id);
2423 }
2424 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2425 }
2426 Ok(())
2427}
2428
2429async fn handle_post_message(
2430 context: &Context,
2431 mime_parser: &MimeMessage,
2432 from_id: ContactId,
2433 state: MessageState,
2434) -> Result<()> {
2435 let PreMessageMode::Post = &mime_parser.pre_message else {
2436 return Ok(());
2437 };
2438 let rfc724_mid = mime_parser
2441 .get_rfc724_mid()
2442 .context("expected Post-Message to have a message id")?;
2443
2444 let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
2445 warn!(
2446 context,
2447 "handle_post_message: {rfc724_mid}: Database entry does not exist."
2448 );
2449 return Ok(());
2450 };
2451 let Some(original_msg) = Message::load_from_db_optional(context, msg_id).await? else {
2452 warn!(
2454 context,
2455 "handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
2456 );
2457 return Ok(());
2458 };
2459 let Some(part) = mime_parser.parts.first() else {
2460 return Ok(());
2461 };
2462
2463 if from_id != original_msg.from_id {
2466 warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
2467 return Ok(());
2468 }
2469 let post_msg_showpadlock = part
2470 .param
2471 .get_bool(Param::GuaranteeE2ee)
2472 .unwrap_or_default();
2473 if !post_msg_showpadlock && original_msg.get_showpadlock() {
2474 warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
2475 return Ok(());
2476 }
2477
2478 if !part.typ.has_file() {
2479 warn!(
2480 context,
2481 "handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
2482 );
2483 return Ok(());
2484 }
2485
2486 if part.typ == Viewtype::Webxdc
2487 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2488 {
2489 let topic = iroh_topic_from_str(topic)?;
2490 insert_topic_stub(context, msg_id, topic).await?;
2491 }
2492
2493 let mut new_params = original_msg.param.clone();
2494 new_params
2495 .merge_in_params(part.param.clone())
2496 .remove(Param::PostMessageFileBytes)
2497 .remove(Param::PostMessageViewtype);
2498 context
2501 .sql
2502 .execute(
2503 "
2504UPDATE msgs SET param=?, type=?, bytes=?, error=?, state=max(state,?), download_state=?
2505WHERE id=?
2506 ",
2507 (
2508 new_params.to_string(),
2509 part.typ,
2510 part.bytes as isize,
2511 part.error.as_deref().unwrap_or_default(),
2512 state,
2513 DownloadState::Done as u32,
2514 original_msg.id,
2515 ),
2516 )
2517 .await?;
2518
2519 if context.get_config_bool(Config::Bot).await? {
2520 if original_msg.hidden {
2521 } else if !original_msg.chat_id.is_trash() {
2523 let fresh = original_msg.state == MessageState::InFresh;
2524 let important = mime_parser.incoming && fresh;
2525
2526 original_msg
2527 .chat_id
2528 .emit_msg_event(context, original_msg.id, important);
2529 context.new_msgs_notify.notify_one();
2530 }
2531 } else {
2532 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2533 }
2534
2535 Ok(())
2536}
2537
2538async fn tweak_sort_timestamp(
2539 context: &Context,
2540 mime_parser: &mut MimeMessage,
2541 silent: bool,
2542 chat_id: ChatId,
2543 sort_timestamp: i64,
2544) -> Result<i64> {
2545 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2554 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2555 std::cmp::max(sort_timestamp, parent_timestamp)
2556 });
2557
2558 if silent {
2562 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2563 t
2564 } else {
2565 chat_id.created_timestamp(context).await?
2566 };
2567 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2568 }
2569 Ok(sort_timestamp)
2570}
2571
2572async fn save_locations(
2576 context: &Context,
2577 mime_parser: &MimeMessage,
2578 chat_id: ChatId,
2579 from_id: ContactId,
2580 msg_id: MsgId,
2581) -> Result<()> {
2582 if chat_id.is_special() {
2583 return Ok(());
2585 }
2586
2587 let mut send_event = false;
2588
2589 if let Some(message_kml) = &mime_parser.message_kml
2590 && let Some(newest_location_id) =
2591 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2592 {
2593 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2594 send_event = true;
2595 }
2596
2597 if let Some(location_kml) = &mime_parser.location_kml
2598 && let Some(addr) = &location_kml.addr
2599 {
2600 let contact = Contact::get_by_id(context, from_id).await?;
2601 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2602 if location::save(context, chat_id, from_id, &location_kml.locations, false)
2603 .await?
2604 .is_some()
2605 {
2606 send_event = true;
2607 }
2608 } else {
2609 warn!(
2610 context,
2611 "Address in location.kml {:?} is not the same as the sender address {:?}.",
2612 addr,
2613 contact.get_addr()
2614 );
2615 }
2616 }
2617 if send_event {
2618 context.emit_location_changed(Some(from_id)).await?;
2619 }
2620 Ok(())
2621}
2622
2623async fn lookup_chat_by_reply(
2624 context: &Context,
2625 mime_parser: &MimeMessage,
2626 parent: &Message,
2627) -> Result<Option<(ChatId, Blocked)>> {
2628 ensure_and_debug_assert!(
2633 mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2634 "Encrypted message has group ID {}",
2635 mime_parser.get_chat_group_id().unwrap_or_default(),
2636 );
2637
2638 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2640 return Ok(None);
2641 };
2642
2643 if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2646 return Ok(None);
2647 }
2648
2649 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2653 if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2654 return Ok(None);
2655 }
2656
2657 if parent_chat.is_encrypted(context).await? && !mime_parser.was_encrypted() {
2659 return Ok(None);
2660 }
2661
2662 info!(
2663 context,
2664 "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2665 );
2666 Ok(Some((parent_chat.id, parent_chat.blocked)))
2667}
2668
2669async fn lookup_or_create_adhoc_group(
2670 context: &Context,
2671 mime_parser: &MimeMessage,
2672 to_ids: &[Option<ContactId>],
2673 allow_creation: bool,
2674 create_blocked: Blocked,
2675) -> Result<Option<(ChatId, Blocked, bool)>> {
2676 if mime_parser.decryption_error.is_some() {
2677 warn!(
2678 context,
2679 "Not creating ad-hoc group for message that cannot be decrypted."
2680 );
2681 return Ok(None);
2682 }
2683
2684 let fingerprint = None;
2686 let find_key_contact_by_addr = false;
2687 let prevent_rename = should_prevent_rename(mime_parser);
2688 let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2689 context,
2690 &mime_parser.from,
2691 fingerprint,
2692 prevent_rename,
2693 find_key_contact_by_addr,
2694 )
2695 .await?
2696 .context("Cannot lookup address-contact by the From field")?;
2697
2698 let grpname = mime_parser
2699 .get_header(HeaderDef::ChatGroupName)
2700 .map(|s| s.to_string())
2701 .unwrap_or_else(|| {
2702 mime_parser
2703 .get_subject()
2704 .map(|s| remove_subject_prefix(&s))
2705 .unwrap_or_else(|| "👥📧".to_string())
2706 });
2707 let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2708 let mut contact_ids = BTreeSet::<ContactId>::from_iter(to_ids.iter().copied());
2709 contact_ids.insert(from_id);
2710 if mime_parser.was_encrypted() {
2711 contact_ids.remove(&ContactId::SELF);
2712 }
2713 let trans_fn = |t: &mut rusqlite::Transaction| {
2714 t.pragma_update(None, "query_only", "0")?;
2715 t.execute(
2716 "CREATE TEMP TABLE temp.contacts (
2717 id INTEGER PRIMARY KEY
2718 ) STRICT",
2719 (),
2720 )
2721 .context("CREATE TEMP TABLE temp.contacts")?;
2722 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2723 for &id in &contact_ids {
2724 stmt.execute((id,)).context("INSERT INTO temp.contacts")?;
2725 }
2726 let val = t
2727 .query_row(
2728 "SELECT c.id, c.blocked
2729 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2730 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2731 AND (SELECT COUNT(*) FROM chats_contacts
2732 WHERE chat_id=c.id
2733 AND add_timestamp >= remove_timestamp)=?
2734 AND (SELECT COUNT(*) FROM chats_contacts
2735 WHERE chat_id=c.id
2736 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2737 AND add_timestamp >= remove_timestamp)=0
2738 ORDER BY m.timestamp DESC",
2739 (&grpname, contact_ids.len()),
2740 |row| {
2741 let id: ChatId = row.get(0)?;
2742 let blocked: Blocked = row.get(1)?;
2743 Ok((id, blocked))
2744 },
2745 )
2746 .optional()
2747 .context("Select chat with matching name and members")?;
2748 t.execute("DROP TABLE temp.contacts", ())
2749 .context("DROP TABLE temp.contacts")?;
2750 Ok(val)
2751 };
2752 let query_only = true;
2753 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2754 info!(
2755 context,
2756 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2757 );
2758 return Ok(Some((chat_id, blocked, false)));
2759 }
2760 if !allow_creation {
2761 return Ok(None);
2762 }
2763 Ok(create_adhoc_group(
2764 context,
2765 mime_parser,
2766 create_blocked,
2767 from_id,
2768 &to_ids,
2769 &grpname,
2770 )
2771 .await
2772 .context("Could not create ad hoc group")?
2773 .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2774}
2775
2776async fn is_probably_private_reply(
2779 context: &Context,
2780 mime_parser: &MimeMessage,
2781 parent_chat_id: ChatId,
2782) -> Result<bool> {
2783 if mime_parser.get_chat_group_id().is_some() {
2785 return Ok(false);
2786 }
2787
2788 if mime_parser.recipients.len() != 1 {
2796 return Ok(false);
2797 }
2798
2799 if !mime_parser.has_chat_version() {
2800 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2801 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2802 return Ok(false);
2803 }
2804 }
2805
2806 Ok(true)
2807}
2808
2809async fn create_group(
2815 context: &Context,
2816 mime_parser: &mut MimeMessage,
2817 create_blocked: Blocked,
2818 from_id: ContactId,
2819 to_ids: &[Option<ContactId>],
2820 past_ids: &[Option<ContactId>],
2821 grpid: &str,
2822) -> Result<Option<(ChatId, Blocked)>> {
2823 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2824 let mut chat_id = None;
2825 let mut chat_id_blocked = Default::default();
2826
2827 if !mime_parser.is_mailinglist_message()
2828 && !grpid.is_empty()
2829 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2830 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2832 {
2833 let grpname = mime_parser
2835 .get_header(HeaderDef::ChatGroupName)
2836 .context("Chat-Group-Name vanished")?
2837 .trim();
2841 let new_chat_id = ChatId::create_multiuser_record(
2842 context,
2843 Chattype::Group,
2844 grpid,
2845 grpname,
2846 create_blocked,
2847 None,
2848 mime_parser.timestamp_sent,
2849 )
2850 .await
2851 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2852
2853 chat_id = Some(new_chat_id);
2854 chat_id_blocked = create_blocked;
2855
2856 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2858 let mut new_to_ids = to_ids.to_vec();
2859 if !new_to_ids.contains(&Some(from_id)) {
2860 new_to_ids.insert(0, Some(from_id));
2861 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2862 }
2863
2864 update_chats_contacts_timestamps(
2865 context,
2866 new_chat_id,
2867 None,
2868 &new_to_ids,
2869 past_ids,
2870 &chat_group_member_timestamps,
2871 )
2872 .await?;
2873 } else {
2874 let mut members = vec![ContactId::SELF];
2875 if !from_id.is_special() {
2876 members.push(from_id);
2877 }
2878 members.extend(to_ids_flat);
2879
2880 let timestamp = 0;
2886
2887 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2888 }
2889
2890 context.emit_event(EventType::ChatModified(new_chat_id));
2891 chatlist_events::emit_chatlist_changed(context);
2892 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2893 }
2894
2895 if let Some(chat_id) = chat_id {
2896 Ok(Some((chat_id, chat_id_blocked)))
2897 } else if mime_parser.decryption_error.is_some() {
2898 Ok(None)
2905 } else {
2906 info!(context, "Message belongs to unwanted group (TRASH).");
2909 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2910 }
2911}
2912
2913#[expect(clippy::arithmetic_side_effects)]
2914async fn update_chats_contacts_timestamps(
2915 context: &Context,
2916 chat_id: ChatId,
2917 ignored_id: Option<ContactId>,
2918 to_ids: &[Option<ContactId>],
2919 past_ids: &[Option<ContactId>],
2920 chat_group_member_timestamps: &[i64],
2921) -> Result<bool> {
2922 let expected_timestamps_count = to_ids.len() + past_ids.len();
2923
2924 if chat_group_member_timestamps.len() != expected_timestamps_count {
2925 warn!(
2926 context,
2927 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2928 chat_group_member_timestamps.len(),
2929 expected_timestamps_count
2930 );
2931 return Ok(false);
2932 }
2933
2934 let mut modified = false;
2935
2936 context
2937 .sql
2938 .transaction(|transaction| {
2939 let mut add_statement = transaction.prepare(
2940 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2941 VALUES (?1, ?2, ?3)
2942 ON CONFLICT (chat_id, contact_id)
2943 DO
2944 UPDATE SET add_timestamp=?3
2945 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2946 )?;
2947
2948 for (contact_id, ts) in iter::zip(
2949 to_ids.iter(),
2950 chat_group_member_timestamps.iter().take(to_ids.len()),
2951 ) {
2952 if let Some(contact_id) = contact_id
2953 && Some(*contact_id) != ignored_id
2954 {
2955 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2959 }
2960 }
2961
2962 let mut remove_statement = transaction.prepare(
2963 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2964 VALUES (?1, ?2, ?3)
2965 ON CONFLICT (chat_id, contact_id)
2966 DO
2967 UPDATE SET remove_timestamp=?3
2968 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2969 )?;
2970
2971 for (contact_id, ts) in iter::zip(
2972 past_ids.iter(),
2973 chat_group_member_timestamps.iter().skip(to_ids.len()),
2974 ) {
2975 if let Some(contact_id) = contact_id {
2976 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2980 }
2981 }
2982
2983 Ok(())
2984 })
2985 .await?;
2986
2987 Ok(modified)
2988}
2989
2990#[derive(Default)]
2994struct GroupChangesInfo {
2995 better_msg: Option<String>,
2998 added_removed_id: Option<ContactId>,
3000 silent: bool,
3002 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
3004}
3005
3006async fn apply_group_changes(
3013 context: &Context,
3014 mime_parser: &mut MimeMessage,
3015 chat: &mut Chat,
3016 from_id: ContactId,
3017 to_ids: &[Option<ContactId>],
3018 past_ids: &[Option<ContactId>],
3019 is_chat_created: bool,
3020) -> Result<GroupChangesInfo> {
3021 let from_is_key_contact = Contact::get_by_id(context, from_id).await?.is_key_contact();
3022 ensure!(from_is_key_contact || chat.grpid.is_empty());
3023 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
3024 ensure!(chat.typ == Chattype::Group);
3025 ensure!(!chat.id.is_special());
3026
3027 let mut send_event_chat_modified = false;
3028 let (mut removed_id, mut added_id) = (None, None);
3029 let mut better_msg = None;
3030 let mut silent = false;
3031 let chat_contacts =
3032 BTreeSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
3033 let is_from_in_chat =
3034 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
3035
3036 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3037 if !is_from_in_chat {
3038 better_msg = Some(String::new());
3039 } else if let Some(removed_fpr) =
3040 mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr)
3041 {
3042 removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3043 } else {
3044 removed_id =
3046 lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?;
3047 }
3048 if let Some(id) = removed_id {
3049 better_msg = if id == from_id {
3050 silent = true;
3051 Some(stock_str::msg_group_left_local(context, from_id).await)
3052 } else {
3053 Some(stock_str::msg_del_member_local(context, id, from_id).await)
3054 };
3055 } else {
3056 warn!(context, "Removed {removed_addr:?} has no contact id.")
3057 }
3058 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3059 if !is_from_in_chat {
3060 better_msg = Some(String::new());
3061 } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
3062 if !chat_contacts.contains(&from_id) {
3063 chat::add_to_chat_contacts_table(
3064 context,
3065 mime_parser.timestamp_sent,
3066 chat.id,
3067 &[from_id],
3068 )
3069 .await?;
3070 }
3071
3072 let fingerprint = key.public_key.dc_fingerprint().hex();
3079 if let Some(contact_id) =
3080 lookup_key_contact_by_fingerprint(context, &fingerprint).await?
3081 {
3082 added_id = Some(contact_id);
3083 better_msg =
3084 Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
3085 } else {
3086 warn!(context, "Added {added_addr:?} has no contact id.");
3087 }
3088 } else {
3089 warn!(context, "Added {added_addr:?} has no gossiped key.");
3090 }
3091 }
3092
3093 apply_chat_name_avatar_and_description_changes(
3094 context,
3095 mime_parser,
3096 from_id,
3097 is_from_in_chat,
3098 chat,
3099 &mut send_event_chat_modified,
3100 &mut better_msg,
3101 )
3102 .await?;
3103
3104 if is_from_in_chat {
3105 if from_is_key_contact != chat.grpid.is_empty()
3107 && chat.member_list_is_stale(context).await?
3108 {
3109 info!(context, "Member list is stale.");
3110 let mut new_members: BTreeSet<ContactId> =
3111 BTreeSet::from_iter(to_ids_flat.iter().copied());
3112 new_members.insert(ContactId::SELF);
3113 if !from_id.is_special() {
3114 new_members.insert(from_id);
3115 }
3116 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3117 new_members.remove(&ContactId::SELF);
3118 }
3119 context
3120 .sql
3121 .transaction(|transaction| {
3122 transaction.execute(
3124 "DELETE FROM chats_contacts
3125 WHERE chat_id=?",
3126 (chat.id,),
3127 )?;
3128
3129 let mut statement = transaction.prepare(
3131 "INSERT INTO chats_contacts (chat_id, contact_id)
3132 VALUES (?, ?)",
3133 )?;
3134 for contact_id in &new_members {
3135 statement.execute((chat.id, contact_id))?;
3136 }
3137
3138 Ok(())
3139 })
3140 .await?;
3141 send_event_chat_modified = true;
3142 } else if let Some(ref chat_group_member_timestamps) =
3143 mime_parser.chat_group_member_timestamps()
3144 {
3145 send_event_chat_modified |= update_chats_contacts_timestamps(
3146 context,
3147 chat.id,
3148 Some(from_id),
3149 to_ids,
3150 past_ids,
3151 chat_group_member_timestamps,
3152 )
3153 .await?;
3154 } else {
3155 let mut new_members: BTreeSet<ContactId>;
3156 let self_added =
3159 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3160 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
3161 && !chat_contacts.contains(&ContactId::SELF)
3162 } else {
3163 false
3164 };
3165 if self_added {
3166 new_members = BTreeSet::from_iter(to_ids_flat.iter().copied());
3167 new_members.insert(ContactId::SELF);
3168 if !from_id.is_special() && from_is_key_contact != chat.grpid.is_empty() {
3169 new_members.insert(from_id);
3170 }
3171 } else {
3172 new_members = chat_contacts.clone();
3173 }
3174
3175 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
3177 new_members.extend(to_ids_flat.iter());
3180 }
3181
3182 if let Some(added_id) = added_id {
3184 new_members.insert(added_id);
3185 }
3186
3187 if let Some(removed_id) = removed_id {
3189 new_members.remove(&removed_id);
3190 }
3191
3192 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3193 new_members.remove(&ContactId::SELF);
3194 }
3195
3196 if new_members != chat_contacts {
3197 chat::update_chat_contacts_table(
3198 context,
3199 mime_parser.timestamp_sent,
3200 chat.id,
3201 &new_members,
3202 )
3203 .await?;
3204 send_event_chat_modified = true;
3205 }
3206 }
3207
3208 chat.id
3209 .update_timestamp(
3210 context,
3211 Param::MemberListTimestamp,
3212 mime_parser.timestamp_sent,
3213 )
3214 .await?;
3215 }
3216
3217 let new_chat_contacts = BTreeSet::<ContactId>::from_iter(
3218 chat::get_chat_contacts(context, chat.id)
3219 .await?
3220 .iter()
3221 .copied(),
3222 );
3223
3224 let mut added_ids: BTreeSet<ContactId> = new_chat_contacts
3226 .difference(&chat_contacts)
3227 .copied()
3228 .collect();
3229 let mut removed_ids: BTreeSet<ContactId> = chat_contacts
3230 .difference(&new_chat_contacts)
3231 .copied()
3232 .collect();
3233 let id_was_already_added = if let Some(added_id) = added_id {
3234 !added_ids.remove(&added_id)
3235 } else {
3236 false
3237 };
3238 if let Some(removed_id) = removed_id {
3239 removed_ids.remove(&removed_id);
3240 }
3241
3242 let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
3243 && new_chat_contacts.contains(&ContactId::SELF)
3244 {
3245 Vec::new()
3246 } else {
3247 group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
3248 };
3249
3250 if id_was_already_added && group_changes_msgs.is_empty() && !is_chat_created {
3251 info!(context, "No-op 'Member added' message (TRASH)");
3252 better_msg = Some(String::new());
3253 }
3254
3255 if send_event_chat_modified {
3256 context.emit_event(EventType::ChatModified(chat.id));
3257 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3258 }
3259 Ok(GroupChangesInfo {
3260 better_msg,
3261 added_removed_id: if added_id.is_some() {
3262 added_id
3263 } else {
3264 removed_id
3265 },
3266 silent,
3267 extra_msgs: group_changes_msgs,
3268 })
3269}
3270
3271async fn apply_chat_name_avatar_and_description_changes(
3276 context: &Context,
3277 mime_parser: &MimeMessage,
3278 from_id: ContactId,
3279 is_from_in_chat: bool,
3280 chat: &mut Chat,
3281 send_event_chat_modified: &mut bool,
3282 better_msg: &mut Option<String>,
3283) -> Result<()> {
3284 let group_name_timestamp = mime_parser
3287 .get_header(HeaderDef::ChatGroupNameTimestamp)
3288 .and_then(|s| s.parse::<i64>().ok());
3289
3290 if let Some(old_name) = mime_parser
3291 .get_header(HeaderDef::ChatGroupNameChanged)
3292 .map(|s| s.trim())
3293 .or(match group_name_timestamp {
3294 Some(0) => None,
3295 Some(_) => Some(chat.name.as_str()),
3296 None => None,
3297 })
3298 && let Some(grpname) = mime_parser
3299 .get_header(HeaderDef::ChatGroupName)
3300 .map(|grpname| grpname.trim())
3301 .filter(|grpname| grpname.len() < 200)
3302 {
3303 let grpname = &sanitize_single_line(grpname);
3304
3305 let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3306 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3307 if is_from_in_chat
3309 && (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3310 && chat
3311 .id
3312 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3313 .await?
3314 && grpname != &chat.name
3315 {
3316 info!(context, "Updating grpname for chat {}.", chat.id);
3317 context
3318 .sql
3319 .execute(
3320 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3321 (grpname, normalize_text(grpname), chat.id),
3322 )
3323 .await?;
3324 *send_event_chat_modified = true;
3325 }
3326 if mime_parser
3327 .get_header(HeaderDef::ChatGroupNameChanged)
3328 .is_some()
3329 {
3330 if is_from_in_chat {
3331 let old_name = &sanitize_single_line(old_name);
3332 better_msg.get_or_insert(
3333 if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
3334 stock_str::msg_broadcast_name_changed(context, old_name, grpname)
3335 } else {
3336 stock_str::msg_grp_name(context, old_name, grpname, from_id).await
3337 },
3338 );
3339 } else {
3340 *better_msg = Some(String::new());
3342 }
3343 }
3344 }
3345
3346 if let Some(new_description) = mime_parser
3349 .get_header(HeaderDef::ChatGroupDescription)
3350 .map(|d| d.trim())
3351 {
3352 let new_description = sanitize_bidi_characters(new_description.trim());
3353 let old_description = chat::get_chat_description(context, chat.id).await?;
3354
3355 let old_timestamp = chat
3356 .param
3357 .get_i64(Param::GroupDescriptionTimestamp)
3358 .unwrap_or(0);
3359 let timestamp_in_header = mime_parser
3360 .get_header(HeaderDef::ChatGroupDescriptionTimestamp)
3361 .and_then(|s| s.parse::<i64>().ok());
3362
3363 let new_timestamp = timestamp_in_header.unwrap_or(mime_parser.timestamp_sent);
3364 if is_from_in_chat
3366 && (old_timestamp, &old_description) < (new_timestamp, &new_description)
3367 && chat
3368 .id
3369 .update_timestamp(context, Param::GroupDescriptionTimestamp, new_timestamp)
3370 .await?
3371 && new_description != old_description
3372 {
3373 info!(context, "Updating description for chat {}.", chat.id);
3374 context
3375 .sql
3376 .execute(
3377 "INSERT OR REPLACE INTO chats_descriptions(chat_id, description) VALUES(?, ?)",
3378 (chat.id, &new_description),
3379 )
3380 .await?;
3381 *send_event_chat_modified = true;
3382 }
3383 if mime_parser
3384 .get_header(HeaderDef::ChatGroupDescriptionChanged)
3385 .is_some()
3386 {
3387 if is_from_in_chat {
3388 better_msg
3389 .get_or_insert(stock_str::msg_chat_description_changed(context, from_id).await);
3390 } else {
3391 *better_msg = Some(String::new());
3393 }
3394 }
3395 }
3396
3397 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3400 && value == "group-avatar-changed"
3401 && let Some(avatar_action) = &mime_parser.group_avatar
3402 {
3403 if is_from_in_chat {
3404 better_msg.get_or_insert(
3407 if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
3408 stock_str::msg_broadcast_img_changed(context)
3409 } else {
3410 match avatar_action {
3411 AvatarAction::Delete => {
3412 stock_str::msg_grp_img_deleted(context, from_id).await
3413 }
3414 AvatarAction::Change(_) => {
3415 stock_str::msg_grp_img_changed(context, from_id).await
3416 }
3417 }
3418 },
3419 );
3420 } else {
3421 *better_msg = Some(String::new());
3423 }
3424 }
3425
3426 if let Some(avatar_action) = &mime_parser.group_avatar
3427 && is_from_in_chat
3428 && chat
3429 .param
3430 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3431 {
3432 info!(context, "Group-avatar change for {}.", chat.id);
3433 match avatar_action {
3434 AvatarAction::Change(profile_image) => {
3435 chat.param.set(Param::ProfileImage, profile_image);
3436 }
3437 AvatarAction::Delete => {
3438 chat.param.remove(Param::ProfileImage);
3439 }
3440 };
3441 chat.update_param(context).await?;
3442 *send_event_chat_modified = true;
3443 }
3444
3445 Ok(())
3446}
3447
3448#[expect(clippy::arithmetic_side_effects)]
3450async fn group_changes_msgs(
3451 context: &Context,
3452 added_ids: &BTreeSet<ContactId>,
3453 removed_ids: &BTreeSet<ContactId>,
3454 chat_id: ChatId,
3455) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3456 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3457 if !added_ids.is_empty() {
3458 warn!(
3459 context,
3460 "Implicit addition of {added_ids:?} to chat {chat_id}."
3461 );
3462 }
3463 if !removed_ids.is_empty() {
3464 warn!(
3465 context,
3466 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3467 );
3468 }
3469 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3470 for contact_id in added_ids {
3471 group_changes_msgs.push((
3472 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3473 SystemMessage::MemberAddedToGroup,
3474 Some(*contact_id),
3475 ));
3476 }
3477 for contact_id in removed_ids {
3478 group_changes_msgs.push((
3479 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3480 SystemMessage::MemberRemovedFromGroup,
3481 Some(*contact_id),
3482 ));
3483 }
3484
3485 Ok(group_changes_msgs)
3486}
3487
3488static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3489
3490fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3491 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3492 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3493 None => list_id_header
3494 .trim()
3495 .trim_start_matches('<')
3496 .trim_end_matches('>'),
3497 }
3498 .to_string())
3499}
3500
3501async fn create_or_lookup_mailinglist_or_broadcast(
3516 context: &Context,
3517 allow_creation: bool,
3518 create_blocked: Blocked,
3519 list_id_header: &str,
3520 from_id: ContactId,
3521 mime_parser: &MimeMessage,
3522) -> Result<Option<(ChatId, Blocked, bool)>> {
3523 let listid = mailinglist_header_listid(list_id_header)?;
3524
3525 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3526 return Ok(Some((chat_id, blocked, false)));
3527 }
3528
3529 let chattype = if mime_parser.was_encrypted() {
3530 Chattype::InBroadcast
3531 } else {
3532 Chattype::Mailinglist
3533 };
3534
3535 let name = if chattype == Chattype::InBroadcast {
3536 mime_parser
3537 .get_header(HeaderDef::ChatGroupName)
3538 .unwrap_or("Broadcast Channel")
3539 } else {
3540 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3541 };
3542
3543 if allow_creation {
3544 let param = mime_parser.list_post.as_ref().map(|list_post| {
3546 let mut p = Params::new();
3547 p.set(Param::ListPost, list_post);
3548 p.to_string()
3549 });
3550
3551 let chat_id = ChatId::create_multiuser_record(
3552 context,
3553 chattype,
3554 &listid,
3555 name,
3556 if chattype == Chattype::InBroadcast {
3557 Blocked::Not
3561 } else {
3562 create_blocked
3563 },
3564 param,
3565 mime_parser.timestamp_sent,
3566 )
3567 .await
3568 .with_context(|| format!("failed to create mailinglist '{name}' for grpid={listid}",))?;
3569
3570 if chattype == Chattype::InBroadcast {
3571 chat::add_to_chat_contacts_table(
3572 context,
3573 mime_parser.timestamp_sent,
3574 chat_id,
3575 &[from_id],
3576 )
3577 .await?;
3578 }
3579
3580 context.emit_event(EventType::ChatModified(chat_id));
3581 chatlist_events::emit_chatlist_changed(context);
3582 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3583
3584 Ok(Some((chat_id, create_blocked, true)))
3585 } else {
3586 info!(context, "Creating list forbidden by caller.");
3587 Ok(None)
3588 }
3589}
3590
3591fn compute_mailinglist_name(
3592 list_id_header: &str,
3593 listid: &str,
3594 mime_parser: &MimeMessage,
3595) -> String {
3596 let mut name = match LIST_ID_REGEX
3597 .captures(list_id_header)
3598 .and_then(|caps| caps.get(1))
3599 {
3600 Some(cap) => cap.as_str().trim().to_string(),
3601 None => "".to_string(),
3602 };
3603
3604 if listid.ends_with(".list-id.mcsv.net")
3608 && let Some(display_name) = &mime_parser.from.display_name
3609 {
3610 name.clone_from(display_name);
3611 }
3612
3613 let subject = mime_parser.get_subject().unwrap_or_default();
3617 static SUBJECT: LazyLock<Regex> =
3618 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3620 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3621 }
3622
3623 if name.is_empty()
3630 && (mime_parser.from.addr.contains("noreply")
3631 || mime_parser.from.addr.contains("no-reply")
3632 || mime_parser.from.addr.starts_with("notifications@")
3633 || mime_parser.from.addr.starts_with("newsletter@")
3634 || listid.ends_with(".xt.local"))
3635 && let Some(display_name) = &mime_parser.from.display_name
3636 {
3637 name.clone_from(display_name);
3638 }
3639
3640 if name.is_empty() {
3643 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3645 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3646 if let Some(cap) = PREFIX_32_CHARS_HEX
3647 .captures(listid)
3648 .and_then(|caps| caps.get(2))
3649 {
3650 name = cap.as_str().to_string();
3651 } else {
3652 name = listid.to_string();
3653 }
3654 }
3655
3656 sanitize_single_line(&name)
3657}
3658
3659async fn apply_mailinglist_changes(
3663 context: &Context,
3664 mime_parser: &MimeMessage,
3665 chat_id: ChatId,
3666) -> Result<()> {
3667 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3668 return Ok(());
3669 };
3670
3671 let mut chat = Chat::load_from_db(context, chat_id).await?;
3672 if chat.typ != Chattype::Mailinglist {
3673 return Ok(());
3674 }
3675 let listid = &chat.grpid;
3676
3677 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3678 if chat.name != new_name
3679 && chat_id
3680 .update_timestamp(
3681 context,
3682 Param::GroupNameTimestamp,
3683 mime_parser.timestamp_sent,
3684 )
3685 .await?
3686 {
3687 info!(context, "Updating listname for chat {chat_id}.");
3688 context
3689 .sql
3690 .execute(
3691 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3692 (&new_name, normalize_text(&new_name), chat_id),
3693 )
3694 .await?;
3695 context.emit_event(EventType::ChatModified(chat_id));
3696 }
3697
3698 let Some(list_post) = &mime_parser.list_post else {
3699 return Ok(());
3700 };
3701
3702 let list_post = match ContactAddress::new(list_post) {
3703 Ok(list_post) => list_post,
3704 Err(err) => {
3705 warn!(context, "Invalid List-Post: {:#}.", err);
3706 return Ok(());
3707 }
3708 };
3709 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3710 let mut contact = Contact::get_by_id(context, contact_id).await?;
3711 if contact.param.get(Param::ListId) != Some(listid) {
3712 contact.param.set(Param::ListId, listid);
3713 contact.update_param(context).await?;
3714 }
3715
3716 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3717 if list_post.as_ref() != old_list_post {
3718 chat.param.remove(Param::ListPost);
3721 chat.update_param(context).await?;
3722 }
3723 } else {
3724 chat.param.set(Param::ListPost, list_post);
3725 chat.update_param(context).await?;
3726 }
3727
3728 Ok(())
3729}
3730
3731async fn apply_out_broadcast_changes(
3732 context: &Context,
3733 mime_parser: &MimeMessage,
3734 chat: &mut Chat,
3735 from_id: ContactId,
3736) -> Result<GroupChangesInfo> {
3737 ensure!(chat.typ == Chattype::OutBroadcast);
3738
3739 let mut send_event_chat_modified = false;
3740 let mut better_msg = None;
3741 let mut added_removed_id: Option<ContactId> = None;
3742
3743 if from_id == ContactId::SELF {
3744 let is_from_in_chat = true;
3745 apply_chat_name_avatar_and_description_changes(
3746 context,
3747 mime_parser,
3748 from_id,
3749 is_from_in_chat,
3750 chat,
3751 &mut send_event_chat_modified,
3752 &mut better_msg,
3753 )
3754 .await?;
3755 }
3756
3757 if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3758 if from_id == ContactId::SELF {
3759 let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3760 if let Some(added_id) = added_id {
3761 if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3762 info!(context, "No-op broadcast addition (TRASH)");
3763 better_msg.get_or_insert("".to_string());
3764 } else {
3765 chat::add_to_chat_contacts_table(
3766 context,
3767 mime_parser.timestamp_sent,
3768 chat.id,
3769 &[added_id],
3770 )
3771 .await?;
3772 let msg =
3773 stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3774 .await;
3775 better_msg.get_or_insert(msg);
3776 added_removed_id = Some(added_id);
3777 send_event_chat_modified = true;
3778 }
3779 } else {
3780 warn!(context, "Failed to find contact with fpr {added_fpr}");
3781 }
3782 }
3783 } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3784 send_event_chat_modified = true;
3785 let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3786 if removed_id == Some(from_id) {
3787 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3790 info!(context, "Broadcast leave message (TRASH)");
3791 better_msg = Some("".to_string());
3792 } else if from_id == ContactId::SELF
3793 && let Some(removed_id) = removed_id
3794 {
3795 if chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3796 .await?
3797 {
3798 better_msg.get_or_insert(
3799 stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3800 );
3801 added_removed_id = Some(removed_id);
3802 } else {
3803 info!(context, "No-op broadcast member removal message (TRASH).");
3804 better_msg = Some("".to_string());
3805 }
3806 }
3807 }
3808
3809 if send_event_chat_modified {
3810 context.emit_event(EventType::ChatModified(chat.id));
3811 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3812 }
3813 Ok(GroupChangesInfo {
3814 better_msg,
3815 added_removed_id,
3816 silent: false,
3817 extra_msgs: vec![],
3818 })
3819}
3820
3821async fn apply_in_broadcast_changes(
3822 context: &Context,
3823 mime_parser: &MimeMessage,
3824 chat: &mut Chat,
3825 from_id: ContactId,
3826) -> Result<GroupChangesInfo> {
3827 ensure!(chat.typ == Chattype::InBroadcast);
3828
3829 if let Some(part) = mime_parser.parts.first()
3830 && let Some(error) = &part.error
3831 {
3832 warn!(
3833 context,
3834 "Not applying broadcast changes from message with error: {error}"
3835 );
3836 return Ok(GroupChangesInfo::default());
3837 }
3838
3839 let mut send_event_chat_modified = false;
3840 let mut better_msg = None;
3841
3842 let is_from_in_chat = is_contact_in_chat(context, chat.id, from_id).await?;
3843 apply_chat_name_avatar_and_description_changes(
3844 context,
3845 mime_parser,
3846 from_id,
3847 is_from_in_chat,
3848 chat,
3849 &mut send_event_chat_modified,
3850 &mut better_msg,
3851 )
3852 .await?;
3853
3854 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3855 && context.is_self_addr(added_addr).await?
3856 {
3857 let msg = if chat.is_self_in_chat(context).await? {
3858 info!(context, "No-op broadcast 'Member added' message (TRASH)");
3862 "".to_string()
3863 } else {
3864 stock_str::msg_you_joined_broadcast(context)
3865 };
3866
3867 better_msg.get_or_insert(msg);
3868 send_event_chat_modified = true;
3869 }
3870
3871 if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3872 if removed_fpr != self_fingerprint(context).await? {
3874 logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3875 return Ok(GroupChangesInfo::default());
3876 }
3877 chat::delete_broadcast_secret(context, chat.id).await?;
3878
3879 let removed =
3880 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3881 .await?;
3882 if !removed {
3883 info!(context, "No-op broadcast SELF-removal message (TRASH).");
3884 better_msg = Some("".to_string());
3885 } else if from_id == ContactId::SELF {
3886 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
3887 } else {
3888 better_msg.get_or_insert(
3889 stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3890 );
3891 }
3892 send_event_chat_modified |= removed;
3893 } else if !chat.is_self_in_chat(context).await? {
3894 chat::add_to_chat_contacts_table(
3895 context,
3896 mime_parser.timestamp_sent,
3897 chat.id,
3898 &[ContactId::SELF],
3899 )
3900 .await?;
3901 send_event_chat_modified = true;
3902 }
3903
3904 if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3905 if validate_broadcast_secret(secret) {
3906 save_broadcast_secret(context, chat.id, secret).await?;
3907 } else {
3908 warn!(context, "Not saving invalid broadcast secret");
3909 }
3910 }
3911
3912 if send_event_chat_modified {
3913 context.emit_event(EventType::ChatModified(chat.id));
3914 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3915 }
3916 Ok(GroupChangesInfo {
3917 better_msg,
3918 added_removed_id: None,
3919 silent: false,
3920 extra_msgs: vec![],
3921 })
3922}
3923
3924async fn create_adhoc_group(
3926 context: &Context,
3927 mime_parser: &MimeMessage,
3928 create_blocked: Blocked,
3929 from_id: ContactId,
3930 to_ids: &[ContactId],
3931 grpname: &str,
3932) -> Result<Option<(ChatId, Blocked)>> {
3933 let mut member_ids: Vec<ContactId> = to_ids
3934 .iter()
3935 .copied()
3936 .filter(|&id| id != ContactId::SELF)
3937 .collect();
3938 if from_id != ContactId::SELF && !member_ids.contains(&from_id) {
3939 member_ids.push(from_id);
3940 }
3941 if !mime_parser.was_encrypted() {
3942 member_ids.push(ContactId::SELF);
3943 }
3944
3945 if mime_parser.is_mailinglist_message() {
3946 return Ok(None);
3947 }
3948 if mime_parser
3949 .get_header(HeaderDef::ChatGroupMemberRemoved)
3950 .is_some()
3951 {
3952 info!(
3953 context,
3954 "Message removes member from unknown ad-hoc group (TRASH)."
3955 );
3956 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3957 }
3958
3959 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3960 context,
3961 Chattype::Group,
3962 "", grpname,
3964 create_blocked,
3965 None,
3966 mime_parser.timestamp_sent,
3967 )
3968 .await?;
3969
3970 info!(
3971 context,
3972 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3973 );
3974 chat::add_to_chat_contacts_table(
3975 context,
3976 mime_parser.timestamp_sent,
3977 new_chat_id,
3978 &member_ids,
3979 )
3980 .await?;
3981
3982 context.emit_event(EventType::ChatModified(new_chat_id));
3983 chatlist_events::emit_chatlist_changed(context);
3984 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3985
3986 Ok(Some((new_chat_id, create_blocked)))
3987}
3988
3989#[derive(Debug, PartialEq, Eq)]
3990enum VerifiedEncryption {
3991 Verified,
3992 NotVerified(String), }
3994
3995async fn has_verified_encryption(
3999 context: &Context,
4000 mimeparser: &MimeMessage,
4001 from_id: ContactId,
4002) -> Result<VerifiedEncryption> {
4003 use VerifiedEncryption::*;
4004
4005 if !mimeparser.was_encrypted() {
4006 return Ok(NotVerified("This message is not encrypted".to_string()));
4007 };
4008
4009 if from_id == ContactId::SELF {
4010 return Ok(Verified);
4011 }
4012
4013 let from_contact = Contact::get_by_id(context, from_id).await?;
4014
4015 let Some(fingerprint) = from_contact.fingerprint() else {
4016 return Ok(NotVerified(
4017 "The message was sent without encryption".to_string(),
4018 ));
4019 };
4020
4021 if from_contact.get_verifier_id(context).await?.is_none() {
4022 return Ok(NotVerified(
4023 "The message was sent by non-verified contact".to_string(),
4024 ));
4025 }
4026
4027 let signed_with_verified_key = mimeparser
4028 .signature
4029 .as_ref()
4030 .is_some_and(|(signature, _)| *signature == fingerprint);
4031 if signed_with_verified_key {
4032 Ok(Verified)
4033 } else {
4034 Ok(NotVerified(
4035 "The message was sent with non-verified encryption".to_string(),
4036 ))
4037 }
4038}
4039
4040async fn mark_recipients_as_verified(
4041 context: &Context,
4042 from_id: ContactId,
4043 mimeparser: &MimeMessage,
4044) -> Result<()> {
4045 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
4046
4047 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
4051
4052 for gossiped_key in mimeparser
4053 .gossiped_keys
4054 .values()
4055 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
4056 {
4057 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
4058 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
4059 continue;
4060 };
4061
4062 if to_id == ContactId::SELF || to_id == from_id {
4063 continue;
4064 }
4065
4066 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
4067 }
4068
4069 Ok(())
4070}
4071
4072async fn get_previous_message(
4076 context: &Context,
4077 mime_parser: &MimeMessage,
4078) -> Result<Option<Message>> {
4079 if let Some(field) = mime_parser.get_header(HeaderDef::References)
4080 && let Some(rfc724mid) = parse_message_ids(field).last()
4081 && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
4082 {
4083 return Message::load_from_db_optional(context, msg_id).await;
4084 }
4085 Ok(None)
4086}
4087
4088async fn get_parent_message(
4093 context: &Context,
4094 references: Option<&str>,
4095 in_reply_to: Option<&str>,
4096) -> Result<Option<Message>> {
4097 let mut mids = Vec::new();
4098 if let Some(field) = in_reply_to {
4099 mids = parse_message_ids(field);
4100 }
4101 if let Some(field) = references {
4102 mids.append(&mut parse_message_ids(field));
4103 }
4104 message::get_by_rfc724_mids(context, &mids).await
4105}
4106
4107pub(crate) async fn get_prefetch_parent_message(
4108 context: &Context,
4109 headers: &[mailparse::MailHeader<'_>],
4110) -> Result<Option<Message>> {
4111 get_parent_message(
4112 context,
4113 headers.get_header_value(HeaderDef::References).as_deref(),
4114 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
4115 )
4116 .await
4117}
4118
4119async fn add_or_lookup_contacts_by_address_list(
4121 context: &Context,
4122 address_list: &[SingleInfo],
4123 origin: Origin,
4124) -> Result<Vec<Option<ContactId>>> {
4125 let mut contact_ids = Vec::new();
4126 for info in address_list {
4127 let addr = &info.addr;
4128 if !may_be_valid_addr(addr) {
4129 contact_ids.push(None);
4130 continue;
4131 }
4132 let display_name = info.display_name.as_deref();
4133 if let Ok(addr) = ContactAddress::new(addr) {
4134 let (contact_id, _) =
4135 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
4136 .await?;
4137 contact_ids.push(Some(contact_id));
4138 } else {
4139 warn!(context, "Contact with address {:?} cannot exist.", addr);
4140 contact_ids.push(None);
4141 }
4142 }
4143
4144 Ok(contact_ids)
4145}
4146
4147async fn add_or_lookup_key_contacts(
4149 context: &Context,
4150 address_list: &[SingleInfo],
4151 gossiped_keys: &BTreeMap<String, GossipedKey>,
4152 fingerprints: &[Fingerprint],
4153 origin: Origin,
4154) -> Result<Vec<Option<ContactId>>> {
4155 let mut contact_ids = Vec::new();
4156 let mut fingerprint_iter = fingerprints.iter();
4157 for info in address_list {
4158 let fp = fingerprint_iter.next();
4159 let addr = &info.addr;
4160 if !may_be_valid_addr(addr) {
4161 contact_ids.push(None);
4162 continue;
4163 }
4164 let fingerprint: String = if let Some(fp) = fp {
4165 fp.hex()
4167 } else if let Some(key) = gossiped_keys.get(addr) {
4168 key.public_key.dc_fingerprint().hex()
4169 } else if context.is_self_addr(addr).await? {
4170 contact_ids.push(Some(ContactId::SELF));
4171 continue;
4172 } else {
4173 contact_ids.push(None);
4174 continue;
4175 };
4176 let display_name = info.display_name.as_deref();
4177 if let Ok(addr) = ContactAddress::new(addr) {
4178 let (contact_id, _) = Contact::add_or_lookup_ex(
4179 context,
4180 display_name.unwrap_or_default(),
4181 &addr,
4182 &fingerprint,
4183 origin,
4184 )
4185 .await?;
4186 contact_ids.push(Some(contact_id));
4187 } else {
4188 warn!(context, "Contact with address {:?} cannot exist.", addr);
4189 contact_ids.push(None);
4190 }
4191 }
4192
4193 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
4194 Ok(contact_ids)
4195}
4196
4197async fn lookup_key_contact_by_address(
4202 context: &Context,
4203 addr: &str,
4204 chat_id: Option<ChatId>,
4205) -> Result<Option<ContactId>> {
4206 if context.is_self_addr(addr).await? {
4207 if chat_id.is_none() {
4208 return Ok(Some(ContactId::SELF));
4209 }
4210 let is_self_in_chat = context
4211 .sql
4212 .exists(
4213 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
4214 (chat_id,),
4215 )
4216 .await?;
4217 if is_self_in_chat {
4218 return Ok(Some(ContactId::SELF));
4219 }
4220 }
4221 let contact_id: Option<ContactId> = match chat_id {
4222 Some(chat_id) => {
4223 context
4224 .sql
4225 .query_row_optional(
4226 "SELECT id FROM contacts
4227 WHERE contacts.addr=?
4228 AND EXISTS (SELECT 1 FROM chats_contacts
4229 WHERE contact_id=contacts.id
4230 AND chat_id=?)
4231 AND fingerprint<>'' -- Should always be true
4232 ",
4233 (addr, chat_id),
4234 |row| {
4235 let contact_id: ContactId = row.get(0)?;
4236 Ok(contact_id)
4237 },
4238 )
4239 .await?
4240 }
4241 None => {
4242 context
4243 .sql
4244 .query_row_optional(
4245 "SELECT id FROM contacts
4246 WHERE addr=?
4247 AND fingerprint<>''
4248 ORDER BY
4249 (
4250 SELECT COUNT(*) FROM chats c
4251 INNER JOIN chats_contacts cc
4252 ON c.id=cc.chat_id
4253 WHERE c.type=?
4254 AND c.id>?
4255 AND c.blocked=?
4256 AND cc.contact_id=contacts.id
4257 ) DESC,
4258 last_seen DESC, id DESC
4259 ",
4260 (
4261 addr,
4262 Chattype::Single,
4263 constants::DC_CHAT_ID_LAST_SPECIAL,
4264 Blocked::Not,
4265 ),
4266 |row| {
4267 let contact_id: ContactId = row.get(0)?;
4268 Ok(contact_id)
4269 },
4270 )
4271 .await?
4272 }
4273 };
4274 Ok(contact_id)
4275}
4276
4277async fn lookup_key_contact_by_fingerprint(
4278 context: &Context,
4279 fingerprint: &str,
4280) -> Result<Option<ContactId>> {
4281 logged_debug_assert!(
4282 context,
4283 !fingerprint.is_empty(),
4284 "lookup_key_contact_by_fingerprint: fingerprint is empty."
4285 );
4286 if fingerprint.is_empty() {
4287 return Ok(None);
4289 }
4290 if let Some(contact_id) = context
4291 .sql
4292 .query_row_optional(
4293 "SELECT id FROM contacts
4294 WHERE fingerprint=? AND fingerprint!=''",
4295 (fingerprint,),
4296 |row| {
4297 let contact_id: ContactId = row.get(0)?;
4298 Ok(contact_id)
4299 },
4300 )
4301 .await?
4302 {
4303 Ok(Some(contact_id))
4304 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4305 if self_fp == fingerprint {
4306 Ok(Some(ContactId::SELF))
4307 } else {
4308 Ok(None)
4309 }
4310 } else {
4311 Ok(None)
4312 }
4313}
4314
4315async fn lookup_key_contacts_fallback_to_chat(
4331 context: &Context,
4332 address_list: &[SingleInfo],
4333 fingerprints: &[Fingerprint],
4334 chat_id: Option<ChatId>,
4335) -> Result<Vec<Option<ContactId>>> {
4336 let mut contact_ids = Vec::new();
4337 let mut fingerprint_iter = fingerprints.iter();
4338 for info in address_list {
4339 let fp = fingerprint_iter.next();
4340 let addr = &info.addr;
4341 if !may_be_valid_addr(addr) {
4342 contact_ids.push(None);
4343 continue;
4344 }
4345
4346 if let Some(fp) = fp {
4347 let display_name = info.display_name.as_deref();
4349 let fingerprint: String = fp.hex();
4350
4351 if let Ok(addr) = ContactAddress::new(addr) {
4352 let (contact_id, _) = Contact::add_or_lookup_ex(
4353 context,
4354 display_name.unwrap_or_default(),
4355 &addr,
4356 &fingerprint,
4357 Origin::Hidden,
4358 )
4359 .await?;
4360 contact_ids.push(Some(contact_id));
4361 } else {
4362 warn!(context, "Contact with address {:?} cannot exist.", addr);
4363 contact_ids.push(None);
4364 }
4365 } else {
4366 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4367 contact_ids.push(contact_id);
4368 }
4369 }
4370 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4371 Ok(contact_ids)
4372}
4373
4374fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4377 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4378 || mime_parser.get_header(HeaderDef::Sender).is_some()
4379}
4380
4381#[cfg(test)]
4382mod receive_imf_tests;