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