1use std::cmp;
4use std::collections::{BTreeMap, BTreeSet, HashSet};
5use std::iter;
6use std::sync::LazyLock;
7
8use anyhow::{Context as _, Result, ensure};
9use deltachat_contact_tools::{
10 ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_bidi_characters,
11 sanitize_single_line,
12};
13use mailparse::SingleInfo;
14use num_traits::FromPrimitive;
15use regex::Regex;
16
17use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ChatVisibility, save_broadcast_secret};
18use crate::config::Config;
19use crate::constants::{self, Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
20use crate::contact::{self, Contact, ContactId, Origin, mark_contact_id_as_verified};
21use crate::context::Context;
22use crate::debug_logging::maybe_set_logging_xdc_inner;
23use crate::download::{DownloadState, msg_is_downloaded_for};
24use crate::ephemeral::{Timer as EphemeralTimer, stock_ephemeral_timer_changed};
25use crate::events::EventType;
26use crate::headerdef::{HeaderDef, HeaderDefMap};
27use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table};
28use crate::key::{DcKey, Fingerprint};
29use crate::key::{
30 load_self_public_key, load_self_public_key_opt, self_fingerprint, self_fingerprint_opt,
31};
32use crate::log::{LogExt as _, warn};
33use crate::message::{
34 self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
35};
36use crate::mimeparser::{
37 AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids,
38};
39use crate::param::{Param, Params};
40use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub, iroh_topic_from_str};
41use crate::reaction::{Reaction, set_msg_reaction};
42use crate::rusqlite::OptionalExtension;
43use crate::securejoin::{
44 self, get_secure_join_step, handle_securejoin_handshake, observe_securejoin_on_other_device,
45};
46use crate::simplify;
47use crate::stats::STATISTICS_BOT_EMAIL;
48use crate::stock_str;
49use crate::sync::Sync::*;
50use crate::tools::{
51 self, buf_compress, normalize_text, remove_subject_prefix, validate_broadcast_secret,
52};
53use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
54use crate::{logged_debug_assert, mimeparser};
55
56#[derive(Debug)]
61pub struct ReceivedMsg {
62 pub chat_id: ChatId,
64
65 pub state: MessageState,
67
68 pub hidden: bool,
70
71 pub sort_timestamp: i64,
73
74 pub msg_ids: Vec<MsgId>,
76
77 pub needs_delete_job: bool,
79}
80
81#[derive(Debug)]
94enum ChatAssignment {
95 Trash,
97
98 GroupChat { grpid: String },
103
104 MailingListOrBroadcast,
119
120 AdHocGroup,
124
125 ExistingChat {
128 chat_id: ChatId,
131
132 chat_id_blocked: Blocked,
140 },
141
142 OneOneChat,
150}
151
152#[cfg(any(test, feature = "internals"))]
157pub async fn receive_imf(
158 context: &Context,
159 imf_raw: &[u8],
160 seen: bool,
161) -> Result<Option<ReceivedMsg>> {
162 let mail = mailparse::parse_mail(imf_raw).context("can't parse mail")?;
163 let rfc724_mid = crate::imap::prefetch_get_message_id(&mail.headers)
164 .unwrap_or_else(crate::imap::create_message_id);
165 receive_imf_from_inbox(context, &rfc724_mid, imf_raw, seen).await
166}
167
168#[cfg(any(test, feature = "internals"))]
172pub(crate) async fn receive_imf_from_inbox(
173 context: &Context,
174 rfc724_mid: &str,
175 imf_raw: &[u8],
176 seen: bool,
177) -> Result<Option<ReceivedMsg>> {
178 receive_imf_inner(context, rfc724_mid, imf_raw, seen).await
179}
180
181async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
186 let row_id = context
187 .sql
188 .insert(
189 "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
190 (rfc724_mid, DC_CHAT_ID_TRASH),
191 )
192 .await?;
193 let msg_id = MsgId::new(u32::try_from(row_id)?);
194 Ok(msg_id)
195}
196
197async fn get_to_and_past_contact_ids(
198 context: &Context,
199 mime_parser: &MimeMessage,
200 chat_assignment: &mut ChatAssignment,
201 parent_message: &Option<Message>,
202 incoming_origin: Origin,
203) -> Result<(Vec<Option<ContactId>>, Vec<Option<ContactId>>)> {
204 let to_ids: Vec<Option<ContactId>>;
216 let past_ids: Vec<Option<ContactId>>;
217
218 let chat_id = match chat_assignment {
225 ChatAssignment::Trash => None,
226 ChatAssignment::GroupChat { grpid } => {
227 if let Some((chat_id, _blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
228 Some(chat_id)
229 } else {
230 None
231 }
232 }
233 ChatAssignment::AdHocGroup => {
234 None
239 }
240 ChatAssignment::ExistingChat { chat_id, .. } => Some(*chat_id),
241 ChatAssignment::MailingListOrBroadcast => None,
242 ChatAssignment::OneOneChat => {
243 if !mime_parser.incoming {
244 parent_message.as_ref().map(|m| m.chat_id)
245 } else {
246 None
247 }
248 }
249 };
250
251 let member_fingerprints = mime_parser.chat_group_member_fingerprints();
252 let to_member_fingerprints;
253 let past_member_fingerprints;
254
255 if !member_fingerprints.is_empty() {
256 if member_fingerprints.len() >= mime_parser.recipients.len() {
257 (to_member_fingerprints, past_member_fingerprints) =
258 member_fingerprints.split_at(mime_parser.recipients.len());
259 } else {
260 warn!(
261 context,
262 "Unexpected length of the fingerprint header, expected at least {}, got {}.",
263 mime_parser.recipients.len(),
264 member_fingerprints.len()
265 );
266 to_member_fingerprints = &[];
267 past_member_fingerprints = &[];
268 }
269 } else {
270 to_member_fingerprints = &[];
271 past_member_fingerprints = &[];
272 }
273
274 match chat_assignment {
275 ChatAssignment::GroupChat { .. } => {
276 to_ids = add_or_lookup_key_contacts(
277 context,
278 &mime_parser.recipients,
279 &mime_parser.gossiped_keys,
280 to_member_fingerprints,
281 Origin::Hidden,
282 )
283 .await?;
284
285 if let Some(chat_id) = chat_id {
286 past_ids = lookup_key_contacts_fallback_to_chat(
287 context,
288 &mime_parser.past_members,
289 past_member_fingerprints,
290 Some(chat_id),
291 )
292 .await?;
293 } else {
294 past_ids = add_or_lookup_key_contacts(
295 context,
296 &mime_parser.past_members,
297 &mime_parser.gossiped_keys,
298 past_member_fingerprints,
299 Origin::Hidden,
300 )
301 .await?;
302 }
303 }
304 ChatAssignment::Trash => {
305 to_ids = Vec::new();
306 past_ids = Vec::new();
307 }
308 ChatAssignment::ExistingChat { chat_id, .. } => {
309 let chat = Chat::load_from_db(context, *chat_id).await?;
310 if chat.is_encrypted(context).await? {
311 to_ids = add_or_lookup_key_contacts(
312 context,
313 &mime_parser.recipients,
314 &mime_parser.gossiped_keys,
315 to_member_fingerprints,
316 Origin::Hidden,
317 )
318 .await?;
319 past_ids = lookup_key_contacts_fallback_to_chat(
320 context,
321 &mime_parser.past_members,
322 past_member_fingerprints,
323 Some(*chat_id),
324 )
325 .await?;
326 } else {
327 to_ids = add_or_lookup_contacts_by_address_list(
328 context,
329 &mime_parser.recipients,
330 if !mime_parser.incoming {
331 Origin::OutgoingTo
332 } else if incoming_origin.is_known() {
333 Origin::IncomingTo
334 } else {
335 Origin::IncomingUnknownTo
336 },
337 )
338 .await?;
339
340 past_ids = add_or_lookup_contacts_by_address_list(
341 context,
342 &mime_parser.past_members,
343 Origin::Hidden,
344 )
345 .await?;
346 }
347 }
348 ChatAssignment::AdHocGroup => {
349 to_ids = add_or_lookup_contacts_by_address_list(
350 context,
351 &mime_parser.recipients,
352 if !mime_parser.incoming {
353 Origin::OutgoingTo
354 } else if incoming_origin.is_known() {
355 Origin::IncomingTo
356 } else {
357 Origin::IncomingUnknownTo
358 },
359 )
360 .await?;
361
362 past_ids = add_or_lookup_contacts_by_address_list(
363 context,
364 &mime_parser.past_members,
365 Origin::Hidden,
366 )
367 .await?;
368 }
369 ChatAssignment::OneOneChat | ChatAssignment::MailingListOrBroadcast => {
373 let pgp_to_ids = add_or_lookup_key_contacts(
374 context,
375 &mime_parser.recipients,
376 &mime_parser.gossiped_keys,
377 to_member_fingerprints,
378 Origin::Hidden,
379 )
380 .await?;
381 if pgp_to_ids
382 .first()
383 .is_some_and(|contact_id| contact_id.is_some())
384 {
385 to_ids = pgp_to_ids
389 } else {
390 let ids = if mime_parser.was_encrypted() {
391 let mut recipient_fps = mime_parser
392 .signature
393 .as_ref()
394 .map(|(_, recipient_fps)| recipient_fps.iter().cloned().collect::<Vec<_>>())
395 .unwrap_or_default();
396 if !recipient_fps.is_empty() && recipient_fps.len() <= 2 {
399 let self_fp = load_self_public_key(context).await?.dc_fingerprint();
400 recipient_fps.retain(|fp| *fp != self_fp);
401 if recipient_fps.is_empty() {
402 vec![Some(ContactId::SELF)]
403 } else {
404 add_or_lookup_key_contacts(
405 context,
406 &mime_parser.recipients,
407 &mime_parser.gossiped_keys,
408 &recipient_fps,
409 Origin::Hidden,
410 )
411 .await?
412 }
413 } else {
414 lookup_key_contacts_fallback_to_chat(
415 context,
416 &mime_parser.recipients,
417 to_member_fingerprints,
418 chat_id,
419 )
420 .await?
421 }
422 } else {
423 vec![]
424 };
425 if mime_parser.was_encrypted() && !ids.contains(&None)
426 || ids
429 .iter()
430 .any(|&c| c.is_some() && c != Some(ContactId::SELF))
431 {
432 to_ids = ids;
433 } else {
434 if mime_parser.was_encrypted() {
435 warn!(
436 context,
437 "No key-contact looked up. Downgrading to AdHocGroup."
438 );
439 *chat_assignment = ChatAssignment::AdHocGroup;
440 }
441 to_ids = add_or_lookup_contacts_by_address_list(
442 context,
443 &mime_parser.recipients,
444 if !mime_parser.incoming {
445 Origin::OutgoingTo
446 } else if incoming_origin.is_known() {
447 Origin::IncomingTo
448 } else {
449 Origin::IncomingUnknownTo
450 },
451 )
452 .await?;
453 }
454 }
455
456 past_ids = add_or_lookup_contacts_by_address_list(
457 context,
458 &mime_parser.past_members,
459 Origin::Hidden,
460 )
461 .await?;
462 }
463 };
464
465 Ok((to_ids, past_ids))
466}
467
468pub(crate) async fn receive_imf_inner(
478 context: &Context,
479 rfc724_mid: &str,
480 imf_raw: &[u8],
481 seen: bool,
482) -> Result<Option<ReceivedMsg>> {
483 ensure!(
484 !context
485 .get_config_bool(Config::SimulateReceiveImfError)
486 .await?
487 );
488 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
489 info!(
490 context,
491 "receive_imf: incoming message mime-body:\n{}",
492 String::from_utf8_lossy(imf_raw),
493 );
494 }
495
496 let trash = || async {
497 let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
498 Ok(Some(ReceivedMsg {
499 chat_id: DC_CHAT_ID_TRASH,
500 state: MessageState::Undefined,
501 hidden: false,
502 sort_timestamp: 0,
503 msg_ids,
504 needs_delete_job: false,
505 }))
506 };
507
508 let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw).await {
509 Err(err) => {
510 warn!(context, "receive_imf: can't parse MIME: {err:#}.");
511 if rfc724_mid.starts_with(GENERATED_PREFIX) {
512 return Ok(None);
514 }
515 return trash().await;
516 }
517 Ok(mime_parser) => mime_parser,
518 };
519
520 let rfc724_mid_orig = &mime_parser
521 .get_rfc724_mid()
522 .unwrap_or(rfc724_mid.to_string());
523
524 if let Some((_, recipient_fps)) = &mime_parser.signature
525 && !recipient_fps.is_empty()
526 && let Some(self_pubkey) = load_self_public_key_opt(context).await?
527 && !recipient_fps.contains(&self_pubkey.dc_fingerprint())
528 {
529 warn!(
530 context,
531 "Message {rfc724_mid_orig:?} is not intended for us (TRASH)."
532 );
533 return trash().await;
534 }
535 info!(
536 context,
537 "Receiving message {rfc724_mid_orig:?}, seen={seen}...",
538 );
539
540 let (replace_msg_id, replace_chat_id);
543 if mime_parser.pre_message == mimeparser::PreMessageMode::Post {
544 replace_msg_id = None;
547 replace_chat_id = None;
548 } else if let Some(old_msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
549 replace_msg_id = Some(old_msg_id);
553 replace_chat_id = if let Some(msg) = Message::load_from_db_optional(context, old_msg_id)
554 .await?
555 .filter(|msg| msg.download_state() != DownloadState::Done)
556 {
557 info!(context, "Message already partly in DB, replacing.");
559 Some(msg.chat_id)
560 } else {
561 None
564 };
565 } else {
566 replace_msg_id = if rfc724_mid_orig == rfc724_mid {
567 None
568 } else {
569 message::rfc724_mid_exists(context, rfc724_mid_orig).await?
570 };
571 replace_chat_id = None;
572 }
573
574 if replace_chat_id.is_some() {
575 } else if let Some(msg_id) = replace_msg_id {
577 info!(context, "Message is already downloaded.");
578 if mime_parser.incoming {
579 return Ok(None);
580 }
581 let self_addr = context.get_primary_self_addr().await?;
584 context
585 .sql
586 .execute(
587 "DELETE FROM smtp \
588 WHERE rfc724_mid=?1 AND (recipients LIKE ?2 OR recipients LIKE ('% ' || ?2))",
589 (rfc724_mid_orig, &self_addr),
590 )
591 .await?;
592 if !context
593 .sql
594 .exists(
595 "SELECT COUNT(*) FROM smtp WHERE rfc724_mid=?",
596 (rfc724_mid_orig,),
597 )
598 .await?
599 {
600 msg_id.set_delivered(context).await?;
601 }
602 return Ok(None);
603 };
604
605 let prevent_rename = should_prevent_rename(&mime_parser);
606
607 let fingerprint = mime_parser.signature.as_ref().map(|(fp, _)| fp);
619 let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
620 context,
621 &mime_parser.from,
622 fingerprint,
623 prevent_rename,
624 false,
625 )
626 .await?
627 {
628 Some(contact_id_res) => contact_id_res,
629 None => {
630 warn!(
631 context,
632 "receive_imf: From field does not contain an acceptable address."
633 );
634 return Ok(None);
635 }
636 };
637
638 let parent_message = get_parent_message(
649 context,
650 mime_parser.get_header(HeaderDef::References),
651 mime_parser.get_header(HeaderDef::InReplyTo),
652 )
653 .await?
654 .filter(|p| Some(p.id) != replace_msg_id);
655
656 let mut chat_assignment =
657 decide_chat_assignment(context, &mime_parser, &parent_message, rfc724_mid, from_id).await?;
658 let (to_ids, past_ids) = get_to_and_past_contact_ids(
659 context,
660 &mime_parser,
661 &mut chat_assignment,
662 &parent_message,
663 incoming_origin,
664 )
665 .await?;
666
667 let received_msg;
668 if let Some(_step) = get_secure_join_step(&mime_parser) {
669 let res = if mime_parser.incoming {
670 handle_securejoin_handshake(context, &mut mime_parser, from_id)
671 .await
672 .with_context(|| {
673 format!(
674 "Error in Secure-Join '{}' message handling",
675 mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
676 )
677 })?
678 } else if let Some(to_id) = to_ids.first().copied().flatten() {
679 observe_securejoin_on_other_device(context, &mime_parser, to_id)
681 .await
682 .with_context(|| {
683 format!(
684 "Error in Secure-Join '{}' watching",
685 mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
686 )
687 })?
688 } else {
689 securejoin::HandshakeMessage::Propagate
690 };
691
692 match res {
693 securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
694 let msg_id = insert_tombstone(context, rfc724_mid).await?;
695 received_msg = Some(ReceivedMsg {
696 chat_id: DC_CHAT_ID_TRASH,
697 state: MessageState::InSeen,
698 hidden: false,
699 sort_timestamp: mime_parser.timestamp_sent,
700 msg_ids: vec![msg_id],
701 needs_delete_job: res == securejoin::HandshakeMessage::Done,
702 });
703 }
704 securejoin::HandshakeMessage::Propagate => {
705 received_msg = None;
706 }
707 }
708 } else {
709 received_msg = None;
710 }
711
712 let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?;
713
714 if verified_encryption == VerifiedEncryption::Verified {
715 mark_recipients_as_verified(context, from_id, &mime_parser).await?;
716 }
717
718 let is_old_contact_request;
719 let received_msg = if let Some(received_msg) = received_msg {
720 is_old_contact_request = false;
721 received_msg
722 } else {
723 let is_dc_message = if mime_parser.has_chat_version() {
724 MessengerMessage::Yes
725 } else if let Some(parent_message) = &parent_message {
726 match parent_message.is_dc_message {
727 MessengerMessage::No => MessengerMessage::No,
728 MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply,
729 }
730 } else {
731 MessengerMessage::No
732 };
733
734 let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
735 .unwrap_or_default();
736
737 let allow_creation = if mime_parser.decrypting_failed {
738 false
739 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
740 && is_dc_message == MessengerMessage::No
741 && !context.get_config_bool(Config::IsChatmail).await?
742 {
743 match show_emails {
746 ShowEmails::Off | ShowEmails::AcceptedContacts => false,
747 ShowEmails::All => true,
748 }
749 } else {
750 !mime_parser.parts.iter().all(|part| part.is_reaction)
751 };
752
753 let to_id = if mime_parser.incoming {
754 ContactId::SELF
755 } else {
756 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
757 };
758
759 let (chat_id, chat_id_blocked, is_created) = do_chat_assignment(
760 context,
761 &chat_assignment,
762 from_id,
763 &to_ids,
764 &past_ids,
765 to_id,
766 allow_creation,
767 &mut mime_parser,
768 parent_message,
769 )
770 .await?;
771 is_old_contact_request = chat_id_blocked == Blocked::Request && !is_created;
772
773 add_parts(
775 context,
776 &mut mime_parser,
777 imf_raw,
778 &to_ids,
779 &past_ids,
780 rfc724_mid_orig,
781 from_id,
782 seen,
783 replace_msg_id,
784 prevent_rename,
785 chat_id,
786 chat_id_blocked,
787 is_dc_message,
788 )
789 .await
790 .context("add_parts error")?
791 };
792
793 if !from_id.is_special() {
794 contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
795 }
796
797 let chat_id = received_msg.chat_id;
801 if !chat_id.is_special() {
802 for gossiped_key in mime_parser.gossiped_keys.values() {
803 context
804 .sql
805 .transaction(move |transaction| {
806 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
807 transaction.execute(
808 "INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
809 VALUES (?, ?, ?)
810 ON CONFLICT (chat_id, fingerprint)
811 DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
812 (chat_id, &fingerprint, mime_parser.timestamp_sent),
813 )?;
814
815 Ok(())
816 })
817 .await?;
818 }
819 }
820
821 let insert_msg_id = if let Some(msg_id) = received_msg.msg_ids.last() {
822 *msg_id
823 } else {
824 MsgId::new_unset()
825 };
826
827 save_locations(context, &mime_parser, chat_id, from_id, insert_msg_id).await?;
828
829 if let Some(ref sync_items) = mime_parser.sync_items {
830 if from_id == ContactId::SELF {
831 if mime_parser.was_encrypted() {
832 context
833 .execute_sync_items(sync_items, mime_parser.timestamp_sent)
834 .await;
835
836 let from_addr = &mime_parser.from.addr;
838
839 let transport_changed = context
840 .sql
841 .transaction(|transaction| {
842 let transport_exists = transaction.query_row(
843 "SELECT COUNT(*) FROM transports WHERE addr=?",
844 (from_addr,),
845 |row| {
846 let count: i64 = row.get(0)?;
847 Ok(count > 0)
848 },
849 )?;
850
851 let transport_changed = if transport_exists {
852 transaction.execute(
853 "
854UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
855 ",
856 (from_addr,),
857 )? > 0
858 } else {
859 warn!(
860 context,
861 "Received sync message from unknown address {from_addr:?}."
862 );
863 false
864 };
865 Ok(transport_changed)
866 })
867 .await?;
868 if transport_changed {
869 info!(context, "Primary transport changed to {from_addr:?}.");
870 context.sql.uncache_raw_config("configured_addr").await;
871 context.emit_event(EventType::TransportsModified);
872 }
873 } else {
874 warn!(context, "Sync items are not encrypted.");
875 }
876 } else {
877 warn!(context, "Sync items not sent by self.");
878 }
879 }
880
881 if let Some(ref status_update) = mime_parser.webxdc_status_update {
882 let can_info_msg;
883 let instance = if mime_parser
884 .parts
885 .first()
886 .filter(|part| part.typ == Viewtype::Webxdc)
887 .is_some()
888 {
889 can_info_msg = false;
890 Some(
891 Message::load_from_db(context, insert_msg_id)
892 .await
893 .context("Failed to load just created webxdc instance")?,
894 )
895 } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
896 if let Some(instance) =
897 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
898 {
899 can_info_msg = instance.download_state() == DownloadState::Done;
900 Some(instance)
901 } else {
902 can_info_msg = false;
903 None
904 }
905 } else {
906 can_info_msg = false;
907 None
908 };
909
910 if let Some(instance) = instance {
911 if let Err(err) = context
912 .receive_status_update(
913 from_id,
914 &instance,
915 received_msg.sort_timestamp,
916 can_info_msg,
917 status_update,
918 )
919 .await
920 {
921 warn!(context, "receive_imf cannot update status: {err:#}.");
922 }
923 } else {
924 warn!(
925 context,
926 "Received webxdc update, but cannot assign it to message."
927 );
928 }
929 }
930
931 if let Some(avatar_action) = &mime_parser.user_avatar
932 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
933 && context
934 .update_contacts_timestamp(from_id, Param::AvatarTimestamp, mime_parser.timestamp_sent)
935 .await?
936 && let Err(err) = contact::set_profile_image(context, from_id, avatar_action).await
937 {
938 warn!(context, "receive_imf cannot update profile image: {err:#}.");
939 };
940
941 if let Some(footer) = &mime_parser.footer
943 && !mime_parser.is_mailinglist_message()
944 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
945 && context
946 .update_contacts_timestamp(from_id, Param::StatusTimestamp, mime_parser.timestamp_sent)
947 .await?
948 && let Err(err) = contact::set_status(context, from_id, footer.to_string()).await
949 {
950 warn!(context, "Cannot update contact status: {err:#}.");
951 }
952
953 let delete_server_after = context.get_config_delete_server_after().await?;
955
956 if !received_msg.msg_ids.is_empty() {
957 let target = if received_msg.needs_delete_job || delete_server_after == Some(0) {
958 Some("".to_string())
959 } else {
960 None
961 };
962 if target.is_some() || rfc724_mid_orig != rfc724_mid {
963 let target_subst = match &target {
964 Some(_) => "target=?1,",
965 None => "",
966 };
967 context
968 .sql
969 .execute(
970 &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
971 (
972 target.as_deref().unwrap_or_default(),
973 rfc724_mid_orig,
974 rfc724_mid,
975 ),
976 )
977 .await?;
978 context.scheduler.interrupt_inbox().await;
979 }
980 if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
981 {
982 markseen_on_imap_table(context, rfc724_mid_orig).await?;
984 }
985 if !mime_parser.incoming && !context.get_config_bool(Config::TeamProfile).await? {
986 let mut updated_chats = BTreeMap::new();
987 let mut archived_chats_maybe_noticed = false;
988 for report in &mime_parser.mdn_reports {
989 for msg_rfc724_mid in report
990 .original_message_id
991 .iter()
992 .chain(&report.additional_message_ids)
993 {
994 let Some(msg_id) = rfc724_mid_exists(context, msg_rfc724_mid).await? else {
995 continue;
996 };
997 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
998 continue;
999 };
1000 if msg.state < MessageState::InFresh || msg.state >= MessageState::InSeen {
1001 continue;
1002 }
1003 if !mime_parser.was_encrypted() && msg.get_showpadlock() {
1004 warn!(context, "MDN: Not encrypted. Ignoring.");
1005 continue;
1006 }
1007 message::update_msg_state(context, msg_id, MessageState::InSeen).await?;
1008 if let Err(e) = msg_id.start_ephemeral_timer(context).await {
1009 error!(context, "start_ephemeral_timer for {msg_id}: {e:#}.");
1010 }
1011 if !mime_parser.has_chat_version() {
1012 continue;
1013 }
1014 archived_chats_maybe_noticed |= msg.state < MessageState::InNoticed
1015 && msg.chat_visibility == ChatVisibility::Archived;
1016 updated_chats
1017 .entry(msg.chat_id)
1018 .and_modify(|ts| *ts = cmp::max(*ts, msg.timestamp_sort))
1019 .or_insert(msg.timestamp_sort);
1020 }
1021 }
1022 for (chat_id, timestamp_sort) in updated_chats {
1023 context
1024 .sql
1025 .execute(
1026 "
1027UPDATE msgs SET state=? WHERE
1028 state=? AND
1029 hidden=0 AND
1030 chat_id=? AND
1031 timestamp<?",
1032 (
1033 MessageState::InNoticed,
1034 MessageState::InFresh,
1035 chat_id,
1036 timestamp_sort,
1037 ),
1038 )
1039 .await
1040 .context("UPDATE msgs.state")?;
1041 if chat_id.get_fresh_msg_cnt(context).await? == 0 {
1042 context.emit_event(EventType::MsgsNoticed(chat_id));
1044 } else {
1045 context.emit_msgs_changed_without_msg_id(chat_id);
1046 }
1047 chatlist_events::emit_chatlist_item_changed(context, chat_id);
1048 }
1049 if archived_chats_maybe_noticed {
1050 context.on_archived_chats_maybe_noticed();
1051 }
1052 }
1053 }
1054
1055 if mime_parser.is_call() {
1056 context
1057 .handle_call_msg(insert_msg_id, &mime_parser, from_id)
1058 .await?;
1059 } else if received_msg.hidden {
1060 } else if let Some(replace_chat_id) = replace_chat_id {
1062 match replace_chat_id == chat_id {
1063 false => context.emit_msgs_changed_without_msg_id(replace_chat_id),
1064 true => context.emit_msgs_changed(chat_id, replace_msg_id.unwrap_or_default()),
1065 }
1066 } else if !chat_id.is_trash() {
1067 let fresh = received_msg.state == MessageState::InFresh
1068 && mime_parser.is_system_message != SystemMessage::CallAccepted
1069 && mime_parser.is_system_message != SystemMessage::CallEnded;
1070 let important = mime_parser.incoming && fresh && !is_old_contact_request;
1071 for msg_id in &received_msg.msg_ids {
1072 chat_id.emit_msg_event(context, *msg_id, important);
1073 }
1074 }
1075 context.new_msgs_notify.notify_one();
1076
1077 mime_parser
1078 .handle_reports(context, from_id, &mime_parser.parts)
1079 .await;
1080
1081 if let Some(is_bot) = mime_parser.is_bot {
1082 if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
1085 from_id.mark_bot(context, is_bot).await?;
1086 }
1087 }
1088
1089 Ok(Some(received_msg))
1090}
1091
1092pub async fn from_field_to_contact_id(
1110 context: &Context,
1111 from: &SingleInfo,
1112 fingerprint: Option<&Fingerprint>,
1113 prevent_rename: bool,
1114 find_key_contact_by_addr: bool,
1115) -> Result<Option<(ContactId, bool, Origin)>> {
1116 let fingerprint = fingerprint.as_ref().map(|fp| fp.hex()).unwrap_or_default();
1117 let display_name = if prevent_rename {
1118 Some("")
1119 } else {
1120 from.display_name.as_deref()
1121 };
1122 let from_addr = match ContactAddress::new(&from.addr) {
1123 Ok(from_addr) => from_addr,
1124 Err(err) => {
1125 warn!(
1126 context,
1127 "Cannot create a contact for the given From field: {err:#}."
1128 );
1129 return Ok(None);
1130 }
1131 };
1132
1133 if fingerprint.is_empty() && find_key_contact_by_addr {
1134 let addr_normalized = addr_normalize(&from_addr);
1135
1136 if let Some((from_id, origin)) = context
1138 .sql
1139 .query_row_optional(
1140 "SELECT id, origin FROM contacts
1141 WHERE addr=?1 COLLATE NOCASE
1142 AND fingerprint<>'' -- Only key-contacts
1143 AND id>?2 AND origin>=?3 AND blocked=?4
1144 ORDER BY last_seen DESC
1145 LIMIT 1",
1146 (
1147 &addr_normalized,
1148 ContactId::LAST_SPECIAL,
1149 Origin::IncomingUnknownFrom,
1150 Blocked::Not,
1151 ),
1152 |row| {
1153 let id: ContactId = row.get(0)?;
1154 let origin: Origin = row.get(1)?;
1155 Ok((id, origin))
1156 },
1157 )
1158 .await?
1159 {
1160 return Ok(Some((from_id, false, origin)));
1161 }
1162 }
1163
1164 let (from_id, _) = Contact::add_or_lookup_ex(
1165 context,
1166 display_name.unwrap_or_default(),
1167 &from_addr,
1168 &fingerprint,
1169 Origin::IncomingUnknownFrom,
1170 )
1171 .await?;
1172
1173 if from_id == ContactId::SELF {
1174 Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
1175 } else {
1176 let contact = Contact::get_by_id(context, from_id).await?;
1177 let from_id_blocked = contact.blocked;
1178 let incoming_origin = contact.origin;
1179
1180 context
1181 .sql
1182 .execute(
1183 "UPDATE contacts SET addr=? WHERE id=?",
1184 (from_addr, from_id),
1185 )
1186 .await?;
1187
1188 Ok(Some((from_id, from_id_blocked, incoming_origin)))
1189 }
1190}
1191
1192#[expect(clippy::arithmetic_side_effects)]
1193async fn decide_chat_assignment(
1194 context: &Context,
1195 mime_parser: &MimeMessage,
1196 parent_message: &Option<Message>,
1197 rfc724_mid: &str,
1198 from_id: ContactId,
1199) -> Result<ChatAssignment> {
1200 let mut should_trash = if !mime_parser.mdn_reports.is_empty() {
1201 info!(context, "Message is an MDN (TRASH).");
1202 true
1203 } else if mime_parser.delivery_report.is_some() {
1204 info!(context, "Message is a DSN (TRASH).");
1205 markseen_on_imap_table(context, rfc724_mid).await.ok();
1206 true
1207 } else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
1208 || mime_parser.get_header(HeaderDef::ChatDelete).is_some()
1209 || mime_parser.get_header(HeaderDef::IrohNodeAddr).is_some()
1210 || mime_parser.sync_items.is_some()
1211 {
1212 info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
1213 true
1214 } else if mime_parser.is_system_message == SystemMessage::CallAccepted
1215 || mime_parser.is_system_message == SystemMessage::CallEnded
1216 {
1217 info!(context, "Call state changed (TRASH).");
1218 true
1219 } else if mime_parser.decrypting_failed && !mime_parser.incoming {
1220 let last_time = context
1222 .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1223 .await?;
1224 let now = tools::time();
1225 let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1226 let txt = "⚠️ It seems you are using Delta Chat on multiple devices that cannot decrypt each other's outgoing messages. To fix this, on the older device use \"Settings / Add Second Device\" and follow the instructions.";
1227 let mut msg = Message::new_text(txt.to_string());
1228 chat::add_device_msg(context, None, Some(&mut msg))
1229 .await
1230 .log_err(context)
1231 .ok();
1232 true
1233 } else {
1234 last_time > now
1235 };
1236 if update_config {
1237 context
1238 .set_config_internal(Config::LastCantDecryptOutgoingMsgs, Some(&now.to_string()))
1239 .await?;
1240 }
1241 info!(context, "Outgoing undecryptable message (TRASH).");
1242 true
1243 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
1244 && !mime_parser.has_chat_version()
1245 && parent_message
1246 .as_ref()
1247 .is_none_or(|p| p.is_dc_message == MessengerMessage::No)
1248 && !context.get_config_bool(Config::IsChatmail).await?
1249 && ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
1250 .unwrap_or_default()
1251 == ShowEmails::Off
1252 {
1253 info!(context, "Classical email not shown (TRASH).");
1254 true
1257 } else if mime_parser
1258 .get_header(HeaderDef::XMozillaDraftInfo)
1259 .is_some()
1260 {
1261 info!(context, "Email is probably just a draft (TRASH).");
1267 true
1268 } else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1269 if let Some(part) = mime_parser.parts.first() {
1270 if part.typ == Viewtype::Text && part.msg.is_empty() {
1271 info!(context, "Message is a status update only (TRASH).");
1272 markseen_on_imap_table(context, rfc724_mid).await.ok();
1273 true
1274 } else {
1275 false
1276 }
1277 } else {
1278 false
1279 }
1280 } else {
1281 false
1282 };
1283
1284 should_trash |= if mime_parser.pre_message == PreMessageMode::Post {
1285 let pre_message_exists = msg_is_downloaded_for(context, rfc724_mid).await?;
1287 info!(
1288 context,
1289 "Message {rfc724_mid} is a post-message ({}).",
1290 if pre_message_exists {
1291 "pre-message exists already, so trash after replacing attachment"
1292 } else {
1293 "no pre-message -> Keep"
1294 }
1295 );
1296 pre_message_exists
1297 } else if let PreMessageMode::Pre {
1298 post_msg_rfc724_mid,
1299 ..
1300 } = &mime_parser.pre_message
1301 {
1302 let msg_id = rfc724_mid_exists(context, post_msg_rfc724_mid).await?;
1303 if let Some(msg_id) = msg_id {
1304 context
1305 .sql
1306 .execute(
1307 "UPDATE msgs SET pre_rfc724_mid=? WHERE id=?",
1308 (rfc724_mid, msg_id),
1309 )
1310 .await?;
1311 }
1312 let post_msg_exists = msg_id.is_some();
1313 info!(
1314 context,
1315 "Message {rfc724_mid} is a pre-message for {post_msg_rfc724_mid} (post_msg_exists:{post_msg_exists})."
1316 );
1317 post_msg_exists
1318 } else {
1319 false
1320 };
1321
1322 let mut num_recipients = 0;
1327 let mut has_self_addr = false;
1328 for recipient in &mime_parser.recipients {
1329 has_self_addr |= context.is_self_addr(&recipient.addr).await?;
1330 if addr_cmp(&recipient.addr, &mime_parser.from.addr) {
1331 continue;
1332 }
1333 num_recipients += 1;
1334 }
1335 if from_id != ContactId::SELF && !has_self_addr {
1336 num_recipients += 1;
1337 }
1338 let mut can_be_11_chat_log = String::new();
1339 let mut l = |cond: bool, s: String| {
1340 can_be_11_chat_log += &s;
1341 cond
1342 };
1343 let can_be_11_chat = l(
1344 num_recipients <= 1,
1345 format!("num_recipients={num_recipients}."),
1346 ) && (l(from_id != ContactId::SELF, format!(" from_id={from_id}."))
1347 || !(l(
1348 mime_parser.recipients.is_empty(),
1349 format!(" Raw recipients len={}.", mime_parser.recipients.len()),
1350 ) || l(has_self_addr, format!(" has_self_addr={has_self_addr}.")))
1351 || l(
1352 mime_parser.was_encrypted(),
1353 format!(" was_encrypted={}.", mime_parser.was_encrypted()),
1354 ));
1355
1356 let chat_assignment_log;
1357 let chat_assignment = if should_trash {
1358 chat_assignment_log = "".to_string();
1359 ChatAssignment::Trash
1360 } else if mime_parser.get_mailinglist_header().is_some() {
1361 chat_assignment_log = "Mailing list header found.".to_string();
1362 ChatAssignment::MailingListOrBroadcast
1363 } else if let Some(grpid) = mime_parser.get_chat_group_id() {
1364 if mime_parser.was_encrypted() {
1365 chat_assignment_log = "Encrypted group message.".to_string();
1366 ChatAssignment::GroupChat {
1367 grpid: grpid.to_string(),
1368 }
1369 } else if let Some(parent) = &parent_message {
1370 if let Some((chat_id, chat_id_blocked)) =
1371 lookup_chat_by_reply(context, mime_parser, parent).await?
1372 {
1373 chat_assignment_log = "Unencrypted group reply.".to_string();
1375 ChatAssignment::ExistingChat {
1376 chat_id,
1377 chat_id_blocked,
1378 }
1379 } else {
1380 chat_assignment_log = "Unencrypted group reply.".to_string();
1381 ChatAssignment::AdHocGroup
1382 }
1383 } else {
1384 chat_assignment_log = "Unencrypted group message, no parent.".to_string();
1392 ChatAssignment::AdHocGroup
1393 }
1394 } else if let Some(parent) = &parent_message {
1395 if let Some((chat_id, chat_id_blocked)) =
1396 lookup_chat_by_reply(context, mime_parser, parent).await?
1397 {
1398 chat_assignment_log = "Reply w/o grpid.".to_string();
1400 ChatAssignment::ExistingChat {
1401 chat_id,
1402 chat_id_blocked,
1403 }
1404 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1405 chat_assignment_log = "Reply with Chat-Group-Name.".to_string();
1406 ChatAssignment::AdHocGroup
1407 } else if can_be_11_chat {
1408 chat_assignment_log = format!("Non-group reply. {can_be_11_chat_log}");
1409 ChatAssignment::OneOneChat
1410 } else {
1411 chat_assignment_log = format!("Non-group reply. {can_be_11_chat_log}");
1412 ChatAssignment::AdHocGroup
1413 }
1414 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1415 chat_assignment_log = "Message with Chat-Group-Name, no parent.".to_string();
1416 ChatAssignment::AdHocGroup
1417 } else if can_be_11_chat {
1418 chat_assignment_log = format!("Non-group message, no parent. {can_be_11_chat_log}");
1419 ChatAssignment::OneOneChat
1420 } else {
1421 chat_assignment_log = format!("Non-group message, no parent. {can_be_11_chat_log}");
1422 ChatAssignment::AdHocGroup
1423 };
1424
1425 if !chat_assignment_log.is_empty() {
1426 info!(
1427 context,
1428 "{chat_assignment_log} Chat assignment = {chat_assignment:?}."
1429 );
1430 }
1431 Ok(chat_assignment)
1432}
1433
1434#[expect(clippy::too_many_arguments)]
1443async fn do_chat_assignment(
1444 context: &Context,
1445 chat_assignment: &ChatAssignment,
1446 from_id: ContactId,
1447 to_ids: &[Option<ContactId>],
1448 past_ids: &[Option<ContactId>],
1449 to_id: ContactId,
1450 allow_creation: bool,
1451 mime_parser: &mut MimeMessage,
1452 parent_message: Option<Message>,
1453) -> Result<(ChatId, Blocked, bool)> {
1454 let is_bot = context.get_config_bool(Config::Bot).await?;
1455
1456 let mut chat_id = None;
1457 let mut chat_id_blocked = Blocked::Not;
1458 let mut chat_created = false;
1459
1460 if mime_parser.incoming {
1461 let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
1462
1463 let create_blocked_default = if is_bot {
1464 Blocked::Not
1465 } else {
1466 Blocked::Request
1467 };
1468 let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
1469 match blocked {
1470 Blocked::Request => create_blocked_default,
1471 Blocked::Not => Blocked::Not,
1472 Blocked::Yes => {
1473 if Contact::is_blocked_load(context, from_id).await? {
1474 Blocked::Yes
1477 } else {
1478 create_blocked_default
1482 }
1483 }
1484 }
1485 } else {
1486 create_blocked_default
1487 };
1488
1489 match &chat_assignment {
1490 ChatAssignment::Trash => {
1491 chat_id = Some(DC_CHAT_ID_TRASH);
1492 }
1493 ChatAssignment::GroupChat { grpid } => {
1494 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1496 chat_id = Some(id);
1497 chat_id_blocked = blocked;
1498 } else if (allow_creation || test_normal_chat.is_some())
1499 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1500 context,
1501 mime_parser,
1502 create_blocked,
1503 from_id,
1504 to_ids,
1505 past_ids,
1506 grpid,
1507 )
1508 .await?
1509 {
1510 chat_id = Some(new_chat_id);
1511 chat_id_blocked = new_chat_id_blocked;
1512 chat_created = true;
1513 }
1514 }
1515 ChatAssignment::MailingListOrBroadcast => {
1516 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header()
1517 && let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1518 create_or_lookup_mailinglist_or_broadcast(
1519 context,
1520 allow_creation,
1521 create_blocked,
1522 mailinglist_header,
1523 from_id,
1524 mime_parser,
1525 )
1526 .await?
1527 {
1528 chat_id = Some(new_chat_id);
1529 chat_id_blocked = new_chat_id_blocked;
1530 chat_created = new_chat_created;
1531
1532 apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
1533 }
1534 }
1535 ChatAssignment::ExistingChat {
1536 chat_id: new_chat_id,
1537 chat_id_blocked: new_chat_id_blocked,
1538 } => {
1539 chat_id = Some(*new_chat_id);
1540 chat_id_blocked = *new_chat_id_blocked;
1541 }
1542 ChatAssignment::AdHocGroup => {
1543 if let Some((new_chat_id, new_chat_id_blocked, new_created)) =
1544 lookup_or_create_adhoc_group(
1545 context,
1546 mime_parser,
1547 to_ids,
1548 allow_creation || test_normal_chat.is_some(),
1549 create_blocked,
1550 )
1551 .await?
1552 {
1553 chat_id = Some(new_chat_id);
1554 chat_id_blocked = new_chat_id_blocked;
1555 chat_created = new_created;
1556 }
1557 }
1558 ChatAssignment::OneOneChat => {}
1559 }
1560
1561 if chat_id_blocked != Blocked::Not
1564 && create_blocked != Blocked::Yes
1565 && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
1566 && let Some(chat_id) = chat_id
1567 {
1568 chat_id.set_blocked(context, create_blocked).await?;
1569 chat_id_blocked = create_blocked;
1570 }
1571
1572 if chat_id.is_none() {
1573 let contact = Contact::get_by_id(context, from_id).await?;
1575 let create_blocked = match contact.is_blocked() {
1576 true => Blocked::Yes,
1577 false if is_bot => Blocked::Not,
1578 false => Blocked::Request,
1579 };
1580
1581 if let Some(chat) = test_normal_chat {
1582 chat_id = Some(chat.id);
1583 chat_id_blocked = chat.blocked;
1584 } else if allow_creation {
1585 let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
1586 .await
1587 .context("Failed to get (new) chat for contact")?;
1588 chat_id = Some(chat.id);
1589 chat_id_blocked = chat.blocked;
1590 chat_created = true;
1591 }
1592
1593 if let Some(chat_id) = chat_id
1594 && chat_id_blocked != Blocked::Not
1595 {
1596 if chat_id_blocked != create_blocked {
1597 chat_id.set_blocked(context, create_blocked).await?;
1598 }
1599 if create_blocked == Blocked::Request && parent_message.is_some() {
1600 ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo).await?;
1603 info!(
1604 context,
1605 "Message is a reply to a known message, mark sender as known.",
1606 );
1607 }
1608 }
1609 }
1610 } else {
1611 let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1618
1619 match &chat_assignment {
1620 ChatAssignment::Trash => {
1621 chat_id = Some(DC_CHAT_ID_TRASH);
1622 }
1623 ChatAssignment::GroupChat { grpid } => {
1624 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1625 chat_id = Some(id);
1626 chat_id_blocked = blocked;
1627 } else if allow_creation
1628 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1629 context,
1630 mime_parser,
1631 Blocked::Not,
1632 from_id,
1633 to_ids,
1634 past_ids,
1635 grpid,
1636 )
1637 .await?
1638 {
1639 chat_id = Some(new_chat_id);
1640 chat_id_blocked = new_chat_id_blocked;
1641 chat_created = true;
1642 }
1643 }
1644 ChatAssignment::ExistingChat {
1645 chat_id: new_chat_id,
1646 chat_id_blocked: new_chat_id_blocked,
1647 } => {
1648 chat_id = Some(*new_chat_id);
1649 chat_id_blocked = *new_chat_id_blocked;
1650 }
1651 ChatAssignment::MailingListOrBroadcast => {
1652 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1655 let listid = mailinglist_header_listid(mailinglist_header)?;
1656 if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await? {
1657 chat_id = Some(id);
1658 } else {
1659 let name =
1661 compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1662 if let Some(secret) = mime_parser
1663 .get_header(HeaderDef::ChatBroadcastSecret)
1664 .filter(|s| validate_broadcast_secret(s))
1665 {
1666 chat_created = true;
1667 chat_id = Some(
1668 chat::create_out_broadcast_ex(
1669 context,
1670 Nosync,
1671 listid,
1672 name,
1673 secret.to_string(),
1674 )
1675 .await?,
1676 );
1677 } else {
1678 warn!(
1679 context,
1680 "Not creating outgoing broadcast with id {listid}, because secret is unknown"
1681 );
1682 }
1683 }
1684 }
1685 }
1686 ChatAssignment::AdHocGroup => {
1687 if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1688 lookup_or_create_adhoc_group(
1689 context,
1690 mime_parser,
1691 to_ids,
1692 allow_creation,
1693 Blocked::Not,
1694 )
1695 .await?
1696 {
1697 chat_id = Some(new_chat_id);
1698 chat_id_blocked = new_chat_id_blocked;
1699 chat_created = new_chat_created;
1700 }
1701 }
1702 ChatAssignment::OneOneChat => {}
1703 }
1704
1705 if !to_ids.is_empty() {
1706 if chat_id.is_none() && allow_creation {
1707 let to_contact = Contact::get_by_id(context, to_id).await?;
1708 if let Some(list_id) = to_contact.param.get(Param::ListId) {
1709 if let Some((id, blocked)) =
1710 chat::get_chat_id_by_grpid(context, list_id).await?
1711 {
1712 chat_id = Some(id);
1713 chat_id_blocked = blocked;
1714 }
1715 } else {
1716 let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1717 chat_id = Some(chat.id);
1718 chat_id_blocked = chat.blocked;
1719 chat_created = true;
1720 }
1721 }
1722 if chat_id.is_none()
1723 && mime_parser.has_chat_version()
1724 && let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await?
1725 {
1726 chat_id = Some(chat.id);
1727 chat_id_blocked = chat.blocked;
1728 }
1729 }
1730
1731 if chat_id.is_none() && self_sent {
1732 let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1735 .await
1736 .context("Failed to get (new) chat for contact")?;
1737
1738 chat_id = Some(chat.id);
1739 chat_id_blocked = chat.blocked;
1740
1741 if Blocked::Not != chat.blocked {
1742 chat.id.unblock_ex(context, Nosync).await?;
1743 }
1744 }
1745
1746 if chat_id_blocked != Blocked::Not
1748 && let Some(chat_id) = chat_id
1749 {
1750 chat_id.unblock_ex(context, Nosync).await?;
1751 chat_id_blocked = Blocked::Not;
1752 }
1753 }
1754 let chat_id = chat_id.unwrap_or_else(|| {
1755 info!(context, "No chat id for message (TRASH).");
1756 DC_CHAT_ID_TRASH
1757 });
1758 Ok((chat_id, chat_id_blocked, chat_created))
1759}
1760
1761#[expect(clippy::too_many_arguments)]
1765async fn add_parts(
1766 context: &Context,
1767 mime_parser: &mut MimeMessage,
1768 imf_raw: &[u8],
1769 to_ids: &[Option<ContactId>],
1770 past_ids: &[Option<ContactId>],
1771 rfc724_mid: &str,
1772 from_id: ContactId,
1773 seen: bool,
1774 mut replace_msg_id: Option<MsgId>,
1775 prevent_rename: bool,
1776 mut chat_id: ChatId,
1777 mut chat_id_blocked: Blocked,
1778 is_dc_message: MessengerMessage,
1779) -> Result<ReceivedMsg> {
1780 let to_id = if mime_parser.incoming {
1781 ContactId::SELF
1782 } else {
1783 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
1784 };
1785
1786 if prevent_rename && let Some(name) = &mime_parser.from.display_name {
1789 for part in &mut mime_parser.parts {
1790 part.param.set(Param::OverrideSenderDisplayname, name);
1791 }
1792 }
1793
1794 let mut chat = Chat::load_from_db(context, chat_id).await?;
1795
1796 if mime_parser.incoming && !chat_id.is_trash() {
1797 if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
1800 let from = &mime_parser.from;
1804 let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
1805 for part in &mut mime_parser.parts {
1806 part.param.set(Param::OverrideSenderDisplayname, name);
1807 }
1808
1809 if chat.typ == Chattype::InBroadcast {
1810 warn!(
1811 context,
1812 "Not assigning msg '{rfc724_mid}' to broadcast {chat_id}: wrong sender: {from_id}."
1813 );
1814 let direct_chat =
1815 ChatIdBlocked::get_for_contact(context, from_id, Blocked::Request).await?;
1816 chat_id = direct_chat.id;
1817 chat_id_blocked = direct_chat.blocked;
1818 chat = Chat::load_from_db(context, chat_id).await?;
1819 }
1820 }
1821 }
1822
1823 let is_location_kml = mime_parser.location_kml.is_some();
1824 let mut group_changes = match chat.typ {
1825 _ if chat.id.is_special() => GroupChangesInfo::default(),
1826 Chattype::Single => GroupChangesInfo::default(),
1827 Chattype::Mailinglist => GroupChangesInfo::default(),
1828 Chattype::OutBroadcast => {
1829 apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1830 }
1831 Chattype::Group => {
1832 apply_group_changes(context, mime_parser, &mut chat, from_id, to_ids, past_ids).await?
1833 }
1834 Chattype::InBroadcast => {
1835 apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1836 }
1837 };
1838
1839 let rfc724_mid_orig = &mime_parser
1840 .get_rfc724_mid()
1841 .unwrap_or(rfc724_mid.to_string());
1842
1843 let mut ephemeral_timer = if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer)
1845 {
1846 match value.parse::<EphemeralTimer>() {
1847 Ok(timer) => timer,
1848 Err(err) => {
1849 warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1850 EphemeralTimer::Disabled
1851 }
1852 }
1853 } else {
1854 EphemeralTimer::Disabled
1855 };
1856
1857 let state = if !mime_parser.incoming {
1858 MessageState::OutDelivered
1859 } else if seen
1860 || !mime_parser.mdn_reports.is_empty()
1861 || chat_id_blocked == Blocked::Yes
1862 || group_changes.silent
1863 {
1865 MessageState::InSeen
1866 } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL {
1867 MessageState::InNoticed
1868 } else {
1869 MessageState::InFresh
1870 };
1871 let in_fresh = state == MessageState::InFresh;
1872
1873 let sort_to_bottom = false;
1874 let received = true;
1875 let sort_timestamp = chat_id
1876 .calc_sort_timestamp(
1877 context,
1878 mime_parser.timestamp_sent,
1879 sort_to_bottom,
1880 received,
1881 mime_parser.incoming,
1882 )
1883 .await?;
1884
1885 if !chat_id.is_special()
1891 && !mime_parser.parts.is_empty()
1892 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1893 {
1894 let chat_contacts =
1895 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1896 let is_from_in_chat =
1897 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1898
1899 info!(
1900 context,
1901 "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1902 );
1903 if !is_from_in_chat {
1904 warn!(
1905 context,
1906 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1907 );
1908 } else if is_dc_message == MessengerMessage::Yes
1909 && get_previous_message(context, mime_parser)
1910 .await?
1911 .map(|p| p.ephemeral_timer)
1912 == Some(ephemeral_timer)
1913 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1914 {
1915 warn!(
1922 context,
1923 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1924 );
1925 } else if chat_id
1926 .update_timestamp(
1927 context,
1928 Param::EphemeralSettingsTimestamp,
1929 mime_parser.timestamp_sent,
1930 )
1931 .await?
1932 {
1933 if let Err(err) = chat_id
1934 .inner_set_ephemeral_timer(context, ephemeral_timer)
1935 .await
1936 {
1937 warn!(
1938 context,
1939 "Failed to modify timer for chat {chat_id}: {err:#}."
1940 );
1941 } else {
1942 info!(
1943 context,
1944 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1945 );
1946 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1947 chat::add_info_msg_with_cmd(
1948 context,
1949 chat_id,
1950 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1951 SystemMessage::Unknown,
1952 Some(sort_timestamp),
1953 mime_parser.timestamp_sent,
1954 None,
1955 None,
1956 None,
1957 )
1958 .await?;
1959 }
1960 }
1961 } else {
1962 warn!(
1963 context,
1964 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1965 );
1966 }
1967 }
1968
1969 let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1970 {
1971 Some(stock_str::msg_location_enabled_by(context, from_id).await)
1972 } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1973 let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1974
1975 ephemeral_timer = EphemeralTimer::Disabled;
1982
1983 Some(better_msg)
1984 } else {
1985 None
1986 };
1987
1988 drop(chat); let sort_timestamp = tweak_sort_timestamp(
1991 context,
1992 mime_parser,
1993 group_changes.silent,
1994 chat_id,
1995 sort_timestamp,
1996 )
1997 .await?;
1998
1999 let mime_in_reply_to = mime_parser
2000 .get_header(HeaderDef::InReplyTo)
2001 .unwrap_or_default();
2002 let mime_references = mime_parser
2003 .get_header(HeaderDef::References)
2004 .unwrap_or_default();
2005
2006 let icnt = mime_parser.parts.len();
2011
2012 let subject = mime_parser.get_subject().unwrap_or_default();
2013
2014 let is_system_message = mime_parser.is_system_message;
2015
2016 let mut save_mime_modified = false;
2023
2024 let mime_headers = if mime_parser.is_mime_modified {
2025 let headers = if !mime_parser.decoded_data.is_empty() {
2026 mime_parser.decoded_data.clone()
2027 } else {
2028 imf_raw.to_vec()
2029 };
2030 tokio::task::block_in_place(move || buf_compress(&headers))?
2031 } else {
2032 Vec::new()
2033 };
2034
2035 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
2036
2037 if let Some(m) = group_changes.better_msg {
2038 match &better_msg {
2039 None => better_msg = Some(m),
2040 Some(_) => {
2041 if !m.is_empty() {
2042 group_changes.extra_msgs.push((m, is_system_message, None))
2043 }
2044 }
2045 }
2046 }
2047
2048 let chat_id = if better_msg
2049 .as_ref()
2050 .is_some_and(|better_msg| better_msg.is_empty())
2051 {
2052 DC_CHAT_ID_TRASH
2053 } else {
2054 chat_id
2055 };
2056
2057 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
2058 chat::add_info_msg_with_cmd(
2059 context,
2060 chat_id,
2061 &group_changes_msg,
2062 cmd,
2063 Some(sort_timestamp),
2064 mime_parser.timestamp_sent,
2065 None,
2066 None,
2067 added_removed_id,
2068 )
2069 .await?;
2070 }
2071
2072 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
2073 match mime_parser.get_header(HeaderDef::InReplyTo) {
2074 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
2075 Some(instance_id) => {
2076 if let Err(err) =
2077 add_gossip_peer_from_header(context, instance_id, node_addr).await
2078 {
2079 warn!(context, "Failed to add iroh peer from header: {err:#}.");
2080 }
2081 }
2082 None => {
2083 warn!(
2084 context,
2085 "Cannot add iroh peer because WebXDC instance does not exist."
2086 );
2087 }
2088 },
2089 None => {
2090 warn!(
2091 context,
2092 "Cannot add iroh peer because the message has no In-Reply-To."
2093 );
2094 }
2095 }
2096 }
2097
2098 handle_edit_delete(context, mime_parser, from_id).await?;
2099 handle_post_message(context, mime_parser, from_id, state).await?;
2100
2101 if mime_parser.is_system_message == SystemMessage::CallAccepted
2102 || mime_parser.is_system_message == SystemMessage::CallEnded
2103 {
2104 if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
2105 if let Some(call) =
2106 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
2107 {
2108 context
2109 .handle_call_msg(call.get_id(), mime_parser, from_id)
2110 .await?;
2111 } else {
2112 warn!(context, "Call: Cannot load parent.")
2113 }
2114 } else {
2115 warn!(context, "Call: Not a reply.")
2116 }
2117 }
2118
2119 let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
2120 let mut parts = mime_parser.parts.iter().peekable();
2121 while let Some(part) = parts.next() {
2122 let hidden = part.is_reaction;
2123 if part.is_reaction {
2124 let reaction_str = simplify::remove_footers(part.msg.as_str());
2125 let is_incoming_fresh = mime_parser.incoming && !seen;
2126 set_msg_reaction(
2127 context,
2128 mime_in_reply_to,
2129 chat_id,
2130 from_id,
2131 sort_timestamp,
2132 Reaction::from(reaction_str.as_str()),
2133 is_incoming_fresh,
2134 )
2135 .await?;
2136 }
2137
2138 let mut param = part.param.clone();
2139 if is_system_message != SystemMessage::Unknown {
2140 param.set_int(Param::Cmd, is_system_message as i32);
2141 }
2142
2143 if let Some(replace_msg_id) = replace_msg_id {
2144 let placeholder = Message::load_from_db(context, replace_msg_id)
2145 .await
2146 .context("Failed to load placeholder message")?;
2147 for key in [
2148 Param::WebxdcSummary,
2149 Param::WebxdcSummaryTimestamp,
2150 Param::WebxdcDocument,
2151 Param::WebxdcDocumentTimestamp,
2152 ] {
2153 if let Some(value) = placeholder.param.get(key) {
2154 param.set(key, value);
2155 }
2156 }
2157 }
2158
2159 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
2160 (better_msg, Viewtype::Text)
2161 } else {
2162 (&part.msg, part.typ)
2163 };
2164 let part_is_empty =
2165 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
2166
2167 if let Some(contact_id) = group_changes.added_removed_id {
2168 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
2169 }
2170
2171 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
2172 let save_mime_modified = save_mime_modified && parts.peek().is_none();
2173
2174 let ephemeral_timestamp = if in_fresh {
2175 0
2176 } else {
2177 match ephemeral_timer {
2178 EphemeralTimer::Disabled => 0,
2179 EphemeralTimer::Enabled { duration } => {
2180 mime_parser.timestamp_rcvd.saturating_add(duration.into())
2181 }
2182 }
2183 };
2184
2185 if let PreMessageMode::Pre {
2186 metadata: Some(metadata),
2187 ..
2188 } = &mime_parser.pre_message
2189 {
2190 param.apply_post_msg_metadata(metadata);
2191 };
2192
2193 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2196
2197 let row_id = context
2198 .sql
2199 .call_write(|conn| {
2200 let mut stmt = conn.prepare_cached(
2201 r#"
2202INSERT INTO msgs
2203 (
2204 id,
2205 rfc724_mid, pre_rfc724_mid, chat_id,
2206 from_id, to_id, timestamp, timestamp_sent,
2207 timestamp_rcvd, type, state, msgrmsg,
2208 txt, txt_normalized, subject, param, hidden,
2209 bytes, mime_headers, mime_compressed, mime_in_reply_to,
2210 mime_references, mime_modified, error, ephemeral_timer,
2211 ephemeral_timestamp, download_state, hop_info
2212 )
2213 VALUES (
2214 ?,
2215 ?, ?, ?, ?, ?,
2216 ?, ?, ?, ?,
2217 ?, ?, ?, ?,
2218 ?, ?, ?, ?, ?, 1,
2219 ?, ?, ?, ?,
2220 ?, ?, ?, ?
2221 )
2222ON CONFLICT (id) DO UPDATE
2223SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
2224 from_id=excluded.from_id, to_id=excluded.to_id, timestamp_sent=excluded.timestamp_sent,
2225 type=excluded.type, state=max(state,excluded.state), msgrmsg=excluded.msgrmsg,
2226 txt=excluded.txt, txt_normalized=excluded.txt_normalized, subject=excluded.subject,
2227 param=excluded.param,
2228 hidden=excluded.hidden,bytes=excluded.bytes, mime_headers=excluded.mime_headers,
2229 mime_compressed=excluded.mime_compressed, mime_in_reply_to=excluded.mime_in_reply_to,
2230 mime_references=excluded.mime_references, mime_modified=excluded.mime_modified, error=excluded.error, ephemeral_timer=excluded.ephemeral_timer,
2231 ephemeral_timestamp=excluded.ephemeral_timestamp, download_state=excluded.download_state, hop_info=excluded.hop_info
2232RETURNING id
2233"#)?;
2234 let row_id: MsgId = stmt.query_row(params![
2235 replace_msg_id,
2236 if let PreMessageMode::Pre {post_msg_rfc724_mid, ..} = &mime_parser.pre_message {
2237 post_msg_rfc724_mid
2238 } else { rfc724_mid_orig },
2239 if let PreMessageMode::Pre {..} = &mime_parser.pre_message {
2240 rfc724_mid_orig
2241 } else { "" },
2242 if trash { DC_CHAT_ID_TRASH } else { chat_id },
2243 if trash { ContactId::UNDEFINED } else { from_id },
2244 if trash { ContactId::UNDEFINED } else { to_id },
2245 sort_timestamp,
2246 if trash { 0 } else { mime_parser.timestamp_sent },
2247 if trash { 0 } else { mime_parser.timestamp_rcvd },
2248 if trash {
2249 Viewtype::Unknown
2250 } else if let PreMessageMode::Pre {..} = mime_parser.pre_message {
2251 Viewtype::Text
2252 } else { typ },
2253 if trash { MessageState::Undefined } else { state },
2254 if trash { MessengerMessage::No } else { is_dc_message },
2255 if trash || hidden { "" } else { msg },
2256 if trash || hidden { None } else { normalize_text(msg) },
2257 if trash || hidden { "" } else { &subject },
2258 if trash {
2259 "".to_string()
2260 } else {
2261 param.to_string()
2262 },
2263 !trash && hidden,
2264 if trash { 0 } else { part.bytes as isize },
2265 if save_mime_modified && !(trash || hidden) {
2266 mime_headers.clone()
2267 } else {
2268 Vec::new()
2269 },
2270 if trash { "" } else { mime_in_reply_to },
2271 if trash { "" } else { mime_references },
2272 !trash && save_mime_modified,
2273 if trash { "" } else { part.error.as_deref().unwrap_or_default() },
2274 if trash { 0 } else { ephemeral_timer.to_u32() },
2275 if trash { 0 } else { ephemeral_timestamp },
2276 if trash {
2277 DownloadState::Done
2278 } else if mime_parser.decrypting_failed {
2279 DownloadState::Undecipherable
2280 } else if let PreMessageMode::Pre {..} = mime_parser.pre_message {
2281 DownloadState::Available
2282 } else {
2283 DownloadState::Done
2284 },
2285 if trash { "" } else { &mime_parser.hop_info },
2286 ],
2287 |row| {
2288 let msg_id: MsgId = row.get(0)?;
2289 Ok(msg_id)
2290 }
2291 )?;
2292 Ok(row_id)
2293 })
2294 .await?;
2295
2296 replace_msg_id = None;
2299
2300 ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2301 created_db_entries.push(row_id);
2302 }
2303
2304 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2306 if mime_parser.pre_message != PreMessageMode::Post
2307 && part.typ == Viewtype::Webxdc
2308 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2309 {
2310 let topic = iroh_topic_from_str(topic)?;
2311 insert_topic_stub(context, *msg_id, topic).await?;
2312 }
2313
2314 maybe_set_logging_xdc_inner(
2315 context,
2316 part.typ,
2317 chat_id,
2318 part.param.get(Param::Filename),
2319 *msg_id,
2320 )
2321 .await?;
2322 }
2323
2324 if let Some(replace_msg_id) = replace_msg_id {
2325 let on_server = rfc724_mid == rfc724_mid_orig;
2329 replace_msg_id.trash(context, on_server).await?;
2330 }
2331
2332 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2333 Some(addr) => context.is_self_addr(addr).await?,
2334 None => true,
2335 };
2336 if unarchive {
2337 chat_id.unarchive_if_not_muted(context, state).await?;
2338 }
2339
2340 info!(
2341 context,
2342 "Message has {icnt} parts and is assigned to chat #{chat_id}."
2343 );
2344
2345 if !chat_id.is_trash() && !hidden {
2346 let mut chat = Chat::load_from_db(context, chat_id).await?;
2347 let mut update_param = false;
2348
2349 if chat
2353 .param
2354 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2355 {
2356 let subject = mime_parser.get_subject().unwrap_or_default();
2359
2360 chat.param.set(Param::LastSubject, subject);
2361 update_param = true;
2362 }
2363
2364 if chat.is_unpromoted() {
2365 chat.param.remove(Param::Unpromoted);
2366 update_param = true;
2367 }
2368 if update_param {
2369 chat.update_param(context).await?;
2370 }
2371 }
2372
2373 Ok(ReceivedMsg {
2374 chat_id,
2375 state,
2376 hidden,
2377 sort_timestamp,
2378 msg_ids: created_db_entries,
2379 needs_delete_job: false,
2380 })
2381}
2382
2383async fn handle_edit_delete(
2388 context: &Context,
2389 mime_parser: &MimeMessage,
2390 from_id: ContactId,
2391) -> Result<()> {
2392 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2393 if let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? {
2394 if let Some(mut original_msg) =
2395 Message::load_from_db_optional(context, original_msg_id).await?
2396 {
2397 if original_msg.from_id == from_id {
2398 if let Some(part) = mime_parser.parts.first() {
2399 let edit_msg_showpadlock = part
2400 .param
2401 .get_bool(Param::GuaranteeE2ee)
2402 .unwrap_or_default();
2403 if edit_msg_showpadlock || !original_msg.get_showpadlock() {
2404 let new_text =
2405 part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2406 chat::save_text_edit_to_db(context, &mut original_msg, new_text)
2407 .await?;
2408 } else {
2409 warn!(context, "Edit message: Not encrypted.");
2410 }
2411 }
2412 } else {
2413 warn!(context, "Edit message: Bad sender.");
2414 }
2415 } else {
2416 warn!(context, "Edit message: Database entry does not exist.");
2417 }
2418 } else {
2419 warn!(
2420 context,
2421 "Edit message: rfc724_mid {rfc724_mid:?} not found."
2422 );
2423 }
2424 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
2425 && let Some(part) = mime_parser.parts.first()
2426 {
2427 if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
2430 let mut modified_chat_ids = HashSet::new();
2431 let mut msg_ids = Vec::new();
2432
2433 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2434 for rfc724_mid in rfc724_mid_vec {
2435 if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
2436 if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
2437 if msg.from_id == from_id {
2438 message::delete_msg_locally(context, &msg).await?;
2439 msg_ids.push(msg.id);
2440 modified_chat_ids.insert(msg.chat_id);
2441 } else {
2442 warn!(context, "Delete message: Bad sender.");
2443 }
2444 } else {
2445 warn!(context, "Delete message: Database entry does not exist.");
2446 }
2447 } else {
2448 warn!(context, "Delete message: {rfc724_mid:?} not found.");
2449 }
2450 }
2451 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2452 } else {
2453 warn!(context, "Delete message: Not encrypted.");
2454 }
2455 }
2456 Ok(())
2457}
2458
2459async fn handle_post_message(
2460 context: &Context,
2461 mime_parser: &MimeMessage,
2462 from_id: ContactId,
2463 state: MessageState,
2464) -> Result<()> {
2465 let PreMessageMode::Post = &mime_parser.pre_message else {
2466 return Ok(());
2467 };
2468 let rfc724_mid = mime_parser
2471 .get_rfc724_mid()
2472 .context("expected Post-Message to have a message id")?;
2473
2474 let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
2475 warn!(
2476 context,
2477 "handle_post_message: {rfc724_mid}: Database entry does not exist."
2478 );
2479 return Ok(());
2480 };
2481 let Some(original_msg) = Message::load_from_db_optional(context, msg_id).await? else {
2482 warn!(
2484 context,
2485 "handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
2486 );
2487 return Ok(());
2488 };
2489 let Some(part) = mime_parser.parts.first() else {
2490 return Ok(());
2491 };
2492
2493 if from_id != original_msg.from_id {
2496 warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
2497 return Ok(());
2498 }
2499 let post_msg_showpadlock = part
2500 .param
2501 .get_bool(Param::GuaranteeE2ee)
2502 .unwrap_or_default();
2503 if !post_msg_showpadlock && original_msg.get_showpadlock() {
2504 warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
2505 return Ok(());
2506 }
2507
2508 if !part.typ.has_file() {
2509 warn!(
2510 context,
2511 "handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
2512 );
2513 return Ok(());
2514 }
2515
2516 if part.typ == Viewtype::Webxdc
2517 && let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
2518 {
2519 let topic = iroh_topic_from_str(topic)?;
2520 insert_topic_stub(context, msg_id, topic).await?;
2521 }
2522
2523 let mut new_params = original_msg.param.clone();
2524 new_params
2525 .merge_in_params(part.param.clone())
2526 .remove(Param::PostMessageFileBytes)
2527 .remove(Param::PostMessageViewtype);
2528 context
2531 .sql
2532 .execute(
2533 "
2534UPDATE msgs SET param=?, type=?, bytes=?, error=?, state=max(state,?), download_state=?
2535WHERE id=?
2536 ",
2537 (
2538 new_params.to_string(),
2539 part.typ,
2540 part.bytes as isize,
2541 part.error.as_deref().unwrap_or_default(),
2542 state,
2543 DownloadState::Done as u32,
2544 original_msg.id,
2545 ),
2546 )
2547 .await?;
2548 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2549
2550 Ok(())
2551}
2552
2553async fn tweak_sort_timestamp(
2554 context: &Context,
2555 mime_parser: &mut MimeMessage,
2556 silent: bool,
2557 chat_id: ChatId,
2558 sort_timestamp: i64,
2559) -> Result<i64> {
2560 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2569 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2570 std::cmp::max(sort_timestamp, parent_timestamp)
2571 });
2572
2573 if silent {
2577 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2578 t
2579 } else {
2580 chat_id.created_timestamp(context).await?
2581 };
2582 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2583 }
2584 Ok(sort_timestamp)
2585}
2586
2587async fn save_locations(
2591 context: &Context,
2592 mime_parser: &MimeMessage,
2593 chat_id: ChatId,
2594 from_id: ContactId,
2595 msg_id: MsgId,
2596) -> Result<()> {
2597 if chat_id.is_special() {
2598 return Ok(());
2600 }
2601
2602 let mut send_event = false;
2603
2604 if let Some(message_kml) = &mime_parser.message_kml
2605 && let Some(newest_location_id) =
2606 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2607 {
2608 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2609 send_event = true;
2610 }
2611
2612 if let Some(location_kml) = &mime_parser.location_kml
2613 && let Some(addr) = &location_kml.addr
2614 {
2615 let contact = Contact::get_by_id(context, from_id).await?;
2616 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2617 if location::save(context, chat_id, from_id, &location_kml.locations, false)
2618 .await?
2619 .is_some()
2620 {
2621 send_event = true;
2622 }
2623 } else {
2624 warn!(
2625 context,
2626 "Address in location.kml {:?} is not the same as the sender address {:?}.",
2627 addr,
2628 contact.get_addr()
2629 );
2630 }
2631 }
2632 if send_event {
2633 context.emit_location_changed(Some(from_id)).await?;
2634 }
2635 Ok(())
2636}
2637
2638async fn lookup_chat_by_reply(
2639 context: &Context,
2640 mime_parser: &MimeMessage,
2641 parent: &Message,
2642) -> Result<Option<(ChatId, Blocked)>> {
2643 ensure_and_debug_assert!(
2648 mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2649 "Encrypted message has group ID {}",
2650 mime_parser.get_chat_group_id().unwrap_or_default(),
2651 );
2652
2653 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2655 return Ok(None);
2656 };
2657
2658 if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2661 return Ok(None);
2662 }
2663
2664 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2668 if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2669 return Ok(None);
2670 }
2671
2672 if parent_chat.is_encrypted(context).await? && !mime_parser.was_encrypted() {
2674 return Ok(None);
2675 }
2676
2677 info!(
2678 context,
2679 "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2680 );
2681 Ok(Some((parent_chat.id, parent_chat.blocked)))
2682}
2683
2684async fn lookup_or_create_adhoc_group(
2685 context: &Context,
2686 mime_parser: &MimeMessage,
2687 to_ids: &[Option<ContactId>],
2688 allow_creation: bool,
2689 create_blocked: Blocked,
2690) -> Result<Option<(ChatId, Blocked, bool)>> {
2691 if mime_parser.decrypting_failed {
2692 warn!(
2693 context,
2694 "Not creating ad-hoc group for message that cannot be decrypted."
2695 );
2696 return Ok(None);
2697 }
2698
2699 let fingerprint = None;
2701 let find_key_contact_by_addr = false;
2702 let prevent_rename = should_prevent_rename(mime_parser);
2703 let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2704 context,
2705 &mime_parser.from,
2706 fingerprint,
2707 prevent_rename,
2708 find_key_contact_by_addr,
2709 )
2710 .await?
2711 .context("Cannot lookup address-contact by the From field")?;
2712
2713 let grpname = mime_parser
2714 .get_header(HeaderDef::ChatGroupName)
2715 .map(|s| s.to_string())
2716 .unwrap_or_else(|| {
2717 mime_parser
2718 .get_subject()
2719 .map(|s| remove_subject_prefix(&s))
2720 .unwrap_or_else(|| "👥📧".to_string())
2721 });
2722 let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2723 let mut contact_ids = BTreeSet::<ContactId>::from_iter(to_ids.iter().copied());
2724 contact_ids.insert(from_id);
2725 if mime_parser.was_encrypted() {
2726 contact_ids.remove(&ContactId::SELF);
2727 }
2728 let trans_fn = |t: &mut rusqlite::Transaction| {
2729 t.pragma_update(None, "query_only", "0")?;
2730 t.execute(
2731 "CREATE TEMP TABLE temp.contacts (
2732 id INTEGER PRIMARY KEY
2733 ) STRICT",
2734 (),
2735 )
2736 .context("CREATE TEMP TABLE temp.contacts")?;
2737 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2738 for &id in &contact_ids {
2739 stmt.execute((id,)).context("INSERT INTO temp.contacts")?;
2740 }
2741 let val = t
2742 .query_row(
2743 "SELECT c.id, c.blocked
2744 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2745 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2746 AND (SELECT COUNT(*) FROM chats_contacts
2747 WHERE chat_id=c.id
2748 AND add_timestamp >= remove_timestamp)=?
2749 AND (SELECT COUNT(*) FROM chats_contacts
2750 WHERE chat_id=c.id
2751 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2752 AND add_timestamp >= remove_timestamp)=0
2753 ORDER BY m.timestamp DESC",
2754 (&grpname, contact_ids.len()),
2755 |row| {
2756 let id: ChatId = row.get(0)?;
2757 let blocked: Blocked = row.get(1)?;
2758 Ok((id, blocked))
2759 },
2760 )
2761 .optional()
2762 .context("Select chat with matching name and members")?;
2763 t.execute("DROP TABLE temp.contacts", ())
2764 .context("DROP TABLE temp.contacts")?;
2765 Ok(val)
2766 };
2767 let query_only = true;
2768 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2769 info!(
2770 context,
2771 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2772 );
2773 return Ok(Some((chat_id, blocked, false)));
2774 }
2775 if !allow_creation {
2776 return Ok(None);
2777 }
2778 Ok(create_adhoc_group(
2779 context,
2780 mime_parser,
2781 create_blocked,
2782 from_id,
2783 &to_ids,
2784 &grpname,
2785 )
2786 .await
2787 .context("Could not create ad hoc group")?
2788 .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2789}
2790
2791async fn is_probably_private_reply(
2794 context: &Context,
2795 mime_parser: &MimeMessage,
2796 parent_chat_id: ChatId,
2797) -> Result<bool> {
2798 if mime_parser.get_chat_group_id().is_some() {
2800 return Ok(false);
2801 }
2802
2803 if mime_parser.recipients.len() != 1 {
2811 return Ok(false);
2812 }
2813
2814 if !mime_parser.has_chat_version() {
2815 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2816 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2817 return Ok(false);
2818 }
2819 }
2820
2821 Ok(true)
2822}
2823
2824async fn create_group(
2830 context: &Context,
2831 mime_parser: &mut MimeMessage,
2832 create_blocked: Blocked,
2833 from_id: ContactId,
2834 to_ids: &[Option<ContactId>],
2835 past_ids: &[Option<ContactId>],
2836 grpid: &str,
2837) -> Result<Option<(ChatId, Blocked)>> {
2838 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2839 let mut chat_id = None;
2840 let mut chat_id_blocked = Default::default();
2841
2842 if chat_id.is_none()
2843 && !mime_parser.is_mailinglist_message()
2844 && !grpid.is_empty()
2845 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2846 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2848 {
2849 let grpname = mime_parser
2851 .get_header(HeaderDef::ChatGroupName)
2852 .context("Chat-Group-Name vanished")?
2853 .trim();
2857 let new_chat_id = ChatId::create_multiuser_record(
2858 context,
2859 Chattype::Group,
2860 grpid,
2861 grpname,
2862 create_blocked,
2863 None,
2864 mime_parser.timestamp_sent,
2865 )
2866 .await
2867 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2868
2869 chat_id = Some(new_chat_id);
2870 chat_id_blocked = create_blocked;
2871
2872 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2874 let mut new_to_ids = to_ids.to_vec();
2875 if !new_to_ids.contains(&Some(from_id)) {
2876 new_to_ids.insert(0, Some(from_id));
2877 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2878 }
2879
2880 update_chats_contacts_timestamps(
2881 context,
2882 new_chat_id,
2883 None,
2884 &new_to_ids,
2885 past_ids,
2886 &chat_group_member_timestamps,
2887 )
2888 .await?;
2889 } else {
2890 let mut members = vec![ContactId::SELF];
2891 if !from_id.is_special() {
2892 members.push(from_id);
2893 }
2894 members.extend(to_ids_flat);
2895
2896 let timestamp = 0;
2902
2903 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2904 }
2905
2906 context.emit_event(EventType::ChatModified(new_chat_id));
2907 chatlist_events::emit_chatlist_changed(context);
2908 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2909 }
2910
2911 if let Some(chat_id) = chat_id {
2912 Ok(Some((chat_id, chat_id_blocked)))
2913 } else if mime_parser.decrypting_failed {
2914 Ok(None)
2921 } else {
2922 info!(context, "Message belongs to unwanted group (TRASH).");
2925 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2926 }
2927}
2928
2929#[expect(clippy::arithmetic_side_effects)]
2930async fn update_chats_contacts_timestamps(
2931 context: &Context,
2932 chat_id: ChatId,
2933 ignored_id: Option<ContactId>,
2934 to_ids: &[Option<ContactId>],
2935 past_ids: &[Option<ContactId>],
2936 chat_group_member_timestamps: &[i64],
2937) -> Result<bool> {
2938 let expected_timestamps_count = to_ids.len() + past_ids.len();
2939
2940 if chat_group_member_timestamps.len() != expected_timestamps_count {
2941 warn!(
2942 context,
2943 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2944 chat_group_member_timestamps.len(),
2945 expected_timestamps_count
2946 );
2947 return Ok(false);
2948 }
2949
2950 let mut modified = false;
2951
2952 context
2953 .sql
2954 .transaction(|transaction| {
2955 let mut add_statement = transaction.prepare(
2956 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2957 VALUES (?1, ?2, ?3)
2958 ON CONFLICT (chat_id, contact_id)
2959 DO
2960 UPDATE SET add_timestamp=?3
2961 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2962 )?;
2963
2964 for (contact_id, ts) in iter::zip(
2965 to_ids.iter(),
2966 chat_group_member_timestamps.iter().take(to_ids.len()),
2967 ) {
2968 if let Some(contact_id) = contact_id
2969 && Some(*contact_id) != ignored_id
2970 {
2971 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2975 }
2976 }
2977
2978 let mut remove_statement = transaction.prepare(
2979 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2980 VALUES (?1, ?2, ?3)
2981 ON CONFLICT (chat_id, contact_id)
2982 DO
2983 UPDATE SET remove_timestamp=?3
2984 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2985 )?;
2986
2987 for (contact_id, ts) in iter::zip(
2988 past_ids.iter(),
2989 chat_group_member_timestamps.iter().skip(to_ids.len()),
2990 ) {
2991 if let Some(contact_id) = contact_id {
2992 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2996 }
2997 }
2998
2999 Ok(())
3000 })
3001 .await?;
3002
3003 Ok(modified)
3004}
3005
3006#[derive(Default)]
3010struct GroupChangesInfo {
3011 better_msg: Option<String>,
3014 added_removed_id: Option<ContactId>,
3016 silent: bool,
3018 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
3020}
3021
3022async fn apply_group_changes(
3029 context: &Context,
3030 mime_parser: &mut MimeMessage,
3031 chat: &mut Chat,
3032 from_id: ContactId,
3033 to_ids: &[Option<ContactId>],
3034 past_ids: &[Option<ContactId>],
3035) -> Result<GroupChangesInfo> {
3036 let from_is_key_contact = Contact::get_by_id(context, from_id).await?.is_key_contact();
3037 ensure!(from_is_key_contact || chat.grpid.is_empty());
3038 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
3039 ensure!(chat.typ == Chattype::Group);
3040 ensure!(!chat.id.is_special());
3041
3042 let mut send_event_chat_modified = false;
3043 let (mut removed_id, mut added_id) = (None, None);
3044 let mut better_msg = None;
3045 let mut silent = false;
3046 let chat_contacts =
3047 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
3048 let is_from_in_chat =
3049 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
3050
3051 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3052 if !is_from_in_chat {
3053 better_msg = Some(String::new());
3054 } else if let Some(removed_fpr) =
3055 mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr)
3056 {
3057 removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3058 } else {
3059 removed_id =
3061 lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?;
3062 }
3063 if let Some(id) = removed_id {
3064 better_msg = if id == from_id {
3065 silent = true;
3066 Some(stock_str::msg_group_left_local(context, from_id).await)
3067 } else {
3068 Some(stock_str::msg_del_member_local(context, id, from_id).await)
3069 };
3070 } else {
3071 warn!(context, "Removed {removed_addr:?} has no contact id.")
3072 }
3073 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3074 if !is_from_in_chat {
3075 better_msg = Some(String::new());
3076 } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
3077 if !chat_contacts.contains(&from_id) {
3078 chat::add_to_chat_contacts_table(
3079 context,
3080 mime_parser.timestamp_sent,
3081 chat.id,
3082 &[from_id],
3083 )
3084 .await?;
3085 }
3086
3087 let fingerprint = key.public_key.dc_fingerprint().hex();
3094 if let Some(contact_id) =
3095 lookup_key_contact_by_fingerprint(context, &fingerprint).await?
3096 {
3097 added_id = Some(contact_id);
3098 better_msg =
3099 Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
3100 } else {
3101 warn!(context, "Added {added_addr:?} has no contact id.");
3102 }
3103 } else {
3104 warn!(context, "Added {added_addr:?} has no gossiped key.");
3105 }
3106 }
3107
3108 if is_from_in_chat {
3109 apply_chat_name_avatar_and_description_changes(
3110 context,
3111 mime_parser,
3112 from_id,
3113 chat,
3114 &mut send_event_chat_modified,
3115 &mut better_msg,
3116 )
3117 .await?;
3118
3119 if from_is_key_contact != chat.grpid.is_empty()
3121 && chat.member_list_is_stale(context).await?
3122 {
3123 info!(context, "Member list is stale.");
3124 let mut new_members: HashSet<ContactId> =
3125 HashSet::from_iter(to_ids_flat.iter().copied());
3126 new_members.insert(ContactId::SELF);
3127 if !from_id.is_special() {
3128 new_members.insert(from_id);
3129 }
3130 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3131 new_members.remove(&ContactId::SELF);
3132 }
3133 context
3134 .sql
3135 .transaction(|transaction| {
3136 transaction.execute(
3138 "DELETE FROM chats_contacts
3139 WHERE chat_id=?",
3140 (chat.id,),
3141 )?;
3142
3143 let mut statement = transaction.prepare(
3145 "INSERT INTO chats_contacts (chat_id, contact_id)
3146 VALUES (?, ?)",
3147 )?;
3148 for contact_id in &new_members {
3149 statement.execute((chat.id, contact_id))?;
3150 }
3151
3152 Ok(())
3153 })
3154 .await?;
3155 send_event_chat_modified = true;
3156 } else if let Some(ref chat_group_member_timestamps) =
3157 mime_parser.chat_group_member_timestamps()
3158 {
3159 send_event_chat_modified |= update_chats_contacts_timestamps(
3160 context,
3161 chat.id,
3162 Some(from_id),
3163 to_ids,
3164 past_ids,
3165 chat_group_member_timestamps,
3166 )
3167 .await?;
3168 } else {
3169 let mut new_members: HashSet<ContactId>;
3170 let self_added =
3173 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3174 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
3175 && !chat_contacts.contains(&ContactId::SELF)
3176 } else {
3177 false
3178 };
3179 if self_added {
3180 new_members = HashSet::from_iter(to_ids_flat.iter().copied());
3181 new_members.insert(ContactId::SELF);
3182 if !from_id.is_special() && from_is_key_contact != chat.grpid.is_empty() {
3183 new_members.insert(from_id);
3184 }
3185 } else {
3186 new_members = chat_contacts.clone();
3187 }
3188
3189 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
3191 new_members.extend(to_ids_flat.iter());
3194 }
3195
3196 if let Some(added_id) = added_id {
3198 new_members.insert(added_id);
3199 }
3200
3201 if let Some(removed_id) = removed_id {
3203 new_members.remove(&removed_id);
3204 }
3205
3206 if mime_parser.was_encrypted() && chat.grpid.is_empty() {
3207 new_members.remove(&ContactId::SELF);
3208 }
3209
3210 if new_members != chat_contacts {
3211 chat::update_chat_contacts_table(
3212 context,
3213 mime_parser.timestamp_sent,
3214 chat.id,
3215 &new_members,
3216 )
3217 .await?;
3218 send_event_chat_modified = true;
3219 }
3220 }
3221
3222 chat.id
3223 .update_timestamp(
3224 context,
3225 Param::MemberListTimestamp,
3226 mime_parser.timestamp_sent,
3227 )
3228 .await?;
3229 }
3230
3231 let new_chat_contacts = HashSet::<ContactId>::from_iter(
3232 chat::get_chat_contacts(context, chat.id)
3233 .await?
3234 .iter()
3235 .copied(),
3236 );
3237
3238 let mut added_ids: HashSet<ContactId> = new_chat_contacts
3240 .difference(&chat_contacts)
3241 .copied()
3242 .collect();
3243 let mut removed_ids: HashSet<ContactId> = chat_contacts
3244 .difference(&new_chat_contacts)
3245 .copied()
3246 .collect();
3247
3248 if let Some(added_id) = added_id
3249 && !added_ids.remove(&added_id)
3250 && added_id != ContactId::SELF
3251 {
3252 info!(context, "No-op 'Member added' message (TRASH)");
3255 better_msg = Some(String::new());
3256 }
3257 if let Some(removed_id) = removed_id {
3258 removed_ids.remove(&removed_id);
3259 }
3260 let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
3261 && new_chat_contacts.contains(&ContactId::SELF)
3262 {
3263 Vec::new()
3264 } else {
3265 group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
3266 };
3267
3268 if send_event_chat_modified {
3269 context.emit_event(EventType::ChatModified(chat.id));
3270 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3271 }
3272 Ok(GroupChangesInfo {
3273 better_msg,
3274 added_removed_id: if added_id.is_some() {
3275 added_id
3276 } else {
3277 removed_id
3278 },
3279 silent,
3280 extra_msgs: group_changes_msgs,
3281 })
3282}
3283
3284async fn apply_chat_name_avatar_and_description_changes(
3289 context: &Context,
3290 mime_parser: &MimeMessage,
3291 from_id: ContactId,
3292 chat: &mut Chat,
3293 send_event_chat_modified: &mut bool,
3294 better_msg: &mut Option<String>,
3295) -> Result<()> {
3296 let group_name_timestamp = mime_parser
3299 .get_header(HeaderDef::ChatGroupNameTimestamp)
3300 .and_then(|s| s.parse::<i64>().ok());
3301
3302 if let Some(old_name) = mime_parser
3303 .get_header(HeaderDef::ChatGroupNameChanged)
3304 .map(|s| s.trim())
3305 .or(match group_name_timestamp {
3306 Some(0) => None,
3307 Some(_) => Some(chat.name.as_str()),
3308 None => None,
3309 })
3310 && let Some(grpname) = mime_parser
3311 .get_header(HeaderDef::ChatGroupName)
3312 .map(|grpname| grpname.trim())
3313 .filter(|grpname| grpname.len() < 200)
3314 {
3315 let grpname = &sanitize_single_line(grpname);
3316
3317 let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3318 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3319 if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3321 && chat
3322 .id
3323 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3324 .await?
3325 && grpname != &chat.name
3326 {
3327 info!(context, "Updating grpname for chat {}.", chat.id);
3328 context
3329 .sql
3330 .execute(
3331 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3332 (grpname, normalize_text(grpname), chat.id),
3333 )
3334 .await?;
3335 *send_event_chat_modified = true;
3336 }
3337 if mime_parser
3338 .get_header(HeaderDef::ChatGroupNameChanged)
3339 .is_some()
3340 {
3341 let old_name = &sanitize_single_line(old_name);
3342 better_msg
3343 .get_or_insert(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
3344 }
3345 }
3346
3347 if let Some(new_description) = mime_parser
3350 .get_header(HeaderDef::ChatGroupDescription)
3351 .map(|d| d.trim())
3352 {
3353 let new_description = sanitize_bidi_characters(new_description.trim());
3354 let old_description = chat::get_chat_description(context, chat.id).await?;
3355
3356 let old_timestamp = chat
3357 .param
3358 .get_i64(Param::GroupDescriptionTimestamp)
3359 .unwrap_or(0);
3360 let timestamp_in_header = mime_parser
3361 .get_header(HeaderDef::ChatGroupDescriptionTimestamp)
3362 .and_then(|s| s.parse::<i64>().ok());
3363
3364 let new_timestamp = timestamp_in_header.unwrap_or(mime_parser.timestamp_sent);
3365 if (old_timestamp, &old_description) < (new_timestamp, &new_description)
3367 && chat
3368 .id
3369 .update_timestamp(context, Param::GroupDescriptionTimestamp, new_timestamp)
3370 .await?
3371 && new_description != old_description
3372 {
3373 info!(context, "Updating description for chat {}.", chat.id);
3374 context
3375 .sql
3376 .execute(
3377 "INSERT OR REPLACE INTO chats_descriptions(chat_id, description) VALUES(?, ?)",
3378 (chat.id, &new_description),
3379 )
3380 .await?;
3381 *send_event_chat_modified = true;
3382 }
3383 if mime_parser
3384 .get_header(HeaderDef::ChatGroupDescriptionChanged)
3385 .is_some()
3386 {
3387 better_msg
3388 .get_or_insert(stock_str::msg_chat_description_changed(context, from_id).await);
3389 }
3390 }
3391
3392 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3395 && value == "group-avatar-changed"
3396 && let Some(avatar_action) = &mime_parser.group_avatar
3397 {
3398 better_msg.get_or_insert(match avatar_action {
3401 AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
3402 AvatarAction::Change(_) => stock_str::msg_grp_img_changed(context, from_id).await,
3403 });
3404 }
3405
3406 if let Some(avatar_action) = &mime_parser.group_avatar {
3407 info!(context, "Group-avatar change for {}.", chat.id);
3408 if chat
3409 .param
3410 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3411 {
3412 match avatar_action {
3413 AvatarAction::Change(profile_image) => {
3414 chat.param.set(Param::ProfileImage, profile_image);
3415 }
3416 AvatarAction::Delete => {
3417 chat.param.remove(Param::ProfileImage);
3418 }
3419 };
3420 chat.update_param(context).await?;
3421 *send_event_chat_modified = true;
3422 }
3423 }
3424
3425 Ok(())
3426}
3427
3428#[expect(clippy::arithmetic_side_effects)]
3430async fn group_changes_msgs(
3431 context: &Context,
3432 added_ids: &HashSet<ContactId>,
3433 removed_ids: &HashSet<ContactId>,
3434 chat_id: ChatId,
3435) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3436 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3437 if !added_ids.is_empty() {
3438 warn!(
3439 context,
3440 "Implicit addition of {added_ids:?} to chat {chat_id}."
3441 );
3442 }
3443 if !removed_ids.is_empty() {
3444 warn!(
3445 context,
3446 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3447 );
3448 }
3449 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3450 for contact_id in added_ids {
3451 group_changes_msgs.push((
3452 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3453 SystemMessage::MemberAddedToGroup,
3454 Some(*contact_id),
3455 ));
3456 }
3457 for contact_id in removed_ids {
3458 group_changes_msgs.push((
3459 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3460 SystemMessage::MemberRemovedFromGroup,
3461 Some(*contact_id),
3462 ));
3463 }
3464
3465 Ok(group_changes_msgs)
3466}
3467
3468static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3469
3470fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3471 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3472 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3473 None => list_id_header
3474 .trim()
3475 .trim_start_matches('<')
3476 .trim_end_matches('>'),
3477 }
3478 .to_string())
3479}
3480
3481async fn create_or_lookup_mailinglist_or_broadcast(
3496 context: &Context,
3497 allow_creation: bool,
3498 create_blocked: Blocked,
3499 list_id_header: &str,
3500 from_id: ContactId,
3501 mime_parser: &MimeMessage,
3502) -> Result<Option<(ChatId, Blocked, bool)>> {
3503 let listid = mailinglist_header_listid(list_id_header)?;
3504
3505 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3506 return Ok(Some((chat_id, blocked, false)));
3507 }
3508
3509 let chattype = if mime_parser.was_encrypted() {
3510 Chattype::InBroadcast
3511 } else {
3512 Chattype::Mailinglist
3513 };
3514
3515 let name = if chattype == Chattype::InBroadcast {
3516 mime_parser
3517 .get_header(HeaderDef::ChatGroupName)
3518 .unwrap_or("Broadcast Channel")
3519 } else {
3520 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3521 };
3522
3523 if allow_creation {
3524 let param = mime_parser.list_post.as_ref().map(|list_post| {
3526 let mut p = Params::new();
3527 p.set(Param::ListPost, list_post);
3528 p.to_string()
3529 });
3530
3531 let chat_id = ChatId::create_multiuser_record(
3532 context,
3533 chattype,
3534 &listid,
3535 name,
3536 create_blocked,
3537 param,
3538 mime_parser.timestamp_sent,
3539 )
3540 .await
3541 .with_context(|| {
3542 format!(
3543 "failed to create mailinglist '{}' for grpid={}",
3544 &name, &listid
3545 )
3546 })?;
3547
3548 if chattype == Chattype::InBroadcast {
3549 chat::add_to_chat_contacts_table(
3550 context,
3551 mime_parser.timestamp_sent,
3552 chat_id,
3553 &[from_id],
3554 )
3555 .await?;
3556 }
3557
3558 context.emit_event(EventType::ChatModified(chat_id));
3559 chatlist_events::emit_chatlist_changed(context);
3560 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3561
3562 Ok(Some((chat_id, create_blocked, true)))
3563 } else {
3564 info!(context, "Creating list forbidden by caller.");
3565 Ok(None)
3566 }
3567}
3568
3569fn compute_mailinglist_name(
3570 list_id_header: &str,
3571 listid: &str,
3572 mime_parser: &MimeMessage,
3573) -> String {
3574 let mut name = match LIST_ID_REGEX
3575 .captures(list_id_header)
3576 .and_then(|caps| caps.get(1))
3577 {
3578 Some(cap) => cap.as_str().trim().to_string(),
3579 None => "".to_string(),
3580 };
3581
3582 if listid.ends_with(".list-id.mcsv.net")
3586 && let Some(display_name) = &mime_parser.from.display_name
3587 {
3588 name.clone_from(display_name);
3589 }
3590
3591 let subject = mime_parser.get_subject().unwrap_or_default();
3595 static SUBJECT: LazyLock<Regex> =
3596 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3598 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3599 }
3600
3601 if name.is_empty()
3608 && (mime_parser.from.addr.contains("noreply")
3609 || mime_parser.from.addr.contains("no-reply")
3610 || mime_parser.from.addr.starts_with("notifications@")
3611 || mime_parser.from.addr.starts_with("newsletter@")
3612 || listid.ends_with(".xt.local"))
3613 && let Some(display_name) = &mime_parser.from.display_name
3614 {
3615 name.clone_from(display_name);
3616 }
3617
3618 if name.is_empty() {
3621 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3623 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3624 if let Some(cap) = PREFIX_32_CHARS_HEX
3625 .captures(listid)
3626 .and_then(|caps| caps.get(2))
3627 {
3628 name = cap.as_str().to_string();
3629 } else {
3630 name = listid.to_string();
3631 }
3632 }
3633
3634 sanitize_single_line(&name)
3635}
3636
3637async fn apply_mailinglist_changes(
3641 context: &Context,
3642 mime_parser: &MimeMessage,
3643 chat_id: ChatId,
3644) -> Result<()> {
3645 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3646 return Ok(());
3647 };
3648
3649 let mut chat = Chat::load_from_db(context, chat_id).await?;
3650 if chat.typ != Chattype::Mailinglist {
3651 return Ok(());
3652 }
3653 let listid = &chat.grpid;
3654
3655 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3656 if chat.name != new_name
3657 && chat_id
3658 .update_timestamp(
3659 context,
3660 Param::GroupNameTimestamp,
3661 mime_parser.timestamp_sent,
3662 )
3663 .await?
3664 {
3665 info!(context, "Updating listname for chat {chat_id}.");
3666 context
3667 .sql
3668 .execute(
3669 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3670 (&new_name, normalize_text(&new_name), chat_id),
3671 )
3672 .await?;
3673 context.emit_event(EventType::ChatModified(chat_id));
3674 }
3675
3676 let Some(list_post) = &mime_parser.list_post else {
3677 return Ok(());
3678 };
3679
3680 let list_post = match ContactAddress::new(list_post) {
3681 Ok(list_post) => list_post,
3682 Err(err) => {
3683 warn!(context, "Invalid List-Post: {:#}.", err);
3684 return Ok(());
3685 }
3686 };
3687 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3688 let mut contact = Contact::get_by_id(context, contact_id).await?;
3689 if contact.param.get(Param::ListId) != Some(listid) {
3690 contact.param.set(Param::ListId, listid);
3691 contact.update_param(context).await?;
3692 }
3693
3694 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3695 if list_post.as_ref() != old_list_post {
3696 chat.param.remove(Param::ListPost);
3699 chat.update_param(context).await?;
3700 }
3701 } else {
3702 chat.param.set(Param::ListPost, list_post);
3703 chat.update_param(context).await?;
3704 }
3705
3706 Ok(())
3707}
3708
3709async fn apply_out_broadcast_changes(
3710 context: &Context,
3711 mime_parser: &MimeMessage,
3712 chat: &mut Chat,
3713 from_id: ContactId,
3714) -> Result<GroupChangesInfo> {
3715 ensure!(chat.typ == Chattype::OutBroadcast);
3716
3717 let mut send_event_chat_modified = false;
3718 let mut better_msg = None;
3719 let mut added_removed_id: Option<ContactId> = None;
3720
3721 if from_id == ContactId::SELF {
3722 apply_chat_name_avatar_and_description_changes(
3723 context,
3724 mime_parser,
3725 from_id,
3726 chat,
3727 &mut send_event_chat_modified,
3728 &mut better_msg,
3729 )
3730 .await?;
3731 }
3732
3733 if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3734 if from_id == ContactId::SELF {
3735 let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3736 if let Some(added_id) = added_id {
3737 if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3738 info!(context, "No-op broadcast addition (TRASH)");
3739 better_msg.get_or_insert("".to_string());
3740 } else {
3741 chat::add_to_chat_contacts_table(
3742 context,
3743 mime_parser.timestamp_sent,
3744 chat.id,
3745 &[added_id],
3746 )
3747 .await?;
3748 let msg =
3749 stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3750 .await;
3751 better_msg.get_or_insert(msg);
3752 added_removed_id = Some(added_id);
3753 send_event_chat_modified = true;
3754 }
3755 } else {
3756 warn!(context, "Failed to find contact with fpr {added_fpr}");
3757 }
3758 }
3759 } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3760 send_event_chat_modified = true;
3761 let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3762 if removed_id == Some(from_id) {
3763 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3766 info!(context, "Broadcast leave message (TRASH)");
3767 better_msg = Some("".to_string());
3768 } else if from_id == ContactId::SELF
3769 && let Some(removed_id) = removed_id
3770 {
3771 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3772 .await?;
3773
3774 better_msg.get_or_insert(
3775 stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3776 );
3777 added_removed_id = Some(removed_id);
3778 }
3779 }
3780
3781 if send_event_chat_modified {
3782 context.emit_event(EventType::ChatModified(chat.id));
3783 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3784 }
3785 Ok(GroupChangesInfo {
3786 better_msg,
3787 added_removed_id,
3788 silent: false,
3789 extra_msgs: vec![],
3790 })
3791}
3792
3793async fn apply_in_broadcast_changes(
3794 context: &Context,
3795 mime_parser: &MimeMessage,
3796 chat: &mut Chat,
3797 from_id: ContactId,
3798) -> Result<GroupChangesInfo> {
3799 ensure!(chat.typ == Chattype::InBroadcast);
3800
3801 if let Some(part) = mime_parser.parts.first()
3802 && let Some(error) = &part.error
3803 {
3804 warn!(
3805 context,
3806 "Not applying broadcast changes from message with error: {error}"
3807 );
3808 return Ok(GroupChangesInfo::default());
3809 }
3810
3811 let mut send_event_chat_modified = false;
3812 let mut better_msg = None;
3813
3814 apply_chat_name_avatar_and_description_changes(
3815 context,
3816 mime_parser,
3817 from_id,
3818 chat,
3819 &mut send_event_chat_modified,
3820 &mut better_msg,
3821 )
3822 .await?;
3823
3824 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3825 && context.is_self_addr(added_addr).await?
3826 {
3827 let msg = if chat.is_self_in_chat(context).await? {
3828 info!(context, "No-op broadcast 'Member added' message (TRASH)");
3832 "".to_string()
3833 } else {
3834 stock_str::msg_you_joined_broadcast(context).await
3835 };
3836
3837 better_msg.get_or_insert(msg);
3838 send_event_chat_modified = true;
3839 }
3840
3841 if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3842 if removed_fpr != self_fingerprint(context).await? {
3844 logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3845 return Ok(GroupChangesInfo::default());
3846 }
3847 chat::delete_broadcast_secret(context, chat.id).await?;
3848
3849 if from_id == ContactId::SELF {
3850 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
3851 } else {
3852 better_msg.get_or_insert(
3853 stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3854 );
3855 }
3856
3857 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3858 .await?;
3859 send_event_chat_modified = true;
3860 } else if !chat.is_self_in_chat(context).await? {
3861 chat::add_to_chat_contacts_table(
3862 context,
3863 mime_parser.timestamp_sent,
3864 chat.id,
3865 &[ContactId::SELF],
3866 )
3867 .await?;
3868 send_event_chat_modified = true;
3869 }
3870
3871 if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3872 if validate_broadcast_secret(secret) {
3873 save_broadcast_secret(context, chat.id, secret).await?;
3874 } else {
3875 warn!(context, "Not saving invalid broadcast secret");
3876 }
3877 }
3878
3879 if send_event_chat_modified {
3880 context.emit_event(EventType::ChatModified(chat.id));
3881 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3882 }
3883 Ok(GroupChangesInfo {
3884 better_msg,
3885 added_removed_id: None,
3886 silent: false,
3887 extra_msgs: vec![],
3888 })
3889}
3890
3891async fn create_adhoc_group(
3893 context: &Context,
3894 mime_parser: &MimeMessage,
3895 create_blocked: Blocked,
3896 from_id: ContactId,
3897 to_ids: &[ContactId],
3898 grpname: &str,
3899) -> Result<Option<(ChatId, Blocked)>> {
3900 let mut member_ids: Vec<ContactId> = to_ids
3901 .iter()
3902 .copied()
3903 .filter(|&id| id != ContactId::SELF)
3904 .collect();
3905 if from_id != ContactId::SELF && !member_ids.contains(&from_id) {
3906 member_ids.push(from_id);
3907 }
3908 if !mime_parser.was_encrypted() {
3909 member_ids.push(ContactId::SELF);
3910 }
3911
3912 if mime_parser.is_mailinglist_message() {
3913 return Ok(None);
3914 }
3915 if mime_parser
3916 .get_header(HeaderDef::ChatGroupMemberRemoved)
3917 .is_some()
3918 {
3919 info!(
3920 context,
3921 "Message removes member from unknown ad-hoc group (TRASH)."
3922 );
3923 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3924 }
3925
3926 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3927 context,
3928 Chattype::Group,
3929 "", grpname,
3931 create_blocked,
3932 None,
3933 mime_parser.timestamp_sent,
3934 )
3935 .await?;
3936
3937 info!(
3938 context,
3939 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3940 );
3941 chat::add_to_chat_contacts_table(
3942 context,
3943 mime_parser.timestamp_sent,
3944 new_chat_id,
3945 &member_ids,
3946 )
3947 .await?;
3948
3949 context.emit_event(EventType::ChatModified(new_chat_id));
3950 chatlist_events::emit_chatlist_changed(context);
3951 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3952
3953 Ok(Some((new_chat_id, create_blocked)))
3954}
3955
3956#[derive(Debug, PartialEq, Eq)]
3957enum VerifiedEncryption {
3958 Verified,
3959 NotVerified(String), }
3961
3962async fn has_verified_encryption(
3966 context: &Context,
3967 mimeparser: &MimeMessage,
3968 from_id: ContactId,
3969) -> Result<VerifiedEncryption> {
3970 use VerifiedEncryption::*;
3971
3972 if !mimeparser.was_encrypted() {
3973 return Ok(NotVerified("This message is not encrypted".to_string()));
3974 };
3975
3976 if from_id == ContactId::SELF {
3977 return Ok(Verified);
3978 }
3979
3980 let from_contact = Contact::get_by_id(context, from_id).await?;
3981
3982 let Some(fingerprint) = from_contact.fingerprint() else {
3983 return Ok(NotVerified(
3984 "The message was sent without encryption".to_string(),
3985 ));
3986 };
3987
3988 if from_contact.get_verifier_id(context).await?.is_none() {
3989 return Ok(NotVerified(
3990 "The message was sent by non-verified contact".to_string(),
3991 ));
3992 }
3993
3994 let signed_with_verified_key = mimeparser
3995 .signature
3996 .as_ref()
3997 .is_some_and(|(signature, _)| *signature == fingerprint);
3998 if signed_with_verified_key {
3999 Ok(Verified)
4000 } else {
4001 Ok(NotVerified(
4002 "The message was sent with non-verified encryption".to_string(),
4003 ))
4004 }
4005}
4006
4007async fn mark_recipients_as_verified(
4008 context: &Context,
4009 from_id: ContactId,
4010 mimeparser: &MimeMessage,
4011) -> Result<()> {
4012 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
4013
4014 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
4018
4019 for gossiped_key in mimeparser
4020 .gossiped_keys
4021 .values()
4022 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
4023 {
4024 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
4025 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
4026 continue;
4027 };
4028
4029 if to_id == ContactId::SELF || to_id == from_id {
4030 continue;
4031 }
4032
4033 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
4034 }
4035
4036 Ok(())
4037}
4038
4039async fn get_previous_message(
4043 context: &Context,
4044 mime_parser: &MimeMessage,
4045) -> Result<Option<Message>> {
4046 if let Some(field) = mime_parser.get_header(HeaderDef::References)
4047 && let Some(rfc724mid) = parse_message_ids(field).last()
4048 && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
4049 {
4050 return Message::load_from_db_optional(context, msg_id).await;
4051 }
4052 Ok(None)
4053}
4054
4055async fn get_parent_message(
4060 context: &Context,
4061 references: Option<&str>,
4062 in_reply_to: Option<&str>,
4063) -> Result<Option<Message>> {
4064 let mut mids = Vec::new();
4065 if let Some(field) = in_reply_to {
4066 mids = parse_message_ids(field);
4067 }
4068 if let Some(field) = references {
4069 mids.append(&mut parse_message_ids(field));
4070 }
4071 message::get_by_rfc724_mids(context, &mids).await
4072}
4073
4074pub(crate) async fn get_prefetch_parent_message(
4075 context: &Context,
4076 headers: &[mailparse::MailHeader<'_>],
4077) -> Result<Option<Message>> {
4078 get_parent_message(
4079 context,
4080 headers.get_header_value(HeaderDef::References).as_deref(),
4081 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
4082 )
4083 .await
4084}
4085
4086async fn add_or_lookup_contacts_by_address_list(
4088 context: &Context,
4089 address_list: &[SingleInfo],
4090 origin: Origin,
4091) -> Result<Vec<Option<ContactId>>> {
4092 let mut contact_ids = Vec::new();
4093 for info in address_list {
4094 let addr = &info.addr;
4095 if !may_be_valid_addr(addr) {
4096 contact_ids.push(None);
4097 continue;
4098 }
4099 let display_name = info.display_name.as_deref();
4100 if let Ok(addr) = ContactAddress::new(addr) {
4101 let (contact_id, _) =
4102 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
4103 .await?;
4104 contact_ids.push(Some(contact_id));
4105 } else {
4106 warn!(context, "Contact with address {:?} cannot exist.", addr);
4107 contact_ids.push(None);
4108 }
4109 }
4110
4111 Ok(contact_ids)
4112}
4113
4114async fn add_or_lookup_key_contacts(
4116 context: &Context,
4117 address_list: &[SingleInfo],
4118 gossiped_keys: &BTreeMap<String, GossipedKey>,
4119 fingerprints: &[Fingerprint],
4120 origin: Origin,
4121) -> Result<Vec<Option<ContactId>>> {
4122 let mut contact_ids = Vec::new();
4123 let mut fingerprint_iter = fingerprints.iter();
4124 for info in address_list {
4125 let fp = fingerprint_iter.next();
4126 let addr = &info.addr;
4127 if !may_be_valid_addr(addr) {
4128 contact_ids.push(None);
4129 continue;
4130 }
4131 let fingerprint: String = if let Some(fp) = fp {
4132 fp.hex()
4134 } else if let Some(key) = gossiped_keys.get(addr) {
4135 key.public_key.dc_fingerprint().hex()
4136 } else if context.is_self_addr(addr).await? {
4137 contact_ids.push(Some(ContactId::SELF));
4138 continue;
4139 } else {
4140 contact_ids.push(None);
4141 continue;
4142 };
4143 let display_name = info.display_name.as_deref();
4144 if let Ok(addr) = ContactAddress::new(addr) {
4145 let (contact_id, _) = Contact::add_or_lookup_ex(
4146 context,
4147 display_name.unwrap_or_default(),
4148 &addr,
4149 &fingerprint,
4150 origin,
4151 )
4152 .await?;
4153 contact_ids.push(Some(contact_id));
4154 } else {
4155 warn!(context, "Contact with address {:?} cannot exist.", addr);
4156 contact_ids.push(None);
4157 }
4158 }
4159
4160 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
4161 Ok(contact_ids)
4162}
4163
4164async fn lookup_key_contact_by_address(
4169 context: &Context,
4170 addr: &str,
4171 chat_id: Option<ChatId>,
4172) -> Result<Option<ContactId>> {
4173 if context.is_self_addr(addr).await? {
4174 if chat_id.is_none() {
4175 return Ok(Some(ContactId::SELF));
4176 }
4177 let is_self_in_chat = context
4178 .sql
4179 .exists(
4180 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
4181 (chat_id,),
4182 )
4183 .await?;
4184 if is_self_in_chat {
4185 return Ok(Some(ContactId::SELF));
4186 }
4187 }
4188 let contact_id: Option<ContactId> = match chat_id {
4189 Some(chat_id) => {
4190 context
4191 .sql
4192 .query_row_optional(
4193 "SELECT id FROM contacts
4194 WHERE contacts.addr=?
4195 AND EXISTS (SELECT 1 FROM chats_contacts
4196 WHERE contact_id=contacts.id
4197 AND chat_id=?)
4198 AND fingerprint<>'' -- Should always be true
4199 ",
4200 (addr, chat_id),
4201 |row| {
4202 let contact_id: ContactId = row.get(0)?;
4203 Ok(contact_id)
4204 },
4205 )
4206 .await?
4207 }
4208 None => {
4209 context
4210 .sql
4211 .query_row_optional(
4212 "SELECT id FROM contacts
4213 WHERE addr=?
4214 AND fingerprint<>''
4215 ORDER BY
4216 (
4217 SELECT COUNT(*) FROM chats c
4218 INNER JOIN chats_contacts cc
4219 ON c.id=cc.chat_id
4220 WHERE c.type=?
4221 AND c.id>?
4222 AND c.blocked=?
4223 AND cc.contact_id=contacts.id
4224 ) DESC,
4225 last_seen DESC, id DESC
4226 ",
4227 (
4228 addr,
4229 Chattype::Single,
4230 constants::DC_CHAT_ID_LAST_SPECIAL,
4231 Blocked::Not,
4232 ),
4233 |row| {
4234 let contact_id: ContactId = row.get(0)?;
4235 Ok(contact_id)
4236 },
4237 )
4238 .await?
4239 }
4240 };
4241 Ok(contact_id)
4242}
4243
4244async fn lookup_key_contact_by_fingerprint(
4245 context: &Context,
4246 fingerprint: &str,
4247) -> Result<Option<ContactId>> {
4248 logged_debug_assert!(
4249 context,
4250 !fingerprint.is_empty(),
4251 "lookup_key_contact_by_fingerprint: fingerprint is empty."
4252 );
4253 if fingerprint.is_empty() {
4254 return Ok(None);
4256 }
4257 if let Some(contact_id) = context
4258 .sql
4259 .query_row_optional(
4260 "SELECT id FROM contacts
4261 WHERE fingerprint=? AND fingerprint!=''",
4262 (fingerprint,),
4263 |row| {
4264 let contact_id: ContactId = row.get(0)?;
4265 Ok(contact_id)
4266 },
4267 )
4268 .await?
4269 {
4270 Ok(Some(contact_id))
4271 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4272 if self_fp == fingerprint {
4273 Ok(Some(ContactId::SELF))
4274 } else {
4275 Ok(None)
4276 }
4277 } else {
4278 Ok(None)
4279 }
4280}
4281
4282async fn lookup_key_contacts_fallback_to_chat(
4298 context: &Context,
4299 address_list: &[SingleInfo],
4300 fingerprints: &[Fingerprint],
4301 chat_id: Option<ChatId>,
4302) -> Result<Vec<Option<ContactId>>> {
4303 let mut contact_ids = Vec::new();
4304 let mut fingerprint_iter = fingerprints.iter();
4305 for info in address_list {
4306 let fp = fingerprint_iter.next();
4307 let addr = &info.addr;
4308 if !may_be_valid_addr(addr) {
4309 contact_ids.push(None);
4310 continue;
4311 }
4312
4313 if let Some(fp) = fp {
4314 let display_name = info.display_name.as_deref();
4316 let fingerprint: String = fp.hex();
4317
4318 if let Ok(addr) = ContactAddress::new(addr) {
4319 let (contact_id, _) = Contact::add_or_lookup_ex(
4320 context,
4321 display_name.unwrap_or_default(),
4322 &addr,
4323 &fingerprint,
4324 Origin::Hidden,
4325 )
4326 .await?;
4327 contact_ids.push(Some(contact_id));
4328 } else {
4329 warn!(context, "Contact with address {:?} cannot exist.", addr);
4330 contact_ids.push(None);
4331 }
4332 } else {
4333 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4334 contact_ids.push(contact_id);
4335 }
4336 }
4337 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4338 Ok(contact_ids)
4339}
4340
4341fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4344 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4345 || mime_parser.get_header(HeaderDef::Sender).is_some()
4346}
4347
4348#[cfg(test)]
4349mod receive_imf_tests;