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