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