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