1use std::collections::{BTreeMap, HashSet};
4use std::iter;
5use std::sync::LazyLock;
6
7use anyhow::{Context as _, Result, ensure};
8use data_encoding::BASE32_NOPAD;
9use deltachat_contact_tools::{
10 ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_single_line,
11};
12use iroh_gossip::proto::TopicId;
13use mailparse::SingleInfo;
14use num_traits::FromPrimitive;
15use regex::Regex;
16
17use crate::chat::{self, Chat, ChatId, ChatIdBlocked, remove_from_chat_contacts_table};
18use crate::config::Config;
19use crate::constants::{self, Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
20use crate::contact::{self, Contact, ContactId, Origin, mark_contact_id_as_verified};
21use crate::context::Context;
22use crate::debug_logging::maybe_set_logging_xdc_inner;
23use crate::download::DownloadState;
24use crate::ephemeral::{Timer as EphemeralTimer, stock_ephemeral_timer_changed};
25use crate::events::EventType;
26use crate::headerdef::{HeaderDef, HeaderDefMap};
27use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table};
28use crate::key::self_fingerprint_opt;
29use crate::key::{DcKey, Fingerprint};
30use crate::log::LogExt;
31use crate::log::{info, warn};
32use crate::logged_debug_assert;
33use crate::message::{
34 self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
35};
36use crate::mimeparser::{AvatarAction, GossipedKey, MimeMessage, SystemMessage, parse_message_ids};
37use crate::param::{Param, Params};
38use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub};
39use crate::reaction::{Reaction, set_msg_reaction};
40use crate::rusqlite::OptionalExtension;
41use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on_other_device};
42use crate::simplify;
43use crate::stats::STATISTICS_BOT_EMAIL;
44use crate::stock_str;
45use crate::sync::Sync::*;
46use crate::tools::{self, buf_compress, remove_subject_prefix};
47use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
48
49#[derive(Debug)]
54pub struct ReceivedMsg {
55 pub chat_id: ChatId,
57
58 pub state: MessageState,
60
61 pub hidden: bool,
63
64 pub sort_timestamp: i64,
66
67 pub msg_ids: Vec<MsgId>,
69
70 pub needs_delete_job: bool,
72}
73
74#[derive(Debug)]
87enum ChatAssignment {
88 Trash,
90
91 GroupChat { grpid: String },
96
97 MailingListOrBroadcast,
112
113 AdHocGroup,
117
118 ExistingChat {
121 chat_id: ChatId,
124
125 chat_id_blocked: Blocked,
133 },
134
135 OneOneChat,
143}
144
145#[cfg(any(test, feature = "internals"))]
150pub async fn receive_imf(
151 context: &Context,
152 imf_raw: &[u8],
153 seen: bool,
154) -> Result<Option<ReceivedMsg>> {
155 let mail = mailparse::parse_mail(imf_raw).context("can't parse mail")?;
156 let rfc724_mid = crate::imap::prefetch_get_message_id(&mail.headers)
157 .unwrap_or_else(crate::imap::create_message_id);
158 if let Some(download_limit) = context.download_limit().await? {
159 let download_limit: usize = download_limit.try_into()?;
160 if imf_raw.len() > download_limit {
161 let head = std::str::from_utf8(imf_raw)?
162 .split("\r\n\r\n")
163 .next()
164 .context("No empty line in the message")?;
165 return receive_imf_from_inbox(
166 context,
167 &rfc724_mid,
168 head.as_bytes(),
169 seen,
170 Some(imf_raw.len().try_into()?),
171 )
172 .await;
173 }
174 }
175 receive_imf_from_inbox(context, &rfc724_mid, imf_raw, seen, None).await
176}
177
178#[cfg(any(test, feature = "internals"))]
182pub(crate) async fn receive_imf_from_inbox(
183 context: &Context,
184 rfc724_mid: &str,
185 imf_raw: &[u8],
186 seen: bool,
187 is_partial_download: Option<u32>,
188) -> Result<Option<ReceivedMsg>> {
189 receive_imf_inner(
190 context,
191 rfc724_mid,
192 imf_raw,
193 seen,
194 is_partial_download.map(|msg_size| (msg_size, None)),
195 )
196 .await
197}
198
199async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
204 let row_id = context
205 .sql
206 .insert(
207 "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
208 (rfc724_mid, DC_CHAT_ID_TRASH),
209 )
210 .await?;
211 let msg_id = MsgId::new(u32::try_from(row_id)?);
212 Ok(msg_id)
213}
214
215async fn get_to_and_past_contact_ids(
216 context: &Context,
217 mime_parser: &MimeMessage,
218 chat_assignment: &ChatAssignment,
219 is_partial_download: Option<u32>,
220 parent_message: &Option<Message>,
221 incoming_origin: Origin,
222) -> Result<(Vec<Option<ContactId>>, Vec<Option<ContactId>>)> {
223 let to_ids: Vec<Option<ContactId>>;
235 let past_ids: Vec<Option<ContactId>>;
236
237 let chat_id = match chat_assignment {
244 ChatAssignment::Trash => None,
245 ChatAssignment::GroupChat { grpid } => {
246 if let Some((chat_id, _blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
247 Some(chat_id)
248 } else {
249 None
250 }
251 }
252 ChatAssignment::AdHocGroup => {
253 None
258 }
259 ChatAssignment::ExistingChat { chat_id, .. } => Some(*chat_id),
260 ChatAssignment::MailingListOrBroadcast => None,
261 ChatAssignment::OneOneChat => {
262 if is_partial_download.is_none() && !mime_parser.incoming {
263 parent_message.as_ref().map(|m| m.chat_id)
264 } else {
265 None
266 }
267 }
268 };
269
270 let member_fingerprints = mime_parser.chat_group_member_fingerprints();
271 let to_member_fingerprints;
272 let past_member_fingerprints;
273
274 if !member_fingerprints.is_empty() {
275 if member_fingerprints.len() >= mime_parser.recipients.len() {
276 (to_member_fingerprints, past_member_fingerprints) =
277 member_fingerprints.split_at(mime_parser.recipients.len());
278 } else {
279 warn!(
280 context,
281 "Unexpected length of the fingerprint header, expected at least {}, got {}.",
282 mime_parser.recipients.len(),
283 member_fingerprints.len()
284 );
285 to_member_fingerprints = &[];
286 past_member_fingerprints = &[];
287 }
288 } else {
289 to_member_fingerprints = &[];
290 past_member_fingerprints = &[];
291 }
292
293 match chat_assignment {
294 ChatAssignment::GroupChat { .. } => {
295 to_ids = add_or_lookup_key_contacts(
296 context,
297 &mime_parser.recipients,
298 &mime_parser.gossiped_keys,
299 to_member_fingerprints,
300 Origin::Hidden,
301 )
302 .await?;
303
304 if let Some(chat_id) = chat_id {
305 past_ids = lookup_key_contacts_by_address_list(
306 context,
307 &mime_parser.past_members,
308 past_member_fingerprints,
309 Some(chat_id),
310 )
311 .await?;
312 } else {
313 past_ids = add_or_lookup_key_contacts(
314 context,
315 &mime_parser.past_members,
316 &mime_parser.gossiped_keys,
317 past_member_fingerprints,
318 Origin::Hidden,
319 )
320 .await?;
321 }
322 }
323 ChatAssignment::Trash | ChatAssignment::MailingListOrBroadcast => {
324 to_ids = Vec::new();
325 past_ids = Vec::new();
326 }
327 ChatAssignment::ExistingChat { chat_id, .. } => {
328 let chat = Chat::load_from_db(context, *chat_id).await?;
329 if chat.is_encrypted(context).await? {
330 to_ids = add_or_lookup_key_contacts(
331 context,
332 &mime_parser.recipients,
333 &mime_parser.gossiped_keys,
334 to_member_fingerprints,
335 Origin::Hidden,
336 )
337 .await?;
338 past_ids = lookup_key_contacts_by_address_list(
339 context,
340 &mime_parser.past_members,
341 past_member_fingerprints,
342 Some(*chat_id),
343 )
344 .await?;
345 } else {
346 to_ids = add_or_lookup_contacts_by_address_list(
347 context,
348 &mime_parser.recipients,
349 if !mime_parser.incoming {
350 Origin::OutgoingTo
351 } else if incoming_origin.is_known() {
352 Origin::IncomingTo
353 } else {
354 Origin::IncomingUnknownTo
355 },
356 )
357 .await?;
358
359 past_ids = add_or_lookup_contacts_by_address_list(
360 context,
361 &mime_parser.past_members,
362 Origin::Hidden,
363 )
364 .await?;
365 }
366 }
367 ChatAssignment::AdHocGroup => {
368 to_ids = add_or_lookup_contacts_by_address_list(
369 context,
370 &mime_parser.recipients,
371 if !mime_parser.incoming {
372 Origin::OutgoingTo
373 } else if incoming_origin.is_known() {
374 Origin::IncomingTo
375 } else {
376 Origin::IncomingUnknownTo
377 },
378 )
379 .await?;
380
381 past_ids = add_or_lookup_contacts_by_address_list(
382 context,
383 &mime_parser.past_members,
384 Origin::Hidden,
385 )
386 .await?;
387 }
388 ChatAssignment::OneOneChat => {
389 let pgp_to_ids = add_or_lookup_key_contacts(
390 context,
391 &mime_parser.recipients,
392 &mime_parser.gossiped_keys,
393 to_member_fingerprints,
394 Origin::Hidden,
395 )
396 .await?;
397 if pgp_to_ids
398 .first()
399 .is_some_and(|contact_id| contact_id.is_some())
400 {
401 to_ids = pgp_to_ids
405 } else if let Some(chat_id) = chat_id {
406 to_ids = match mime_parser.was_encrypted() {
407 true => {
408 lookup_key_contacts_by_address_list(
409 context,
410 &mime_parser.recipients,
411 to_member_fingerprints,
412 Some(chat_id),
413 )
414 .await?
415 }
416 false => {
417 add_or_lookup_contacts_by_address_list(
418 context,
419 &mime_parser.recipients,
420 if !mime_parser.incoming {
421 Origin::OutgoingTo
422 } else if incoming_origin.is_known() {
423 Origin::IncomingTo
424 } else {
425 Origin::IncomingUnknownTo
426 },
427 )
428 .await?
429 }
430 }
431 } else {
432 let ids = match mime_parser.was_encrypted() {
433 true => {
434 lookup_key_contacts_by_address_list(
435 context,
436 &mime_parser.recipients,
437 to_member_fingerprints,
438 None,
439 )
440 .await?
441 }
442 false => vec![],
443 };
444 if mime_parser.was_encrypted() && !ids.contains(&None)
445 || ids
448 .iter()
449 .any(|&c| c.is_some() && c != Some(ContactId::SELF))
450 {
451 to_ids = ids;
452 } else {
453 to_ids = add_or_lookup_contacts_by_address_list(
454 context,
455 &mime_parser.recipients,
456 if !mime_parser.incoming {
457 Origin::OutgoingTo
458 } else if incoming_origin.is_known() {
459 Origin::IncomingTo
460 } else {
461 Origin::IncomingUnknownTo
462 },
463 )
464 .await?;
465 }
466 }
467
468 past_ids = add_or_lookup_contacts_by_address_list(
469 context,
470 &mime_parser.past_members,
471 Origin::Hidden,
472 )
473 .await?;
474 }
475 };
476
477 Ok((to_ids, past_ids))
478}
479
480pub(crate) async fn receive_imf_inner(
493 context: &Context,
494 rfc724_mid: &str,
495 imf_raw: &[u8],
496 seen: bool,
497 partial: Option<(u32, Option<String>)>,
498) -> Result<Option<ReceivedMsg>> {
499 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
500 info!(
501 context,
502 "receive_imf: incoming message mime-body:\n{}",
503 String::from_utf8_lossy(imf_raw),
504 );
505 }
506 if partial.is_none() {
507 ensure!(
508 !context
509 .get_config_bool(Config::FailOnReceivingFullMsg)
510 .await?
511 );
512 }
513
514 let is_partial_download = partial.as_ref().map(|(msg_size, _err)| *msg_size);
515 let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw, partial).await {
516 Err(err) => {
517 warn!(context, "receive_imf: can't parse MIME: {err:#}.");
518 if rfc724_mid.starts_with(GENERATED_PREFIX) {
519 return Ok(None);
521 }
522
523 let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
524
525 return Ok(Some(ReceivedMsg {
526 chat_id: DC_CHAT_ID_TRASH,
527 state: MessageState::Undefined,
528 hidden: false,
529 sort_timestamp: 0,
530 msg_ids,
531 needs_delete_job: false,
532 }));
533 }
534 Ok(mime_parser) => mime_parser,
535 };
536
537 let rfc724_mid_orig = &mime_parser
538 .get_rfc724_mid()
539 .unwrap_or(rfc724_mid.to_string());
540 info!(
541 context,
542 "Receiving message {rfc724_mid_orig:?}, seen={seen}...",
543 );
544
545 let (replace_msg_id, replace_chat_id);
548 if let Some(old_msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
549 replace_msg_id = Some(old_msg_id);
550 replace_chat_id = if let Some(msg) = Message::load_from_db_optional(context, old_msg_id)
551 .await?
552 .filter(|msg| msg.download_state() != DownloadState::Done)
553 {
554 info!(context, "Message already partly in DB, replacing.");
556 Some(msg.chat_id)
557 } else {
558 None
561 };
562 } else {
563 replace_msg_id = if rfc724_mid_orig == rfc724_mid {
564 None
565 } else {
566 message::rfc724_mid_exists(context, rfc724_mid_orig).await?
567 };
568 replace_chat_id = None;
569 }
570
571 if replace_chat_id.is_some() {
572 } else if let Some(msg_id) = replace_msg_id {
574 info!(context, "Message is already downloaded.");
575 if mime_parser.incoming {
576 return Ok(None);
577 }
578 let self_addr = context.get_primary_self_addr().await?;
581 context
582 .sql
583 .execute(
584 "DELETE FROM smtp \
585 WHERE rfc724_mid=?1 AND (recipients LIKE ?2 OR recipients LIKE ('% ' || ?2))",
586 (rfc724_mid_orig, &self_addr),
587 )
588 .await?;
589 if !context
590 .sql
591 .exists(
592 "SELECT COUNT(*) FROM smtp WHERE rfc724_mid=?",
593 (rfc724_mid_orig,),
594 )
595 .await?
596 {
597 msg_id.set_delivered(context).await?;
598 }
599 return Ok(None);
600 };
601
602 let prevent_rename = should_prevent_rename(&mime_parser);
603
604 let fingerprint = mime_parser.signature.as_ref();
616 let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
617 context,
618 &mime_parser.from,
619 fingerprint,
620 prevent_rename,
621 is_partial_download.is_some()
622 && mime_parser
623 .get_header(HeaderDef::ContentType)
624 .unwrap_or_default()
625 .starts_with("multipart/encrypted"),
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 chat_assignment = decide_chat_assignment(
658 context,
659 &mime_parser,
660 &parent_message,
661 rfc724_mid,
662 from_id,
663 &is_partial_download,
664 )
665 .await?;
666 info!(context, "Chat assignment is {chat_assignment:?}.");
667
668 let (to_ids, past_ids) = get_to_and_past_contact_ids(
669 context,
670 &mime_parser,
671 &chat_assignment,
672 is_partial_download,
673 &parent_message,
674 incoming_origin,
675 )
676 .await?;
677
678 let received_msg;
679 if mime_parser.get_header(HeaderDef::SecureJoin).is_some() {
680 let res = if mime_parser.incoming {
681 handle_securejoin_handshake(context, &mut mime_parser, from_id)
682 .await
683 .context("error in Secure-Join message handling")?
684 } else {
685 let to_id = to_ids.first().copied().flatten().unwrap_or(ContactId::SELF);
686 observe_securejoin_on_other_device(context, &mime_parser, to_id)
688 .await
689 .context("error in Secure-Join watching")?
690 };
691
692 match res {
693 securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
694 let msg_id = insert_tombstone(context, rfc724_mid).await?;
695 received_msg = Some(ReceivedMsg {
696 chat_id: DC_CHAT_ID_TRASH,
697 state: MessageState::InSeen,
698 hidden: false,
699 sort_timestamp: mime_parser.timestamp_sent,
700 msg_ids: vec![msg_id],
701 needs_delete_job: res == securejoin::HandshakeMessage::Done,
702 });
703 }
704 securejoin::HandshakeMessage::Propagate => {
705 received_msg = None;
706 }
707 }
708 } else {
709 received_msg = None;
710 }
711
712 let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?;
713
714 if verified_encryption == VerifiedEncryption::Verified {
715 mark_recipients_as_verified(context, from_id, &mime_parser).await?;
716 }
717
718 let is_old_contact_request;
719 let received_msg = if let Some(received_msg) = received_msg {
720 is_old_contact_request = false;
721 received_msg
722 } else {
723 let is_dc_message = if mime_parser.has_chat_version() {
724 MessengerMessage::Yes
725 } else if let Some(parent_message) = &parent_message {
726 match parent_message.is_dc_message {
727 MessengerMessage::No => MessengerMessage::No,
728 MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply,
729 }
730 } else {
731 MessengerMessage::No
732 };
733
734 let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
735 .unwrap_or_default();
736
737 let allow_creation = if mime_parser.decrypting_failed {
738 false
739 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
740 && is_dc_message == MessengerMessage::No
741 && !context.get_config_bool(Config::IsChatmail).await?
742 {
743 match show_emails {
746 ShowEmails::Off | ShowEmails::AcceptedContacts => false,
747 ShowEmails::All => true,
748 }
749 } else {
750 !mime_parser.parts.iter().all(|part| part.is_reaction)
751 };
752
753 let to_id = if mime_parser.incoming {
754 ContactId::SELF
755 } else {
756 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
757 };
758
759 let (chat_id, chat_id_blocked, is_created) = do_chat_assignment(
760 context,
761 &chat_assignment,
762 from_id,
763 &to_ids,
764 &past_ids,
765 to_id,
766 allow_creation,
767 &mut mime_parser,
768 is_partial_download,
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 is_partial_download,
785 replace_msg_id,
786 prevent_rename,
787 chat_id,
788 chat_id_blocked,
789 is_dc_message,
790 )
791 .await
792 .context("add_parts error")?
793 };
794
795 if !from_id.is_special() {
796 contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
797 }
798
799 let chat_id = received_msg.chat_id;
803 if !chat_id.is_special() {
804 for gossiped_key in mime_parser.gossiped_keys.values() {
805 context
806 .sql
807 .transaction(move |transaction| {
808 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
809 transaction.execute(
810 "INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
811 VALUES (?, ?, ?)
812 ON CONFLICT (chat_id, fingerprint)
813 DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
814 (chat_id, &fingerprint, mime_parser.timestamp_sent),
815 )?;
816
817 Ok(())
818 })
819 .await?;
820 }
821 }
822
823 let insert_msg_id = if let Some(msg_id) = received_msg.msg_ids.last() {
824 *msg_id
825 } else {
826 MsgId::new_unset()
827 };
828
829 save_locations(context, &mime_parser, chat_id, from_id, insert_msg_id).await?;
830
831 if let Some(ref sync_items) = mime_parser.sync_items {
832 if from_id == ContactId::SELF {
833 if mime_parser.was_encrypted() {
834 context
835 .execute_sync_items(sync_items, mime_parser.timestamp_sent)
836 .await;
837 } else {
838 warn!(context, "Sync items are not encrypted.");
839 }
840 } else {
841 warn!(context, "Sync items not sent by self.");
842 }
843 }
844
845 if let Some(ref status_update) = mime_parser.webxdc_status_update {
846 let can_info_msg;
847 let instance = if mime_parser
848 .parts
849 .first()
850 .filter(|part| part.typ == Viewtype::Webxdc)
851 .is_some()
852 {
853 can_info_msg = false;
854 Some(Message::load_from_db(context, insert_msg_id).await?)
855 } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
856 if let Some(instance) =
857 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
858 {
859 can_info_msg = instance.download_state() == DownloadState::Done;
860 Some(instance)
861 } else {
862 can_info_msg = false;
863 None
864 }
865 } else {
866 can_info_msg = false;
867 None
868 };
869
870 if let Some(instance) = instance {
871 if let Err(err) = context
872 .receive_status_update(
873 from_id,
874 &instance,
875 received_msg.sort_timestamp,
876 can_info_msg,
877 status_update,
878 )
879 .await
880 {
881 warn!(context, "receive_imf cannot update status: {err:#}.");
882 }
883 } else {
884 warn!(
885 context,
886 "Received webxdc update, but cannot assign it to message."
887 );
888 }
889 }
890
891 if let Some(avatar_action) = &mime_parser.user_avatar {
892 if from_id != ContactId::UNDEFINED
893 && context
894 .update_contacts_timestamp(
895 from_id,
896 Param::AvatarTimestamp,
897 mime_parser.timestamp_sent,
898 )
899 .await?
900 {
901 if let Err(err) = contact::set_profile_image(
902 context,
903 from_id,
904 avatar_action,
905 mime_parser.was_encrypted(),
906 )
907 .await
908 {
909 warn!(context, "receive_imf cannot update profile image: {err:#}.");
910 };
911 }
912 }
913
914 if let Some(footer) = &mime_parser.footer {
916 if !mime_parser.is_mailinglist_message()
917 && from_id != ContactId::UNDEFINED
918 && context
919 .update_contacts_timestamp(
920 from_id,
921 Param::StatusTimestamp,
922 mime_parser.timestamp_sent,
923 )
924 .await?
925 {
926 if let Err(err) = contact::set_status(
927 context,
928 from_id,
929 footer.to_string(),
930 mime_parser.was_encrypted(),
931 mime_parser.has_chat_version(),
932 )
933 .await
934 {
935 warn!(context, "Cannot update contact status: {err:#}.");
936 }
937 }
938 }
939
940 let delete_server_after = context.get_config_delete_server_after().await?;
942
943 if !received_msg.msg_ids.is_empty() {
944 let target = if received_msg.needs_delete_job
945 || (delete_server_after == Some(0) && is_partial_download.is_none())
946 {
947 Some(context.get_delete_msgs_target().await?)
948 } else {
949 None
950 };
951 if target.is_some() || rfc724_mid_orig != rfc724_mid {
952 let target_subst = match &target {
953 Some(_) => "target=?1,",
954 None => "",
955 };
956 context
957 .sql
958 .execute(
959 &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
960 (
961 target.as_deref().unwrap_or_default(),
962 rfc724_mid_orig,
963 rfc724_mid,
964 ),
965 )
966 .await?;
967 }
968 if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
969 {
970 markseen_on_imap_table(context, rfc724_mid_orig).await?;
972 }
973 }
974
975 if is_partial_download.is_none() && mime_parser.is_call() {
976 context
977 .handle_call_msg(insert_msg_id, &mime_parser, from_id)
978 .await?;
979 } else if received_msg.hidden {
980 } else if let Some(replace_chat_id) = replace_chat_id {
982 match replace_chat_id == chat_id {
983 false => context.emit_msgs_changed_without_msg_id(replace_chat_id),
984 true => context.emit_msgs_changed(chat_id, replace_msg_id.unwrap_or_default()),
985 }
986 } else if !chat_id.is_trash() {
987 let fresh = received_msg.state == MessageState::InFresh
988 && mime_parser.is_system_message != SystemMessage::CallAccepted
989 && mime_parser.is_system_message != SystemMessage::CallEnded;
990 let important = mime_parser.incoming && fresh && !is_old_contact_request;
991 for msg_id in &received_msg.msg_ids {
992 chat_id.emit_msg_event(context, *msg_id, important);
993 }
994 }
995 context.new_msgs_notify.notify_one();
996
997 mime_parser
998 .handle_reports(context, from_id, &mime_parser.parts)
999 .await;
1000
1001 if let Some(is_bot) = mime_parser.is_bot {
1002 if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
1005 from_id.mark_bot(context, is_bot).await?;
1006 }
1007 }
1008
1009 Ok(Some(received_msg))
1010}
1011
1012pub async fn from_field_to_contact_id(
1030 context: &Context,
1031 from: &SingleInfo,
1032 fingerprint: Option<&Fingerprint>,
1033 prevent_rename: bool,
1034 find_key_contact_by_addr: bool,
1035) -> Result<Option<(ContactId, bool, Origin)>> {
1036 let fingerprint = fingerprint.as_ref().map(|fp| fp.hex()).unwrap_or_default();
1037 let display_name = if prevent_rename {
1038 Some("")
1039 } else {
1040 from.display_name.as_deref()
1041 };
1042 let from_addr = match ContactAddress::new(&from.addr) {
1043 Ok(from_addr) => from_addr,
1044 Err(err) => {
1045 warn!(
1046 context,
1047 "Cannot create a contact for the given From field: {err:#}."
1048 );
1049 return Ok(None);
1050 }
1051 };
1052
1053 if fingerprint.is_empty() && find_key_contact_by_addr {
1054 let addr_normalized = addr_normalize(&from_addr);
1055
1056 if let Some((from_id, origin)) = context
1058 .sql
1059 .query_row_optional(
1060 "SELECT id, origin FROM contacts
1061 WHERE addr=?1 COLLATE NOCASE
1062 AND fingerprint<>'' -- Only key-contacts
1063 AND id>?2 AND origin>=?3 AND blocked=?4
1064 ORDER BY last_seen DESC
1065 LIMIT 1",
1066 (
1067 &addr_normalized,
1068 ContactId::LAST_SPECIAL,
1069 Origin::IncomingUnknownFrom,
1070 Blocked::Not,
1071 ),
1072 |row| {
1073 let id: ContactId = row.get(0)?;
1074 let origin: Origin = row.get(1)?;
1075 Ok((id, origin))
1076 },
1077 )
1078 .await?
1079 {
1080 return Ok(Some((from_id, false, origin)));
1081 }
1082 }
1083
1084 let (from_id, _) = Contact::add_or_lookup_ex(
1085 context,
1086 display_name.unwrap_or_default(),
1087 &from_addr,
1088 &fingerprint,
1089 Origin::IncomingUnknownFrom,
1090 )
1091 .await?;
1092
1093 if from_id == ContactId::SELF {
1094 Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
1095 } else {
1096 let contact = Contact::get_by_id(context, from_id).await?;
1097 let from_id_blocked = contact.blocked;
1098 let incoming_origin = contact.origin;
1099
1100 context
1101 .sql
1102 .execute(
1103 "UPDATE contacts SET addr=? WHERE id=?",
1104 (from_addr, from_id),
1105 )
1106 .await?;
1107
1108 Ok(Some((from_id, from_id_blocked, incoming_origin)))
1109 }
1110}
1111
1112async fn decide_chat_assignment(
1113 context: &Context,
1114 mime_parser: &MimeMessage,
1115 parent_message: &Option<Message>,
1116 rfc724_mid: &str,
1117 from_id: ContactId,
1118 is_partial_download: &Option<u32>,
1119) -> Result<ChatAssignment> {
1120 let should_trash = if !mime_parser.mdn_reports.is_empty() {
1121 info!(context, "Message is an MDN (TRASH).");
1122 true
1123 } else if mime_parser.delivery_report.is_some() {
1124 info!(context, "Message is a DSN (TRASH).");
1125 markseen_on_imap_table(context, rfc724_mid).await.ok();
1126 true
1127 } else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
1128 || mime_parser.get_header(HeaderDef::ChatDelete).is_some()
1129 || mime_parser.get_header(HeaderDef::IrohNodeAddr).is_some()
1130 || mime_parser.sync_items.is_some()
1131 {
1132 info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
1133 true
1134 } else if is_partial_download.is_none()
1135 && (mime_parser.is_system_message == SystemMessage::CallAccepted
1136 || mime_parser.is_system_message == SystemMessage::CallEnded)
1137 {
1138 info!(context, "Call state changed (TRASH).");
1139 true
1140 } else if mime_parser.decrypting_failed && !mime_parser.incoming {
1141 let last_time = context
1143 .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1144 .await?;
1145 let now = tools::time();
1146 let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1147 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.";
1148 let mut msg = Message::new_text(txt.to_string());
1149 chat::add_device_msg(context, None, Some(&mut msg))
1150 .await
1151 .log_err(context)
1152 .ok();
1153 true
1154 } else {
1155 last_time > now
1156 };
1157 if update_config {
1158 context
1159 .set_config_internal(Config::LastCantDecryptOutgoingMsgs, Some(&now.to_string()))
1160 .await?;
1161 }
1162 info!(context, "Outgoing undecryptable message (TRASH).");
1163 true
1164 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
1165 && !mime_parser.has_chat_version()
1166 && parent_message
1167 .as_ref()
1168 .is_none_or(|p| p.is_dc_message == MessengerMessage::No)
1169 && !context.get_config_bool(Config::IsChatmail).await?
1170 && ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
1171 .unwrap_or_default()
1172 == ShowEmails::Off
1173 {
1174 info!(context, "Classical email not shown (TRASH).");
1175 true
1178 } else if mime_parser
1179 .get_header(HeaderDef::XMozillaDraftInfo)
1180 .is_some()
1181 {
1182 info!(context, "Email is probably just a draft (TRASH).");
1188 true
1189 } else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1190 if let Some(part) = mime_parser.parts.first() {
1191 if part.typ == Viewtype::Text && part.msg.is_empty() {
1192 info!(context, "Message is a status update only (TRASH).");
1193 markseen_on_imap_table(context, rfc724_mid).await.ok();
1194 true
1195 } else {
1196 false
1197 }
1198 } else {
1199 false
1200 }
1201 } else {
1202 false
1203 };
1204
1205 let mut num_recipients = 0;
1210 let mut has_self_addr = false;
1211 for recipient in &mime_parser.recipients {
1212 if addr_cmp(&recipient.addr, &mime_parser.from.addr) {
1213 continue;
1214 }
1215
1216 if context.is_self_addr(&recipient.addr).await? {
1217 has_self_addr = true;
1218 }
1219
1220 num_recipients += 1;
1221 }
1222 if from_id != ContactId::SELF && !has_self_addr {
1223 num_recipients += 1;
1224 }
1225
1226 let chat_assignment = if should_trash {
1227 ChatAssignment::Trash
1228 } else if mime_parser.get_mailinglist_header().is_some() {
1229 ChatAssignment::MailingListOrBroadcast
1230 } else if let Some(grpid) = mime_parser.get_chat_group_id() {
1231 if mime_parser.was_encrypted() {
1232 ChatAssignment::GroupChat {
1233 grpid: grpid.to_string(),
1234 }
1235 } else if let Some(parent) = &parent_message {
1236 if let Some((chat_id, chat_id_blocked)) =
1237 lookup_chat_by_reply(context, mime_parser, parent, is_partial_download).await?
1238 {
1239 ChatAssignment::ExistingChat {
1241 chat_id,
1242 chat_id_blocked,
1243 }
1244 } else {
1245 ChatAssignment::AdHocGroup
1246 }
1247 } else {
1248 ChatAssignment::AdHocGroup
1256 }
1257 } else if let Some(parent) = &parent_message {
1258 if let Some((chat_id, chat_id_blocked)) =
1259 lookup_chat_by_reply(context, mime_parser, parent, is_partial_download).await?
1260 {
1261 ChatAssignment::ExistingChat {
1263 chat_id,
1264 chat_id_blocked,
1265 }
1266 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1267 ChatAssignment::AdHocGroup
1268 } else if num_recipients <= 1 {
1269 ChatAssignment::OneOneChat
1270 } else {
1271 ChatAssignment::AdHocGroup
1272 }
1273 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1274 ChatAssignment::AdHocGroup
1275 } else if num_recipients <= 1 {
1276 ChatAssignment::OneOneChat
1277 } else {
1278 ChatAssignment::AdHocGroup
1279 };
1280 Ok(chat_assignment)
1281}
1282
1283#[expect(clippy::too_many_arguments)]
1292async fn do_chat_assignment(
1293 context: &Context,
1294 chat_assignment: &ChatAssignment,
1295 from_id: ContactId,
1296 to_ids: &[Option<ContactId>],
1297 past_ids: &[Option<ContactId>],
1298 to_id: ContactId,
1299 allow_creation: bool,
1300 mime_parser: &mut MimeMessage,
1301 is_partial_download: Option<u32>,
1302 parent_message: Option<Message>,
1303) -> Result<(ChatId, Blocked, bool)> {
1304 let is_bot = context.get_config_bool(Config::Bot).await?;
1305
1306 let mut chat_id = None;
1307 let mut chat_id_blocked = Blocked::Not;
1308 let mut chat_created = false;
1309
1310 if mime_parser.incoming {
1311 let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
1312
1313 let create_blocked_default = if is_bot {
1314 Blocked::Not
1315 } else {
1316 Blocked::Request
1317 };
1318 let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
1319 match blocked {
1320 Blocked::Request => create_blocked_default,
1321 Blocked::Not => Blocked::Not,
1322 Blocked::Yes => {
1323 if Contact::is_blocked_load(context, from_id).await? {
1324 Blocked::Yes
1327 } else {
1328 create_blocked_default
1332 }
1333 }
1334 }
1335 } else {
1336 create_blocked_default
1337 };
1338
1339 match &chat_assignment {
1340 ChatAssignment::Trash => {
1341 chat_id = Some(DC_CHAT_ID_TRASH);
1342 }
1343 ChatAssignment::GroupChat { grpid } => {
1344 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1346 chat_id = Some(id);
1347 chat_id_blocked = blocked;
1348 } else if allow_creation || test_normal_chat.is_some() {
1349 if let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1350 context,
1351 mime_parser,
1352 is_partial_download.is_some(),
1353 create_blocked,
1354 from_id,
1355 to_ids,
1356 past_ids,
1357 grpid,
1358 )
1359 .await?
1360 {
1361 chat_id = Some(new_chat_id);
1362 chat_id_blocked = new_chat_id_blocked;
1363 chat_created = true;
1364 }
1365 }
1366 }
1367 ChatAssignment::MailingListOrBroadcast => {
1368 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1369 if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1370 create_or_lookup_mailinglist_or_broadcast(
1371 context,
1372 allow_creation,
1373 mailinglist_header,
1374 from_id,
1375 mime_parser,
1376 )
1377 .await?
1378 {
1379 chat_id = Some(new_chat_id);
1380 chat_id_blocked = new_chat_id_blocked;
1381 chat_created = new_chat_created;
1382
1383 apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
1384 }
1385 }
1386 }
1387 ChatAssignment::ExistingChat {
1388 chat_id: new_chat_id,
1389 chat_id_blocked: new_chat_id_blocked,
1390 } => {
1391 chat_id = Some(*new_chat_id);
1392 chat_id_blocked = *new_chat_id_blocked;
1393 }
1394 ChatAssignment::AdHocGroup => {
1395 if let Some((new_chat_id, new_chat_id_blocked, new_created)) =
1396 lookup_or_create_adhoc_group(
1397 context,
1398 mime_parser,
1399 to_ids,
1400 allow_creation || test_normal_chat.is_some(),
1401 create_blocked,
1402 is_partial_download.is_some(),
1403 )
1404 .await?
1405 {
1406 chat_id = Some(new_chat_id);
1407 chat_id_blocked = new_chat_id_blocked;
1408 chat_created = new_created;
1409 }
1410 }
1411 ChatAssignment::OneOneChat => {}
1412 }
1413
1414 if chat_id_blocked != Blocked::Not
1417 && create_blocked != Blocked::Yes
1418 && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
1419 {
1420 if let Some(chat_id) = chat_id {
1421 chat_id.set_blocked(context, create_blocked).await?;
1422 chat_id_blocked = create_blocked;
1423 }
1424 }
1425
1426 if chat_id.is_none() {
1427 let contact = Contact::get_by_id(context, from_id).await?;
1429 let create_blocked = match contact.is_blocked() {
1430 true => Blocked::Yes,
1431 false if is_bot => Blocked::Not,
1432 false => Blocked::Request,
1433 };
1434
1435 if let Some(chat) = test_normal_chat {
1436 chat_id = Some(chat.id);
1437 chat_id_blocked = chat.blocked;
1438 } else if allow_creation {
1439 let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
1440 .await
1441 .context("Failed to get (new) chat for contact")?;
1442 chat_id = Some(chat.id);
1443 chat_id_blocked = chat.blocked;
1444 chat_created = true;
1445 }
1446
1447 if let Some(chat_id) = chat_id {
1448 if chat_id_blocked != Blocked::Not {
1449 if chat_id_blocked != create_blocked {
1450 chat_id.set_blocked(context, create_blocked).await?;
1451 }
1452 if create_blocked == Blocked::Request && parent_message.is_some() {
1453 ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo)
1456 .await?;
1457 info!(
1458 context,
1459 "Message is a reply to a known message, mark sender as known.",
1460 );
1461 }
1462 }
1463 }
1464 }
1465 } else {
1466 let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1473
1474 match &chat_assignment {
1475 ChatAssignment::Trash => {
1476 chat_id = Some(DC_CHAT_ID_TRASH);
1477 }
1478 ChatAssignment::GroupChat { grpid } => {
1479 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1480 chat_id = Some(id);
1481 chat_id_blocked = blocked;
1482 } else if allow_creation {
1483 if let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1484 context,
1485 mime_parser,
1486 is_partial_download.is_some(),
1487 Blocked::Not,
1488 from_id,
1489 to_ids,
1490 past_ids,
1491 grpid,
1492 )
1493 .await?
1494 {
1495 chat_id = Some(new_chat_id);
1496 chat_id_blocked = new_chat_id_blocked;
1497 chat_created = true;
1498 }
1499 }
1500 }
1501 ChatAssignment::ExistingChat {
1502 chat_id: new_chat_id,
1503 chat_id_blocked: new_chat_id_blocked,
1504 } => {
1505 chat_id = Some(*new_chat_id);
1506 chat_id_blocked = *new_chat_id_blocked;
1507 }
1508 ChatAssignment::MailingListOrBroadcast => {
1509 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1512 let listid = mailinglist_header_listid(mailinglist_header)?;
1513 chat_id = Some(
1514 if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await?
1515 {
1516 id
1517 } else {
1518 chat_created = true;
1519 let name =
1520 compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1521 chat::create_broadcast_ex(context, Nosync, listid, name).await?
1522 },
1523 );
1524 }
1525 }
1526 ChatAssignment::AdHocGroup => {
1527 if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1528 lookup_or_create_adhoc_group(
1529 context,
1530 mime_parser,
1531 to_ids,
1532 allow_creation,
1533 Blocked::Not,
1534 is_partial_download.is_some(),
1535 )
1536 .await?
1537 {
1538 chat_id = Some(new_chat_id);
1539 chat_id_blocked = new_chat_id_blocked;
1540 chat_created = new_chat_created;
1541 }
1542 }
1543 ChatAssignment::OneOneChat => {}
1544 }
1545
1546 if !to_ids.is_empty() {
1547 if chat_id.is_none() && allow_creation {
1548 let to_contact = Contact::get_by_id(context, to_id).await?;
1549 if let Some(list_id) = to_contact.param.get(Param::ListId) {
1550 if let Some((id, blocked)) =
1551 chat::get_chat_id_by_grpid(context, list_id).await?
1552 {
1553 chat_id = Some(id);
1554 chat_id_blocked = blocked;
1555 }
1556 } else {
1557 let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1558 chat_id = Some(chat.id);
1559 chat_id_blocked = chat.blocked;
1560 chat_created = true;
1561 }
1562 }
1563 if chat_id.is_none() && mime_parser.has_chat_version() {
1564 if let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await? {
1565 chat_id = Some(chat.id);
1566 chat_id_blocked = chat.blocked;
1567 }
1568 }
1569 }
1570
1571 if chat_id.is_none() && self_sent {
1572 let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1575 .await
1576 .context("Failed to get (new) chat for contact")?;
1577
1578 chat_id = Some(chat.id);
1579 chat_id_blocked = chat.blocked;
1580
1581 if Blocked::Not != chat.blocked {
1582 chat.id.unblock_ex(context, Nosync).await?;
1583 }
1584 }
1585
1586 if chat_id_blocked != Blocked::Not {
1588 if let Some(chat_id) = chat_id {
1589 chat_id.unblock_ex(context, Nosync).await?;
1590 chat_id_blocked = Blocked::Not;
1591 }
1592 }
1593 }
1594 let chat_id = chat_id.unwrap_or_else(|| {
1595 info!(context, "No chat id for message (TRASH).");
1596 DC_CHAT_ID_TRASH
1597 });
1598 Ok((chat_id, chat_id_blocked, chat_created))
1599}
1600
1601#[expect(clippy::too_many_arguments)]
1605async fn add_parts(
1606 context: &Context,
1607 mime_parser: &mut MimeMessage,
1608 imf_raw: &[u8],
1609 to_ids: &[Option<ContactId>],
1610 past_ids: &[Option<ContactId>],
1611 rfc724_mid: &str,
1612 from_id: ContactId,
1613 seen: bool,
1614 is_partial_download: Option<u32>,
1615 mut replace_msg_id: Option<MsgId>,
1616 prevent_rename: bool,
1617 chat_id: ChatId,
1618 chat_id_blocked: Blocked,
1619 is_dc_message: MessengerMessage,
1620) -> Result<ReceivedMsg> {
1621 let to_id = if mime_parser.incoming {
1622 ContactId::SELF
1623 } else {
1624 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
1625 };
1626
1627 if prevent_rename {
1630 if let Some(name) = &mime_parser.from.display_name {
1631 for part in &mut mime_parser.parts {
1632 part.param.set(Param::OverrideSenderDisplayname, name);
1633 }
1634 }
1635 }
1636
1637 let mut chat = Chat::load_from_db(context, chat_id).await?;
1638
1639 if mime_parser.incoming && !chat_id.is_trash() {
1640 if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
1643 let from = &mime_parser.from;
1647 let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
1648 for part in &mut mime_parser.parts {
1649 part.param.set(Param::OverrideSenderDisplayname, name);
1650 }
1651 }
1652 }
1653
1654 let is_location_kml = mime_parser.location_kml.is_some();
1655 let is_mdn = !mime_parser.mdn_reports.is_empty();
1656
1657 let mut group_changes = match chat.typ {
1658 _ if chat.id.is_special() => GroupChangesInfo::default(),
1659 Chattype::Single => GroupChangesInfo::default(),
1660 Chattype::Mailinglist => GroupChangesInfo::default(),
1661 Chattype::OutBroadcast => {
1662 apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1663 }
1664 Chattype::Group => {
1665 apply_group_changes(context, mime_parser, &mut chat, from_id, to_ids, past_ids).await?
1666 }
1667 Chattype::InBroadcast => {
1668 apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1669 }
1670 };
1671
1672 let rfc724_mid_orig = &mime_parser
1673 .get_rfc724_mid()
1674 .unwrap_or(rfc724_mid.to_string());
1675
1676 let mut ephemeral_timer = if is_partial_download.is_some() {
1678 chat_id.get_ephemeral_timer(context).await?
1679 } else if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer) {
1680 match value.parse::<EphemeralTimer>() {
1681 Ok(timer) => timer,
1682 Err(err) => {
1683 warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1684 EphemeralTimer::Disabled
1685 }
1686 }
1687 } else {
1688 EphemeralTimer::Disabled
1689 };
1690
1691 let state = if !mime_parser.incoming {
1692 MessageState::OutDelivered
1693 } else if seen || is_mdn || chat_id_blocked == Blocked::Yes || group_changes.silent
1694 {
1696 MessageState::InSeen
1697 } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL {
1698 MessageState::InNoticed
1699 } else {
1700 MessageState::InFresh
1701 };
1702 let in_fresh = state == MessageState::InFresh;
1703
1704 let sort_to_bottom = false;
1705 let received = true;
1706 let sort_timestamp = chat_id
1707 .calc_sort_timestamp(
1708 context,
1709 mime_parser.timestamp_sent,
1710 sort_to_bottom,
1711 received,
1712 mime_parser.incoming,
1713 )
1714 .await?;
1715
1716 if !chat_id.is_special()
1722 && !mime_parser.parts.is_empty()
1723 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1724 {
1725 let chat_contacts =
1726 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1727 let is_from_in_chat =
1728 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1729
1730 info!(
1731 context,
1732 "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1733 );
1734 if !is_from_in_chat {
1735 warn!(
1736 context,
1737 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1738 );
1739 } else if is_dc_message == MessengerMessage::Yes
1740 && get_previous_message(context, mime_parser)
1741 .await?
1742 .map(|p| p.ephemeral_timer)
1743 == Some(ephemeral_timer)
1744 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1745 {
1746 warn!(
1753 context,
1754 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1755 );
1756 } else if chat_id
1757 .update_timestamp(
1758 context,
1759 Param::EphemeralSettingsTimestamp,
1760 mime_parser.timestamp_sent,
1761 )
1762 .await?
1763 {
1764 if let Err(err) = chat_id
1765 .inner_set_ephemeral_timer(context, ephemeral_timer)
1766 .await
1767 {
1768 warn!(
1769 context,
1770 "Failed to modify timer for chat {chat_id}: {err:#}."
1771 );
1772 } else {
1773 info!(
1774 context,
1775 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1776 );
1777 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1778 chat::add_info_msg(
1779 context,
1780 chat_id,
1781 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1782 sort_timestamp,
1783 )
1784 .await?;
1785 }
1786 }
1787 } else {
1788 warn!(
1789 context,
1790 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1791 );
1792 }
1793 }
1794
1795 let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1796 {
1797 Some(stock_str::msg_location_enabled_by(context, from_id).await)
1798 } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1799 let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1800
1801 ephemeral_timer = EphemeralTimer::Disabled;
1808
1809 Some(better_msg)
1810 } else {
1811 None
1812 };
1813
1814 drop(chat); let sort_timestamp = tweak_sort_timestamp(
1817 context,
1818 mime_parser,
1819 group_changes.silent,
1820 chat_id,
1821 sort_timestamp,
1822 )
1823 .await?;
1824
1825 let mime_in_reply_to = mime_parser
1826 .get_header(HeaderDef::InReplyTo)
1827 .unwrap_or_default();
1828 let mime_references = mime_parser
1829 .get_header(HeaderDef::References)
1830 .unwrap_or_default();
1831
1832 let icnt = mime_parser.parts.len();
1837
1838 let subject = mime_parser.get_subject().unwrap_or_default();
1839
1840 let is_system_message = mime_parser.is_system_message;
1841
1842 let mut save_mime_modified = false;
1849
1850 let mime_headers = if mime_parser.is_mime_modified {
1851 let headers = if !mime_parser.decoded_data.is_empty() {
1852 mime_parser.decoded_data.clone()
1853 } else {
1854 imf_raw.to_vec()
1855 };
1856 tokio::task::block_in_place(move || buf_compress(&headers))?
1857 } else {
1858 Vec::new()
1859 };
1860
1861 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
1862
1863 if let Some(m) = group_changes.better_msg {
1864 match &better_msg {
1865 None => better_msg = Some(m),
1866 Some(_) => {
1867 if !m.is_empty() {
1868 group_changes.extra_msgs.push((m, is_system_message, None))
1869 }
1870 }
1871 }
1872 }
1873
1874 let chat_id = if better_msg
1875 .as_ref()
1876 .is_some_and(|better_msg| better_msg.is_empty())
1877 && is_partial_download.is_none()
1878 {
1879 DC_CHAT_ID_TRASH
1880 } else {
1881 chat_id
1882 };
1883
1884 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
1885 chat::add_info_msg_with_cmd(
1886 context,
1887 chat_id,
1888 &group_changes_msg,
1889 cmd,
1890 sort_timestamp,
1891 None,
1892 None,
1893 None,
1894 added_removed_id,
1895 )
1896 .await?;
1897 }
1898
1899 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
1900 match mime_parser.get_header(HeaderDef::InReplyTo) {
1901 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
1902 Some(instance_id) => {
1903 if let Err(err) =
1904 add_gossip_peer_from_header(context, instance_id, node_addr).await
1905 {
1906 warn!(context, "Failed to add iroh peer from header: {err:#}.");
1907 }
1908 }
1909 None => {
1910 warn!(
1911 context,
1912 "Cannot add iroh peer because WebXDC instance does not exist."
1913 );
1914 }
1915 },
1916 None => {
1917 warn!(
1918 context,
1919 "Cannot add iroh peer because the message has no In-Reply-To."
1920 );
1921 }
1922 }
1923 }
1924
1925 handle_edit_delete(context, mime_parser, from_id).await?;
1926
1927 if is_partial_download.is_none()
1928 && (mime_parser.is_system_message == SystemMessage::CallAccepted
1929 || mime_parser.is_system_message == SystemMessage::CallEnded)
1930 {
1931 if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
1932 if let Some(call) =
1933 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
1934 {
1935 context
1936 .handle_call_msg(call.get_id(), mime_parser, from_id)
1937 .await?;
1938 } else {
1939 warn!(context, "Call: Cannot load parent.")
1940 }
1941 } else {
1942 warn!(context, "Call: Not a reply.")
1943 }
1944 }
1945
1946 let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
1947 let mut parts = mime_parser.parts.iter().peekable();
1948 while let Some(part) = parts.next() {
1949 let hidden = part.is_reaction;
1950 if part.is_reaction {
1951 let reaction_str = simplify::remove_footers(part.msg.as_str());
1952 let is_incoming_fresh = mime_parser.incoming && !seen;
1953 set_msg_reaction(
1954 context,
1955 mime_in_reply_to,
1956 chat_id,
1957 from_id,
1958 sort_timestamp,
1959 Reaction::from(reaction_str.as_str()),
1960 is_incoming_fresh,
1961 )
1962 .await?;
1963 }
1964
1965 let mut param = part.param.clone();
1966 if is_system_message != SystemMessage::Unknown {
1967 param.set_int(Param::Cmd, is_system_message as i32);
1968 }
1969
1970 if let Some(replace_msg_id) = replace_msg_id {
1971 let placeholder = Message::load_from_db(context, replace_msg_id).await?;
1972 for key in [
1973 Param::WebxdcSummary,
1974 Param::WebxdcSummaryTimestamp,
1975 Param::WebxdcDocument,
1976 Param::WebxdcDocumentTimestamp,
1977 ] {
1978 if let Some(value) = placeholder.param.get(key) {
1979 param.set(key, value);
1980 }
1981 }
1982 }
1983
1984 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
1985 (better_msg, Viewtype::Text)
1986 } else {
1987 (&part.msg, part.typ)
1988 };
1989 let part_is_empty =
1990 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
1991
1992 if let Some(contact_id) = group_changes.added_removed_id {
1993 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
1994 }
1995
1996 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
1997 let save_mime_modified = save_mime_modified && parts.peek().is_none();
1998
1999 let ephemeral_timestamp = if in_fresh {
2000 0
2001 } else {
2002 match ephemeral_timer {
2003 EphemeralTimer::Disabled => 0,
2004 EphemeralTimer::Enabled { duration } => {
2005 mime_parser.timestamp_rcvd.saturating_add(duration.into())
2006 }
2007 }
2008 };
2009
2010 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2013
2014 let row_id = context
2015 .sql
2016 .call_write(|conn| {
2017 let mut stmt = conn.prepare_cached(
2018 r#"
2019INSERT INTO msgs
2020 (
2021 id,
2022 rfc724_mid, chat_id,
2023 from_id, to_id, timestamp, timestamp_sent,
2024 timestamp_rcvd, type, state, msgrmsg,
2025 txt, txt_normalized, subject, param, hidden,
2026 bytes, mime_headers, mime_compressed, mime_in_reply_to,
2027 mime_references, mime_modified, error, ephemeral_timer,
2028 ephemeral_timestamp, download_state, hop_info
2029 )
2030 VALUES (
2031 ?,
2032 ?, ?, ?, ?,
2033 ?, ?, ?, ?,
2034 ?, ?, ?, ?,
2035 ?, ?, ?, ?, ?, 1,
2036 ?, ?, ?, ?,
2037 ?, ?, ?, ?
2038 )
2039ON CONFLICT (id) DO UPDATE
2040SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
2041 from_id=excluded.from_id, to_id=excluded.to_id, timestamp_sent=excluded.timestamp_sent,
2042 type=excluded.type, state=max(state,excluded.state), msgrmsg=excluded.msgrmsg,
2043 txt=excluded.txt, txt_normalized=excluded.txt_normalized, subject=excluded.subject,
2044 param=excluded.param,
2045 hidden=excluded.hidden,bytes=excluded.bytes, mime_headers=excluded.mime_headers,
2046 mime_compressed=excluded.mime_compressed, mime_in_reply_to=excluded.mime_in_reply_to,
2047 mime_references=excluded.mime_references, mime_modified=excluded.mime_modified, error=excluded.error, ephemeral_timer=excluded.ephemeral_timer,
2048 ephemeral_timestamp=excluded.ephemeral_timestamp, download_state=excluded.download_state, hop_info=excluded.hop_info
2049RETURNING id
2050"#)?;
2051 let row_id: MsgId = stmt.query_row(params![
2052 replace_msg_id,
2053 rfc724_mid_orig,
2054 if trash { DC_CHAT_ID_TRASH } else { chat_id },
2055 if trash { ContactId::UNDEFINED } else { from_id },
2056 if trash { ContactId::UNDEFINED } else { to_id },
2057 sort_timestamp,
2058 if trash { 0 } else { mime_parser.timestamp_sent },
2059 if trash { 0 } else { mime_parser.timestamp_rcvd },
2060 if trash { Viewtype::Unknown } else { typ },
2061 if trash { MessageState::Undefined } else { state },
2062 if trash { MessengerMessage::No } else { is_dc_message },
2063 if trash || hidden { "" } else { msg },
2064 if trash || hidden { None } else { message::normalize_text(msg) },
2065 if trash || hidden { "" } else { &subject },
2066 if trash {
2067 "".to_string()
2068 } else {
2069 param.to_string()
2070 },
2071 !trash && hidden,
2072 if trash { 0 } else { part.bytes as isize },
2073 if save_mime_modified && !(trash || hidden) {
2074 mime_headers.clone()
2075 } else {
2076 Vec::new()
2077 },
2078 if trash { "" } else { mime_in_reply_to },
2079 if trash { "" } else { mime_references },
2080 !trash && save_mime_modified,
2081 if trash { "" } else { part.error.as_deref().unwrap_or_default() },
2082 if trash { 0 } else { ephemeral_timer.to_u32() },
2083 if trash { 0 } else { ephemeral_timestamp },
2084 if trash {
2085 DownloadState::Done
2086 } else if is_partial_download.is_some() {
2087 DownloadState::Available
2088 } else if mime_parser.decrypting_failed {
2089 DownloadState::Undecipherable
2090 } else {
2091 DownloadState::Done
2092 },
2093 if trash { "" } else { &mime_parser.hop_info },
2094 ],
2095 |row| {
2096 let msg_id: MsgId = row.get(0)?;
2097 Ok(msg_id)
2098 }
2099 )?;
2100 Ok(row_id)
2101 })
2102 .await?;
2103
2104 replace_msg_id = None;
2107
2108 ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2109 created_db_entries.push(row_id);
2110 }
2111
2112 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2114 if part.typ == Viewtype::Webxdc {
2116 if let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic) {
2117 let mut topic_raw = [0u8; 32];
2119 BASE32_NOPAD
2120 .decode_mut(topic.to_ascii_uppercase().as_bytes(), &mut topic_raw)
2121 .map_err(|e| e.error)
2122 .context("Wrong gossip topic header")?;
2123
2124 let topic = TopicId::from_bytes(topic_raw);
2125 insert_topic_stub(context, *msg_id, topic).await?;
2126 } else {
2127 warn!(context, "webxdc doesn't have a gossip topic")
2128 }
2129 }
2130
2131 maybe_set_logging_xdc_inner(
2132 context,
2133 part.typ,
2134 chat_id,
2135 part.param.get(Param::Filename),
2136 *msg_id,
2137 )
2138 .await?;
2139 }
2140
2141 if let Some(replace_msg_id) = replace_msg_id {
2142 let on_server = rfc724_mid == rfc724_mid_orig;
2146 replace_msg_id.trash(context, on_server).await?;
2147 }
2148
2149 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2150 Some(addr) => context.is_self_addr(addr).await?,
2151 None => true,
2152 };
2153 if unarchive {
2154 chat_id.unarchive_if_not_muted(context, state).await?;
2155 }
2156
2157 info!(
2158 context,
2159 "Message has {icnt} parts and is assigned to chat #{chat_id}."
2160 );
2161
2162 if !chat_id.is_trash() && !hidden {
2163 let mut chat = Chat::load_from_db(context, chat_id).await?;
2164 let mut update_param = false;
2165
2166 if chat
2170 .param
2171 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2172 {
2173 let subject = mime_parser.get_subject().unwrap_or_default();
2176
2177 chat.param.set(Param::LastSubject, subject);
2178 update_param = true;
2179 }
2180
2181 if chat.is_unpromoted() {
2182 chat.param.remove(Param::Unpromoted);
2183 update_param = true;
2184 }
2185 if update_param {
2186 chat.update_param(context).await?;
2187 }
2188 }
2189
2190 let needs_delete_job =
2194 !mime_parser.incoming && is_mdn && is_dc_message == MessengerMessage::Yes;
2195
2196 Ok(ReceivedMsg {
2197 chat_id,
2198 state,
2199 hidden,
2200 sort_timestamp,
2201 msg_ids: created_db_entries,
2202 needs_delete_job,
2203 })
2204}
2205
2206async fn handle_edit_delete(
2211 context: &Context,
2212 mime_parser: &MimeMessage,
2213 from_id: ContactId,
2214) -> Result<()> {
2215 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2216 if let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? {
2217 if let Some(mut original_msg) =
2218 Message::load_from_db_optional(context, original_msg_id).await?
2219 {
2220 if original_msg.from_id == from_id {
2221 if let Some(part) = mime_parser.parts.first() {
2222 let edit_msg_showpadlock = part
2223 .param
2224 .get_bool(Param::GuaranteeE2ee)
2225 .unwrap_or_default();
2226 if edit_msg_showpadlock || !original_msg.get_showpadlock() {
2227 let new_text =
2228 part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2229 chat::save_text_edit_to_db(context, &mut original_msg, new_text)
2230 .await?;
2231 } else {
2232 warn!(context, "Edit message: Not encrypted.");
2233 }
2234 }
2235 } else {
2236 warn!(context, "Edit message: Bad sender.");
2237 }
2238 } else {
2239 warn!(context, "Edit message: Database entry does not exist.");
2240 }
2241 } else {
2242 warn!(
2243 context,
2244 "Edit message: rfc724_mid {rfc724_mid:?} not found."
2245 );
2246 }
2247 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete) {
2248 if let Some(part) = mime_parser.parts.first() {
2249 if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
2252 let mut modified_chat_ids = HashSet::new();
2253 let mut msg_ids = Vec::new();
2254
2255 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2256 for rfc724_mid in rfc724_mid_vec {
2257 if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
2258 if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
2259 if msg.from_id == from_id {
2260 message::delete_msg_locally(context, &msg).await?;
2261 msg_ids.push(msg.id);
2262 modified_chat_ids.insert(msg.chat_id);
2263 } else {
2264 warn!(context, "Delete message: Bad sender.");
2265 }
2266 } else {
2267 warn!(context, "Delete message: Database entry does not exist.");
2268 }
2269 } else {
2270 warn!(context, "Delete message: {rfc724_mid:?} not found.");
2271 }
2272 }
2273 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2274 } else {
2275 warn!(context, "Delete message: Not encrypted.");
2276 }
2277 }
2278 }
2279 Ok(())
2280}
2281
2282async fn tweak_sort_timestamp(
2283 context: &Context,
2284 mime_parser: &mut MimeMessage,
2285 silent: bool,
2286 chat_id: ChatId,
2287 sort_timestamp: i64,
2288) -> Result<i64> {
2289 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2298 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2299 std::cmp::max(sort_timestamp, parent_timestamp)
2300 });
2301
2302 if silent {
2306 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2307 t
2308 } else {
2309 chat_id.created_timestamp(context).await?
2310 };
2311 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2312 }
2313 Ok(sort_timestamp)
2314}
2315
2316async fn save_locations(
2320 context: &Context,
2321 mime_parser: &MimeMessage,
2322 chat_id: ChatId,
2323 from_id: ContactId,
2324 msg_id: MsgId,
2325) -> Result<()> {
2326 if chat_id.is_special() {
2327 return Ok(());
2329 }
2330
2331 let mut send_event = false;
2332
2333 if let Some(message_kml) = &mime_parser.message_kml {
2334 if let Some(newest_location_id) =
2335 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2336 {
2337 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2338 send_event = true;
2339 }
2340 }
2341
2342 if let Some(location_kml) = &mime_parser.location_kml {
2343 if let Some(addr) = &location_kml.addr {
2344 let contact = Contact::get_by_id(context, from_id).await?;
2345 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2346 if location::save(context, chat_id, from_id, &location_kml.locations, false)
2347 .await?
2348 .is_some()
2349 {
2350 send_event = true;
2351 }
2352 } else {
2353 warn!(
2354 context,
2355 "Address in location.kml {:?} is not the same as the sender address {:?}.",
2356 addr,
2357 contact.get_addr()
2358 );
2359 }
2360 }
2361 }
2362 if send_event {
2363 context.emit_location_changed(Some(from_id)).await?;
2364 }
2365 Ok(())
2366}
2367
2368async fn lookup_chat_by_reply(
2369 context: &Context,
2370 mime_parser: &MimeMessage,
2371 parent: &Message,
2372 is_partial_download: &Option<u32>,
2373) -> Result<Option<(ChatId, Blocked)>> {
2374 ensure_and_debug_assert!(
2379 mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2380 "Encrypted message has group ID {}",
2381 mime_parser.get_chat_group_id().unwrap_or_default(),
2382 );
2383
2384 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2386 return Ok(None);
2387 };
2388
2389 if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2392 return Ok(None);
2393 }
2394
2395 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2399 if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2400 return Ok(None);
2401 }
2402
2403 if is_partial_download.is_none()
2405 && parent_chat.is_encrypted(context).await?
2406 && !mime_parser.was_encrypted()
2407 {
2408 return Ok(None);
2409 }
2410
2411 info!(
2412 context,
2413 "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2414 );
2415 Ok(Some((parent_chat.id, parent_chat.blocked)))
2416}
2417
2418async fn lookup_or_create_adhoc_group(
2419 context: &Context,
2420 mime_parser: &MimeMessage,
2421 to_ids: &[Option<ContactId>],
2422 allow_creation: bool,
2423 create_blocked: Blocked,
2424 is_partial_download: bool,
2425) -> Result<Option<(ChatId, Blocked, bool)>> {
2426 if is_partial_download {
2430 info!(
2431 context,
2432 "Ad-hoc group cannot be created from partial download."
2433 );
2434 return Ok(None);
2435 }
2436 if mime_parser.decrypting_failed {
2437 warn!(
2438 context,
2439 "Not creating ad-hoc group for message that cannot be decrypted."
2440 );
2441 return Ok(None);
2442 }
2443
2444 let fingerprint = None;
2446 let find_key_contact_by_addr = false;
2447 let prevent_rename = should_prevent_rename(mime_parser);
2448 let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2449 context,
2450 &mime_parser.from,
2451 fingerprint,
2452 prevent_rename,
2453 find_key_contact_by_addr,
2454 )
2455 .await?
2456 .context("Cannot lookup address-contact by the From field")?;
2457
2458 let grpname = mime_parser
2459 .get_header(HeaderDef::ChatGroupName)
2460 .map(|s| s.to_string())
2461 .unwrap_or_else(|| {
2462 mime_parser
2463 .get_subject()
2464 .map(|s| remove_subject_prefix(&s))
2465 .unwrap_or_else(|| "👥📧".to_string())
2466 });
2467 let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2468 let mut contact_ids = Vec::with_capacity(to_ids.len() + 1);
2469 contact_ids.extend(&to_ids);
2470 if !contact_ids.contains(&from_id) {
2471 contact_ids.push(from_id);
2472 }
2473 let trans_fn = |t: &mut rusqlite::Transaction| {
2474 t.pragma_update(None, "query_only", "0")?;
2475 t.execute(
2476 "CREATE TEMP TABLE temp.contacts (
2477 id INTEGER PRIMARY KEY
2478 ) STRICT",
2479 (),
2480 )?;
2481 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2482 for &id in &contact_ids {
2483 stmt.execute((id,))?;
2484 }
2485 let val = t
2486 .query_row(
2487 "SELECT c.id, c.blocked
2488 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2489 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2490 AND (SELECT COUNT(*) FROM chats_contacts
2491 WHERE chat_id=c.id
2492 AND add_timestamp >= remove_timestamp)=?
2493 AND (SELECT COUNT(*) FROM chats_contacts
2494 WHERE chat_id=c.id
2495 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2496 AND add_timestamp >= remove_timestamp)=0
2497 ORDER BY m.timestamp DESC",
2498 (&grpname, contact_ids.len()),
2499 |row| {
2500 let id: ChatId = row.get(0)?;
2501 let blocked: Blocked = row.get(1)?;
2502 Ok((id, blocked))
2503 },
2504 )
2505 .optional()?;
2506 t.execute("DROP TABLE temp.contacts", ())?;
2507 Ok(val)
2508 };
2509 let query_only = true;
2510 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2511 info!(
2512 context,
2513 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2514 );
2515 return Ok(Some((chat_id, blocked, false)));
2516 }
2517 if !allow_creation {
2518 return Ok(None);
2519 }
2520 Ok(create_adhoc_group(
2521 context,
2522 mime_parser,
2523 create_blocked,
2524 from_id,
2525 &to_ids,
2526 &grpname,
2527 )
2528 .await
2529 .context("Could not create ad hoc group")?
2530 .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2531}
2532
2533async fn is_probably_private_reply(
2536 context: &Context,
2537 mime_parser: &MimeMessage,
2538 parent_chat_id: ChatId,
2539) -> Result<bool> {
2540 if mime_parser.get_chat_group_id().is_some() {
2542 return Ok(false);
2543 }
2544
2545 if mime_parser.recipients.len() != 1 {
2553 return Ok(false);
2554 }
2555
2556 if !mime_parser.has_chat_version() {
2557 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2558 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2559 return Ok(false);
2560 }
2561 }
2562
2563 Ok(true)
2564}
2565
2566#[expect(clippy::too_many_arguments)]
2572async fn create_group(
2573 context: &Context,
2574 mime_parser: &mut MimeMessage,
2575 is_partial_download: bool,
2576 create_blocked: Blocked,
2577 from_id: ContactId,
2578 to_ids: &[Option<ContactId>],
2579 past_ids: &[Option<ContactId>],
2580 grpid: &str,
2581) -> Result<Option<(ChatId, Blocked)>> {
2582 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2583 let mut chat_id = None;
2584 let mut chat_id_blocked = Default::default();
2585
2586 if chat_id.is_none()
2587 && !mime_parser.is_mailinglist_message()
2588 && !grpid.is_empty()
2589 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2590 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2592 {
2593 let grpname = mime_parser
2595 .get_header(HeaderDef::ChatGroupName)
2596 .context("Chat-Group-Name vanished")?
2597 .trim();
2601 let new_chat_id = ChatId::create_multiuser_record(
2602 context,
2603 Chattype::Group,
2604 grpid,
2605 grpname,
2606 create_blocked,
2607 None,
2608 mime_parser.timestamp_sent,
2609 )
2610 .await
2611 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2612
2613 chat_id = Some(new_chat_id);
2614 chat_id_blocked = create_blocked;
2615
2616 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2618 let mut new_to_ids = to_ids.to_vec();
2619 if !new_to_ids.contains(&Some(from_id)) {
2620 new_to_ids.insert(0, Some(from_id));
2621 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2622 }
2623
2624 update_chats_contacts_timestamps(
2625 context,
2626 new_chat_id,
2627 None,
2628 &new_to_ids,
2629 past_ids,
2630 &chat_group_member_timestamps,
2631 )
2632 .await?;
2633 } else {
2634 let mut members = vec![ContactId::SELF];
2635 if !from_id.is_special() {
2636 members.push(from_id);
2637 }
2638 members.extend(to_ids_flat);
2639
2640 let timestamp = 0;
2646
2647 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2648 }
2649
2650 context.emit_event(EventType::ChatModified(new_chat_id));
2651 chatlist_events::emit_chatlist_changed(context);
2652 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2653 }
2654
2655 if let Some(chat_id) = chat_id {
2656 Ok(Some((chat_id, chat_id_blocked)))
2657 } else if is_partial_download || mime_parser.decrypting_failed {
2658 Ok(None)
2665 } else {
2666 info!(context, "Message belongs to unwanted group (TRASH).");
2669 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2670 }
2671}
2672
2673async fn update_chats_contacts_timestamps(
2674 context: &Context,
2675 chat_id: ChatId,
2676 ignored_id: Option<ContactId>,
2677 to_ids: &[Option<ContactId>],
2678 past_ids: &[Option<ContactId>],
2679 chat_group_member_timestamps: &[i64],
2680) -> Result<bool> {
2681 let expected_timestamps_count = to_ids.len() + past_ids.len();
2682
2683 if chat_group_member_timestamps.len() != expected_timestamps_count {
2684 warn!(
2685 context,
2686 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2687 chat_group_member_timestamps.len(),
2688 expected_timestamps_count
2689 );
2690 return Ok(false);
2691 }
2692
2693 let mut modified = false;
2694
2695 context
2696 .sql
2697 .transaction(|transaction| {
2698 let mut add_statement = transaction.prepare(
2699 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2700 VALUES (?1, ?2, ?3)
2701 ON CONFLICT (chat_id, contact_id)
2702 DO
2703 UPDATE SET add_timestamp=?3
2704 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2705 )?;
2706
2707 for (contact_id, ts) in iter::zip(
2708 to_ids.iter(),
2709 chat_group_member_timestamps.iter().take(to_ids.len()),
2710 ) {
2711 if let Some(contact_id) = contact_id {
2712 if Some(*contact_id) != ignored_id {
2713 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2717 }
2718 }
2719 }
2720
2721 let mut remove_statement = transaction.prepare(
2722 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2723 VALUES (?1, ?2, ?3)
2724 ON CONFLICT (chat_id, contact_id)
2725 DO
2726 UPDATE SET remove_timestamp=?3
2727 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2728 )?;
2729
2730 for (contact_id, ts) in iter::zip(
2731 past_ids.iter(),
2732 chat_group_member_timestamps.iter().skip(to_ids.len()),
2733 ) {
2734 if let Some(contact_id) = contact_id {
2735 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2739 }
2740 }
2741
2742 Ok(())
2743 })
2744 .await?;
2745
2746 Ok(modified)
2747}
2748
2749#[derive(Default)]
2753struct GroupChangesInfo {
2754 better_msg: Option<String>,
2757 added_removed_id: Option<ContactId>,
2759 silent: bool,
2761 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
2763}
2764
2765async fn apply_group_changes(
2772 context: &Context,
2773 mime_parser: &mut MimeMessage,
2774 chat: &mut Chat,
2775 from_id: ContactId,
2776 to_ids: &[Option<ContactId>],
2777 past_ids: &[Option<ContactId>],
2778) -> Result<GroupChangesInfo> {
2779 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2780 ensure!(chat.typ == Chattype::Group);
2781 ensure!(!chat.id.is_special());
2782
2783 let mut send_event_chat_modified = false;
2784 let (mut removed_id, mut added_id) = (None, None);
2785 let mut better_msg = None;
2786 let mut silent = false;
2787 let chat_contacts =
2788 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
2789 let is_from_in_chat =
2790 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
2791
2792 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2793 if !is_from_in_chat {
2802 better_msg = Some(String::new());
2803 } else if let Some(id) =
2804 lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?
2805 {
2806 removed_id = Some(id);
2807 better_msg = if id == from_id {
2808 silent = true;
2809 Some(stock_str::msg_group_left_local(context, from_id).await)
2810 } else {
2811 Some(stock_str::msg_del_member_local(context, id, from_id).await)
2812 };
2813 } else {
2814 warn!(context, "Removed {removed_addr:?} has no contact id.")
2815 }
2816 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2817 if !is_from_in_chat {
2818 better_msg = Some(String::new());
2819 } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
2820 let fingerprint = key.public_key.dc_fingerprint().hex();
2827 if let Some(contact_id) =
2828 lookup_key_contact_by_fingerprint(context, &fingerprint).await?
2829 {
2830 added_id = Some(contact_id);
2831 better_msg =
2832 Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
2833 } else {
2834 warn!(context, "Added {added_addr:?} has no contact id.");
2835 }
2836 } else {
2837 warn!(context, "Added {added_addr:?} has no gossiped key.");
2838 }
2839 }
2840
2841 if is_from_in_chat {
2842 apply_chat_name_and_avatar_changes(
2843 context,
2844 mime_parser,
2845 from_id,
2846 chat,
2847 &mut send_event_chat_modified,
2848 &mut better_msg,
2849 )
2850 .await?;
2851
2852 if chat.member_list_is_stale(context).await? {
2853 info!(context, "Member list is stale.");
2854 let mut new_members: HashSet<ContactId> =
2855 HashSet::from_iter(to_ids_flat.iter().copied());
2856 new_members.insert(ContactId::SELF);
2857 if !from_id.is_special() {
2858 new_members.insert(from_id);
2859 }
2860
2861 context
2862 .sql
2863 .transaction(|transaction| {
2864 transaction.execute(
2866 "DELETE FROM chats_contacts
2867 WHERE chat_id=?",
2868 (chat.id,),
2869 )?;
2870
2871 let mut statement = transaction.prepare(
2873 "INSERT INTO chats_contacts (chat_id, contact_id)
2874 VALUES (?, ?)",
2875 )?;
2876 for contact_id in &new_members {
2877 statement.execute((chat.id, contact_id))?;
2878 }
2879
2880 Ok(())
2881 })
2882 .await?;
2883 send_event_chat_modified = true;
2884 } else if let Some(ref chat_group_member_timestamps) =
2885 mime_parser.chat_group_member_timestamps()
2886 {
2887 send_event_chat_modified |= update_chats_contacts_timestamps(
2888 context,
2889 chat.id,
2890 Some(from_id),
2891 to_ids,
2892 past_ids,
2893 chat_group_member_timestamps,
2894 )
2895 .await?;
2896 } else {
2897 let mut new_members: HashSet<ContactId>;
2898 let self_added =
2901 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2902 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
2903 && !chat_contacts.contains(&ContactId::SELF)
2904 } else {
2905 false
2906 };
2907 if self_added {
2908 new_members = HashSet::from_iter(to_ids_flat.iter().copied());
2909 new_members.insert(ContactId::SELF);
2910 if !from_id.is_special() {
2911 new_members.insert(from_id);
2912 }
2913 } else {
2914 new_members = chat_contacts.clone();
2915 }
2916
2917 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
2919 new_members.extend(to_ids_flat.iter());
2922 }
2923
2924 if let Some(added_id) = added_id {
2926 new_members.insert(added_id);
2927 }
2928
2929 if let Some(removed_id) = removed_id {
2931 new_members.remove(&removed_id);
2932 }
2933
2934 if new_members != chat_contacts {
2935 chat::update_chat_contacts_table(
2936 context,
2937 mime_parser.timestamp_sent,
2938 chat.id,
2939 &new_members,
2940 )
2941 .await?;
2942 send_event_chat_modified = true;
2943 }
2944 }
2945
2946 chat.id
2947 .update_timestamp(
2948 context,
2949 Param::MemberListTimestamp,
2950 mime_parser.timestamp_sent,
2951 )
2952 .await?;
2953 }
2954
2955 let new_chat_contacts = HashSet::<ContactId>::from_iter(
2956 chat::get_chat_contacts(context, chat.id)
2957 .await?
2958 .iter()
2959 .copied(),
2960 );
2961
2962 let mut added_ids: HashSet<ContactId> = new_chat_contacts
2964 .difference(&chat_contacts)
2965 .copied()
2966 .collect();
2967 let mut removed_ids: HashSet<ContactId> = chat_contacts
2968 .difference(&new_chat_contacts)
2969 .copied()
2970 .collect();
2971
2972 if let Some(added_id) = added_id {
2973 if !added_ids.remove(&added_id) && added_id != ContactId::SELF {
2974 better_msg = Some(String::new());
2977 }
2978 }
2979 if let Some(removed_id) = removed_id {
2980 removed_ids.remove(&removed_id);
2981 }
2982 let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
2983 && new_chat_contacts.contains(&ContactId::SELF)
2984 {
2985 Vec::new()
2986 } else {
2987 group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
2988 };
2989
2990 if send_event_chat_modified {
2991 context.emit_event(EventType::ChatModified(chat.id));
2992 chatlist_events::emit_chatlist_item_changed(context, chat.id);
2993 }
2994 Ok(GroupChangesInfo {
2995 better_msg,
2996 added_removed_id: if added_id.is_some() {
2997 added_id
2998 } else {
2999 removed_id
3000 },
3001 silent,
3002 extra_msgs: group_changes_msgs,
3003 })
3004}
3005
3006async fn apply_chat_name_and_avatar_changes(
3011 context: &Context,
3012 mime_parser: &MimeMessage,
3013 from_id: ContactId,
3014 chat: &mut Chat,
3015 send_event_chat_modified: &mut bool,
3016 better_msg: &mut Option<String>,
3017) -> Result<()> {
3018 let group_name_timestamp = mime_parser
3021 .get_header(HeaderDef::ChatGroupNameTimestamp)
3022 .and_then(|s| s.parse::<i64>().ok());
3023
3024 if let Some(old_name) = mime_parser
3025 .get_header(HeaderDef::ChatGroupNameChanged)
3026 .map(|s| s.trim())
3027 .or(match group_name_timestamp {
3028 Some(0) => None,
3029 Some(_) => Some(chat.name.as_str()),
3030 None => None,
3031 })
3032 {
3033 if let Some(grpname) = mime_parser
3034 .get_header(HeaderDef::ChatGroupName)
3035 .map(|grpname| grpname.trim())
3036 .filter(|grpname| grpname.len() < 200)
3037 {
3038 let grpname = &sanitize_single_line(grpname);
3039
3040 let chat_group_name_timestamp =
3041 chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3042 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3043 if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3045 && chat
3046 .id
3047 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3048 .await?
3049 && grpname != &chat.name
3050 {
3051 info!(context, "Updating grpname for chat {}.", chat.id);
3052 context
3053 .sql
3054 .execute("UPDATE chats SET name=? WHERE id=?;", (grpname, chat.id))
3055 .await?;
3056 *send_event_chat_modified = true;
3057 }
3058 if mime_parser
3059 .get_header(HeaderDef::ChatGroupNameChanged)
3060 .is_some()
3061 {
3062 let old_name = &sanitize_single_line(old_name);
3063 better_msg.get_or_insert(
3064 stock_str::msg_grp_name(context, old_name, grpname, from_id).await,
3065 );
3066 }
3067 }
3068 }
3069
3070 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg) {
3073 if value == "group-avatar-changed" {
3074 if let Some(avatar_action) = &mime_parser.group_avatar {
3075 better_msg.get_or_insert(match avatar_action {
3078 AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
3079 AvatarAction::Change(_) => {
3080 stock_str::msg_grp_img_changed(context, from_id).await
3081 }
3082 });
3083 }
3084 }
3085 }
3086
3087 if let Some(avatar_action) = &mime_parser.group_avatar {
3088 info!(context, "Group-avatar change for {}.", chat.id);
3089 if chat
3090 .param
3091 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3092 {
3093 match avatar_action {
3094 AvatarAction::Change(profile_image) => {
3095 chat.param.set(Param::ProfileImage, profile_image);
3096 }
3097 AvatarAction::Delete => {
3098 chat.param.remove(Param::ProfileImage);
3099 }
3100 };
3101 chat.update_param(context).await?;
3102 *send_event_chat_modified = true;
3103 }
3104 }
3105
3106 Ok(())
3107}
3108
3109async fn group_changes_msgs(
3111 context: &Context,
3112 added_ids: &HashSet<ContactId>,
3113 removed_ids: &HashSet<ContactId>,
3114 chat_id: ChatId,
3115) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3116 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3117 if !added_ids.is_empty() {
3118 warn!(
3119 context,
3120 "Implicit addition of {added_ids:?} to chat {chat_id}."
3121 );
3122 }
3123 if !removed_ids.is_empty() {
3124 warn!(
3125 context,
3126 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3127 );
3128 }
3129 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3130 for contact_id in added_ids {
3131 group_changes_msgs.push((
3132 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3133 SystemMessage::MemberAddedToGroup,
3134 Some(*contact_id),
3135 ));
3136 }
3137 for contact_id in removed_ids {
3138 group_changes_msgs.push((
3139 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3140 SystemMessage::MemberRemovedFromGroup,
3141 Some(*contact_id),
3142 ));
3143 }
3144
3145 Ok(group_changes_msgs)
3146}
3147
3148static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3149
3150fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3151 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3152 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3153 None => list_id_header
3154 .trim()
3155 .trim_start_matches('<')
3156 .trim_end_matches('>'),
3157 }
3158 .to_string())
3159}
3160
3161async fn create_or_lookup_mailinglist_or_broadcast(
3176 context: &Context,
3177 allow_creation: bool,
3178 list_id_header: &str,
3179 from_id: ContactId,
3180 mime_parser: &MimeMessage,
3181) -> Result<Option<(ChatId, Blocked, bool)>> {
3182 let listid = mailinglist_header_listid(list_id_header)?;
3183
3184 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3185 return Ok(Some((chat_id, blocked, false)));
3186 }
3187
3188 let chattype = if mime_parser.was_encrypted() {
3189 Chattype::InBroadcast
3190 } else {
3191 Chattype::Mailinglist
3192 };
3193
3194 let name = if chattype == Chattype::InBroadcast {
3195 mime_parser
3196 .get_header(HeaderDef::ChatGroupName)
3197 .unwrap_or("Broadcast Channel")
3198 } else {
3199 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3200 };
3201
3202 if allow_creation {
3203 let param = mime_parser.list_post.as_ref().map(|list_post| {
3205 let mut p = Params::new();
3206 p.set(Param::ListPost, list_post);
3207 p.to_string()
3208 });
3209
3210 let is_bot = context.get_config_bool(Config::Bot).await?;
3211 let blocked = if is_bot {
3212 Blocked::Not
3213 } else {
3214 Blocked::Request
3215 };
3216 let chat_id = ChatId::create_multiuser_record(
3217 context,
3218 chattype,
3219 &listid,
3220 name,
3221 blocked,
3222 param,
3223 mime_parser.timestamp_sent,
3224 )
3225 .await
3226 .with_context(|| {
3227 format!(
3228 "failed to create mailinglist '{}' for grpid={}",
3229 &name, &listid
3230 )
3231 })?;
3232
3233 chat::add_to_chat_contacts_table(
3234 context,
3235 mime_parser.timestamp_sent,
3236 chat_id,
3237 &[ContactId::SELF],
3238 )
3239 .await?;
3240 if chattype == Chattype::InBroadcast {
3241 chat::add_to_chat_contacts_table(
3242 context,
3243 mime_parser.timestamp_sent,
3244 chat_id,
3245 &[from_id],
3246 )
3247 .await?;
3248 }
3249 Ok(Some((chat_id, blocked, true)))
3250 } else {
3251 info!(context, "Creating list forbidden by caller.");
3252 Ok(None)
3253 }
3254}
3255
3256fn compute_mailinglist_name(
3257 list_id_header: &str,
3258 listid: &str,
3259 mime_parser: &MimeMessage,
3260) -> String {
3261 let mut name = match LIST_ID_REGEX
3262 .captures(list_id_header)
3263 .and_then(|caps| caps.get(1))
3264 {
3265 Some(cap) => cap.as_str().trim().to_string(),
3266 None => "".to_string(),
3267 };
3268
3269 if listid.ends_with(".list-id.mcsv.net") {
3273 if let Some(display_name) = &mime_parser.from.display_name {
3274 name.clone_from(display_name);
3275 }
3276 }
3277
3278 let subject = mime_parser.get_subject().unwrap_or_default();
3282 static SUBJECT: LazyLock<Regex> =
3283 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3285 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3286 }
3287
3288 if name.is_empty()
3295 && (mime_parser.from.addr.contains("noreply")
3296 || mime_parser.from.addr.contains("no-reply")
3297 || mime_parser.from.addr.starts_with("notifications@")
3298 || mime_parser.from.addr.starts_with("newsletter@")
3299 || listid.ends_with(".xt.local"))
3300 {
3301 if let Some(display_name) = &mime_parser.from.display_name {
3302 name.clone_from(display_name);
3303 }
3304 }
3305
3306 if name.is_empty() {
3309 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3311 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3312 if let Some(cap) = PREFIX_32_CHARS_HEX
3313 .captures(listid)
3314 .and_then(|caps| caps.get(2))
3315 {
3316 name = cap.as_str().to_string();
3317 } else {
3318 name = listid.to_string();
3319 }
3320 }
3321
3322 sanitize_single_line(&name)
3323}
3324
3325async fn apply_mailinglist_changes(
3329 context: &Context,
3330 mime_parser: &MimeMessage,
3331 chat_id: ChatId,
3332) -> Result<()> {
3333 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3334 return Ok(());
3335 };
3336
3337 let mut chat = Chat::load_from_db(context, chat_id).await?;
3338 if chat.typ != Chattype::Mailinglist {
3339 return Ok(());
3340 }
3341 let listid = &chat.grpid;
3342
3343 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3344 if chat.name != new_name
3345 && chat_id
3346 .update_timestamp(
3347 context,
3348 Param::GroupNameTimestamp,
3349 mime_parser.timestamp_sent,
3350 )
3351 .await?
3352 {
3353 info!(context, "Updating listname for chat {chat_id}.");
3354 context
3355 .sql
3356 .execute("UPDATE chats SET name=? WHERE id=?;", (new_name, chat_id))
3357 .await?;
3358 context.emit_event(EventType::ChatModified(chat_id));
3359 }
3360
3361 let Some(list_post) = &mime_parser.list_post else {
3362 return Ok(());
3363 };
3364
3365 let list_post = match ContactAddress::new(list_post) {
3366 Ok(list_post) => list_post,
3367 Err(err) => {
3368 warn!(context, "Invalid List-Post: {:#}.", err);
3369 return Ok(());
3370 }
3371 };
3372 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3373 let mut contact = Contact::get_by_id(context, contact_id).await?;
3374 if contact.param.get(Param::ListId) != Some(listid) {
3375 contact.param.set(Param::ListId, listid);
3376 contact.update_param(context).await?;
3377 }
3378
3379 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3380 if list_post.as_ref() != old_list_post {
3381 chat.param.remove(Param::ListPost);
3384 chat.update_param(context).await?;
3385 }
3386 } else {
3387 chat.param.set(Param::ListPost, list_post);
3388 chat.update_param(context).await?;
3389 }
3390
3391 Ok(())
3392}
3393
3394async fn apply_out_broadcast_changes(
3395 context: &Context,
3396 mime_parser: &MimeMessage,
3397 chat: &mut Chat,
3398 from_id: ContactId,
3399) -> Result<GroupChangesInfo> {
3400 ensure!(chat.typ == Chattype::OutBroadcast);
3401
3402 if let Some(_removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3403 remove_from_chat_contacts_table(context, chat.id, from_id).await?;
3405
3406 return Ok(GroupChangesInfo {
3407 better_msg: Some("".to_string()),
3408 added_removed_id: None,
3409 silent: true,
3410 extra_msgs: vec![],
3411 });
3412 }
3413
3414 Ok(GroupChangesInfo::default())
3415}
3416
3417async fn apply_in_broadcast_changes(
3418 context: &Context,
3419 mime_parser: &MimeMessage,
3420 chat: &mut Chat,
3421 from_id: ContactId,
3422) -> Result<GroupChangesInfo> {
3423 ensure!(chat.typ == Chattype::InBroadcast);
3424
3425 let mut send_event_chat_modified = false;
3426 let mut better_msg = None;
3427
3428 apply_chat_name_and_avatar_changes(
3429 context,
3430 mime_parser,
3431 from_id,
3432 chat,
3433 &mut send_event_chat_modified,
3434 &mut better_msg,
3435 )
3436 .await?;
3437
3438 if let Some(_removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3439 if from_id == ContactId::SELF {
3442 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
3443 }
3444 }
3445
3446 if send_event_chat_modified {
3447 context.emit_event(EventType::ChatModified(chat.id));
3448 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3449 }
3450 Ok(GroupChangesInfo {
3451 better_msg,
3452 added_removed_id: None,
3453 silent: false,
3454 extra_msgs: vec![],
3455 })
3456}
3457
3458async fn create_adhoc_group(
3460 context: &Context,
3461 mime_parser: &MimeMessage,
3462 create_blocked: Blocked,
3463 from_id: ContactId,
3464 to_ids: &[ContactId],
3465 grpname: &str,
3466) -> Result<Option<(ChatId, Blocked)>> {
3467 let mut member_ids: Vec<ContactId> = to_ids.to_vec();
3468 if !member_ids.contains(&(from_id)) {
3469 member_ids.push(from_id);
3470 }
3471 if !member_ids.contains(&(ContactId::SELF)) {
3472 member_ids.push(ContactId::SELF);
3473 }
3474
3475 if mime_parser.is_mailinglist_message() {
3476 return Ok(None);
3477 }
3478 if mime_parser
3479 .get_header(HeaderDef::ChatGroupMemberRemoved)
3480 .is_some()
3481 {
3482 info!(
3483 context,
3484 "Message removes member from unknown ad-hoc group (TRASH)."
3485 );
3486 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3487 }
3488 if member_ids.len() < 2 {
3489 info!(
3490 context,
3491 "Not creating ad hoc group with less than 2 members."
3492 );
3493 return Ok(None);
3494 }
3495
3496 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3497 context,
3498 Chattype::Group,
3499 "", grpname,
3501 create_blocked,
3502 None,
3503 mime_parser.timestamp_sent,
3504 )
3505 .await?;
3506
3507 info!(
3508 context,
3509 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3510 );
3511 chat::add_to_chat_contacts_table(
3512 context,
3513 mime_parser.timestamp_sent,
3514 new_chat_id,
3515 &member_ids,
3516 )
3517 .await?;
3518
3519 context.emit_event(EventType::ChatModified(new_chat_id));
3520 chatlist_events::emit_chatlist_changed(context);
3521 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3522
3523 Ok(Some((new_chat_id, create_blocked)))
3524}
3525
3526#[derive(Debug, PartialEq, Eq)]
3527enum VerifiedEncryption {
3528 Verified,
3529 NotVerified(String), }
3531
3532async fn has_verified_encryption(
3536 context: &Context,
3537 mimeparser: &MimeMessage,
3538 from_id: ContactId,
3539) -> Result<VerifiedEncryption> {
3540 use VerifiedEncryption::*;
3541
3542 if !mimeparser.was_encrypted() {
3543 return Ok(NotVerified("This message is not encrypted".to_string()));
3544 };
3545
3546 if from_id == ContactId::SELF {
3547 return Ok(Verified);
3548 }
3549
3550 let from_contact = Contact::get_by_id(context, from_id).await?;
3551
3552 let Some(fingerprint) = from_contact.fingerprint() else {
3553 return Ok(NotVerified(
3554 "The message was sent without encryption".to_string(),
3555 ));
3556 };
3557
3558 if from_contact.get_verifier_id(context).await?.is_none() {
3559 return Ok(NotVerified(
3560 "The message was sent by non-verified contact".to_string(),
3561 ));
3562 }
3563
3564 let signed_with_verified_key = mimeparser
3565 .signature
3566 .as_ref()
3567 .is_some_and(|signature| *signature == fingerprint);
3568 if signed_with_verified_key {
3569 Ok(Verified)
3570 } else {
3571 Ok(NotVerified(
3572 "The message was sent with non-verified encryption".to_string(),
3573 ))
3574 }
3575}
3576
3577async fn mark_recipients_as_verified(
3578 context: &Context,
3579 from_id: ContactId,
3580 mimeparser: &MimeMessage,
3581) -> Result<()> {
3582 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
3583
3584 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
3588
3589 for gossiped_key in mimeparser
3590 .gossiped_keys
3591 .values()
3592 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
3593 {
3594 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
3595 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
3596 continue;
3597 };
3598
3599 if to_id == ContactId::SELF || to_id == from_id {
3600 continue;
3601 }
3602
3603 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
3604 }
3605
3606 Ok(())
3607}
3608
3609async fn get_previous_message(
3613 context: &Context,
3614 mime_parser: &MimeMessage,
3615) -> Result<Option<Message>> {
3616 if let Some(field) = mime_parser.get_header(HeaderDef::References) {
3617 if let Some(rfc724mid) = parse_message_ids(field).last() {
3618 if let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await? {
3619 return Message::load_from_db_optional(context, msg_id).await;
3620 }
3621 }
3622 }
3623 Ok(None)
3624}
3625
3626async fn get_parent_message(
3631 context: &Context,
3632 references: Option<&str>,
3633 in_reply_to: Option<&str>,
3634) -> Result<Option<Message>> {
3635 let mut mids = Vec::new();
3636 if let Some(field) = in_reply_to {
3637 mids = parse_message_ids(field);
3638 }
3639 if let Some(field) = references {
3640 mids.append(&mut parse_message_ids(field));
3641 }
3642 message::get_by_rfc724_mids(context, &mids).await
3643}
3644
3645pub(crate) async fn get_prefetch_parent_message(
3646 context: &Context,
3647 headers: &[mailparse::MailHeader<'_>],
3648) -> Result<Option<Message>> {
3649 get_parent_message(
3650 context,
3651 headers.get_header_value(HeaderDef::References).as_deref(),
3652 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
3653 )
3654 .await
3655}
3656
3657async fn add_or_lookup_contacts_by_address_list(
3659 context: &Context,
3660 address_list: &[SingleInfo],
3661 origin: Origin,
3662) -> Result<Vec<Option<ContactId>>> {
3663 let mut contact_ids = Vec::new();
3664 for info in address_list {
3665 let addr = &info.addr;
3666 if !may_be_valid_addr(addr) {
3667 contact_ids.push(None);
3668 continue;
3669 }
3670 let display_name = info.display_name.as_deref();
3671 if let Ok(addr) = ContactAddress::new(addr) {
3672 let (contact_id, _) =
3673 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
3674 .await?;
3675 contact_ids.push(Some(contact_id));
3676 } else {
3677 warn!(context, "Contact with address {:?} cannot exist.", addr);
3678 contact_ids.push(None);
3679 }
3680 }
3681
3682 Ok(contact_ids)
3683}
3684
3685async fn add_or_lookup_key_contacts(
3687 context: &Context,
3688 address_list: &[SingleInfo],
3689 gossiped_keys: &BTreeMap<String, GossipedKey>,
3690 fingerprints: &[Fingerprint],
3691 origin: Origin,
3692) -> Result<Vec<Option<ContactId>>> {
3693 let mut contact_ids = Vec::new();
3694 let mut fingerprint_iter = fingerprints.iter();
3695 for info in address_list {
3696 let addr = &info.addr;
3697 if !may_be_valid_addr(addr) {
3698 contact_ids.push(None);
3699 continue;
3700 }
3701 let fingerprint: String = if let Some(fp) = fingerprint_iter.next() {
3702 fp.hex()
3704 } else if let Some(key) = gossiped_keys.get(addr) {
3705 key.public_key.dc_fingerprint().hex()
3706 } else if context.is_self_addr(addr).await? {
3707 contact_ids.push(Some(ContactId::SELF));
3708 continue;
3709 } else {
3710 contact_ids.push(None);
3711 continue;
3712 };
3713 let display_name = info.display_name.as_deref();
3714 if let Ok(addr) = ContactAddress::new(addr) {
3715 let (contact_id, _) = Contact::add_or_lookup_ex(
3716 context,
3717 display_name.unwrap_or_default(),
3718 &addr,
3719 &fingerprint,
3720 origin,
3721 )
3722 .await?;
3723 contact_ids.push(Some(contact_id));
3724 } else {
3725 warn!(context, "Contact with address {:?} cannot exist.", addr);
3726 contact_ids.push(None);
3727 }
3728 }
3729
3730 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
3731 Ok(contact_ids)
3732}
3733
3734async fn lookup_key_contact_by_address(
3739 context: &Context,
3740 addr: &str,
3741 chat_id: Option<ChatId>,
3742) -> Result<Option<ContactId>> {
3743 if context.is_self_addr(addr).await? {
3744 if chat_id.is_none() {
3745 return Ok(Some(ContactId::SELF));
3746 }
3747 let is_self_in_chat = context
3748 .sql
3749 .exists(
3750 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
3751 (chat_id,),
3752 )
3753 .await?;
3754 if is_self_in_chat {
3755 return Ok(Some(ContactId::SELF));
3756 }
3757 }
3758 let contact_id: Option<ContactId> = match chat_id {
3759 Some(chat_id) => {
3760 context
3761 .sql
3762 .query_row_optional(
3763 "SELECT id FROM contacts
3764 WHERE contacts.addr=?
3765 AND EXISTS (SELECT 1 FROM chats_contacts
3766 WHERE contact_id=contacts.id
3767 AND chat_id=?)
3768 AND fingerprint<>'' -- Should always be true
3769 ",
3770 (addr, chat_id),
3771 |row| {
3772 let contact_id: ContactId = row.get(0)?;
3773 Ok(contact_id)
3774 },
3775 )
3776 .await?
3777 }
3778 None => {
3779 context
3780 .sql
3781 .query_row_optional(
3782 "SELECT id FROM contacts
3783 WHERE addr=?
3784 AND fingerprint<>''
3785 ORDER BY
3786 (
3787 SELECT COUNT(*) FROM chats c
3788 INNER JOIN chats_contacts cc
3789 ON c.id=cc.chat_id
3790 WHERE c.type=?
3791 AND c.id>?
3792 AND c.blocked=?
3793 AND cc.contact_id=contacts.id
3794 ) DESC,
3795 last_seen DESC, id DESC
3796 ",
3797 (
3798 addr,
3799 Chattype::Single,
3800 constants::DC_CHAT_ID_LAST_SPECIAL,
3801 Blocked::Not,
3802 ),
3803 |row| {
3804 let contact_id: ContactId = row.get(0)?;
3805 Ok(contact_id)
3806 },
3807 )
3808 .await?
3809 }
3810 };
3811 Ok(contact_id)
3812}
3813
3814async fn lookup_key_contact_by_fingerprint(
3815 context: &Context,
3816 fingerprint: &str,
3817) -> Result<Option<ContactId>> {
3818 logged_debug_assert!(
3819 context,
3820 !fingerprint.is_empty(),
3821 "lookup_key_contact_by_fingerprint: fingerprint is empty."
3822 );
3823 if fingerprint.is_empty() {
3824 return Ok(None);
3826 }
3827 if let Some(contact_id) = context
3828 .sql
3829 .query_row_optional(
3830 "SELECT id FROM contacts
3831 WHERE fingerprint=? AND fingerprint!=''",
3832 (fingerprint,),
3833 |row| {
3834 let contact_id: ContactId = row.get(0)?;
3835 Ok(contact_id)
3836 },
3837 )
3838 .await?
3839 {
3840 Ok(Some(contact_id))
3841 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
3842 if self_fp == fingerprint {
3843 Ok(Some(ContactId::SELF))
3844 } else {
3845 Ok(None)
3846 }
3847 } else {
3848 Ok(None)
3849 }
3850}
3851
3852async fn lookup_key_contacts_by_address_list(
3868 context: &Context,
3869 address_list: &[SingleInfo],
3870 fingerprints: &[Fingerprint],
3871 chat_id: Option<ChatId>,
3872) -> Result<Vec<Option<ContactId>>> {
3873 let mut contact_ids = Vec::new();
3874 let mut fingerprint_iter = fingerprints.iter();
3875 for info in address_list {
3876 let addr = &info.addr;
3877 if !may_be_valid_addr(addr) {
3878 contact_ids.push(None);
3879 continue;
3880 }
3881
3882 if let Some(fp) = fingerprint_iter.next() {
3883 let display_name = info.display_name.as_deref();
3885 let fingerprint: String = fp.hex();
3886
3887 if let Ok(addr) = ContactAddress::new(addr) {
3888 let (contact_id, _) = Contact::add_or_lookup_ex(
3889 context,
3890 display_name.unwrap_or_default(),
3891 &addr,
3892 &fingerprint,
3893 Origin::Hidden,
3894 )
3895 .await?;
3896 contact_ids.push(Some(contact_id));
3897 } else {
3898 warn!(context, "Contact with address {:?} cannot exist.", addr);
3899 contact_ids.push(None);
3900 }
3901 } else {
3902 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
3903 contact_ids.push(contact_id);
3904 }
3905 }
3906 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
3907 Ok(contact_ids)
3908}
3909
3910fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
3913 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
3914 || mime_parser.get_header(HeaderDef::Sender).is_some()
3915}
3916
3917#[cfg(test)]
3918mod receive_imf_tests;