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