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