1use std::collections::{BTreeMap, BTreeSet, HashSet};
4use std::iter;
5use std::sync::LazyLock;
6
7use anyhow::{Context as _, Result, ensure};
8use data_encoding::BASE32_NOPAD;
9use deltachat_contact_tools::{
10 ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_single_line,
11};
12use iroh_gossip::proto::TopicId;
13use mailparse::SingleInfo;
14use num_traits::FromPrimitive;
15use regex::Regex;
16
17use crate::chat::{self, Chat, ChatId, ChatIdBlocked, 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::{self_fingerprint, self_fingerprint_opt};
30use crate::log::{LogExt as _, warn};
31use crate::message::{
32 self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
33};
34use crate::mimeparser::{
35 AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids,
36};
37use crate::param::{Param, Params};
38use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub};
39use crate::reaction::{Reaction, set_msg_reaction};
40use crate::rusqlite::OptionalExtension;
41use crate::securejoin::{
42 self, get_secure_join_step, handle_securejoin_handshake, observe_securejoin_on_other_device,
43};
44use crate::simplify;
45use crate::stats::STATISTICS_BOT_EMAIL;
46use crate::stock_str;
47use crate::sync::Sync::*;
48use crate::tools::{
49 self, buf_compress, normalize_text, remove_subject_prefix, validate_broadcast_secret,
50};
51use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
52use crate::{logged_debug_assert, mimeparser};
53
54#[derive(Debug)]
59pub struct ReceivedMsg {
60 pub chat_id: ChatId,
62
63 pub state: MessageState,
65
66 pub hidden: bool,
68
69 pub sort_timestamp: i64,
71
72 pub msg_ids: Vec<MsgId>,
74
75 pub needs_delete_job: bool,
77}
78
79#[derive(Debug)]
92enum ChatAssignment {
93 Trash,
95
96 GroupChat { grpid: String },
101
102 MailingListOrBroadcast,
117
118 AdHocGroup,
122
123 ExistingChat {
126 chat_id: ChatId,
129
130 chat_id_blocked: Blocked,
138 },
139
140 OneOneChat,
148}
149
150#[cfg(any(test, feature = "internals"))]
155pub async fn receive_imf(
156 context: &Context,
157 imf_raw: &[u8],
158 seen: bool,
159) -> Result<Option<ReceivedMsg>> {
160 let mail = mailparse::parse_mail(imf_raw).context("can't parse mail")?;
161 let rfc724_mid = crate::imap::prefetch_get_message_id(&mail.headers)
162 .unwrap_or_else(crate::imap::create_message_id);
163 receive_imf_from_inbox(context, &rfc724_mid, imf_raw, seen).await
164}
165
166#[cfg(any(test, feature = "internals"))]
170pub(crate) async fn receive_imf_from_inbox(
171 context: &Context,
172 rfc724_mid: &str,
173 imf_raw: &[u8],
174 seen: bool,
175) -> Result<Option<ReceivedMsg>> {
176 receive_imf_inner(context, rfc724_mid, imf_raw, seen).await
177}
178
179async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
184 let row_id = context
185 .sql
186 .insert(
187 "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
188 (rfc724_mid, DC_CHAT_ID_TRASH),
189 )
190 .await?;
191 let msg_id = MsgId::new(u32::try_from(row_id)?);
192 Ok(msg_id)
193}
194
195async fn get_to_and_past_contact_ids(
196 context: &Context,
197 mime_parser: &MimeMessage,
198 chat_assignment: &ChatAssignment,
199 parent_message: &Option<Message>,
200 incoming_origin: Origin,
201) -> Result<(Vec<Option<ContactId>>, Vec<Option<ContactId>>)> {
202 let to_ids: Vec<Option<ContactId>>;
214 let past_ids: Vec<Option<ContactId>>;
215
216 let chat_id = match chat_assignment {
223 ChatAssignment::Trash => None,
224 ChatAssignment::GroupChat { grpid } => {
225 if let Some((chat_id, _blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
226 Some(chat_id)
227 } else {
228 None
229 }
230 }
231 ChatAssignment::AdHocGroup => {
232 None
237 }
238 ChatAssignment::ExistingChat { chat_id, .. } => Some(*chat_id),
239 ChatAssignment::MailingListOrBroadcast => None,
240 ChatAssignment::OneOneChat => {
241 if !mime_parser.incoming {
242 parent_message.as_ref().map(|m| m.chat_id)
243 } else {
244 None
245 }
246 }
247 };
248
249 let member_fingerprints = mime_parser.chat_group_member_fingerprints();
250 let to_member_fingerprints;
251 let past_member_fingerprints;
252
253 if !member_fingerprints.is_empty() {
254 if member_fingerprints.len() >= mime_parser.recipients.len() {
255 (to_member_fingerprints, past_member_fingerprints) =
256 member_fingerprints.split_at(mime_parser.recipients.len());
257 } else {
258 warn!(
259 context,
260 "Unexpected length of the fingerprint header, expected at least {}, got {}.",
261 mime_parser.recipients.len(),
262 member_fingerprints.len()
263 );
264 to_member_fingerprints = &[];
265 past_member_fingerprints = &[];
266 }
267 } else {
268 to_member_fingerprints = &[];
269 past_member_fingerprints = &[];
270 }
271
272 match chat_assignment {
273 ChatAssignment::GroupChat { .. } => {
274 to_ids = add_or_lookup_key_contacts(
275 context,
276 &mime_parser.recipients,
277 &mime_parser.gossiped_keys,
278 to_member_fingerprints,
279 Origin::Hidden,
280 )
281 .await?;
282
283 if let Some(chat_id) = chat_id {
284 past_ids = lookup_key_contacts_by_address_list(
285 context,
286 &mime_parser.past_members,
287 past_member_fingerprints,
288 Some(chat_id),
289 )
290 .await?;
291 } else {
292 past_ids = add_or_lookup_key_contacts(
293 context,
294 &mime_parser.past_members,
295 &mime_parser.gossiped_keys,
296 past_member_fingerprints,
297 Origin::Hidden,
298 )
299 .await?;
300 }
301 }
302 ChatAssignment::Trash => {
303 to_ids = Vec::new();
304 past_ids = Vec::new();
305 }
306 ChatAssignment::ExistingChat { chat_id, .. } => {
307 let chat = Chat::load_from_db(context, *chat_id).await?;
308 if chat.is_encrypted(context).await? {
309 to_ids = add_or_lookup_key_contacts(
310 context,
311 &mime_parser.recipients,
312 &mime_parser.gossiped_keys,
313 to_member_fingerprints,
314 Origin::Hidden,
315 )
316 .await?;
317 past_ids = lookup_key_contacts_by_address_list(
318 context,
319 &mime_parser.past_members,
320 past_member_fingerprints,
321 Some(*chat_id),
322 )
323 .await?;
324 } else {
325 to_ids = add_or_lookup_contacts_by_address_list(
326 context,
327 &mime_parser.recipients,
328 if !mime_parser.incoming {
329 Origin::OutgoingTo
330 } else if incoming_origin.is_known() {
331 Origin::IncomingTo
332 } else {
333 Origin::IncomingUnknownTo
334 },
335 )
336 .await?;
337
338 past_ids = add_or_lookup_contacts_by_address_list(
339 context,
340 &mime_parser.past_members,
341 Origin::Hidden,
342 )
343 .await?;
344 }
345 }
346 ChatAssignment::AdHocGroup => {
347 to_ids = add_or_lookup_contacts_by_address_list(
348 context,
349 &mime_parser.recipients,
350 if !mime_parser.incoming {
351 Origin::OutgoingTo
352 } else if incoming_origin.is_known() {
353 Origin::IncomingTo
354 } else {
355 Origin::IncomingUnknownTo
356 },
357 )
358 .await?;
359
360 past_ids = add_or_lookup_contacts_by_address_list(
361 context,
362 &mime_parser.past_members,
363 Origin::Hidden,
364 )
365 .await?;
366 }
367 ChatAssignment::OneOneChat | ChatAssignment::MailingListOrBroadcast => {
371 let pgp_to_ids = add_or_lookup_key_contacts(
372 context,
373 &mime_parser.recipients,
374 &mime_parser.gossiped_keys,
375 to_member_fingerprints,
376 Origin::Hidden,
377 )
378 .await?;
379 if pgp_to_ids
380 .first()
381 .is_some_and(|contact_id| contact_id.is_some())
382 {
383 to_ids = pgp_to_ids
387 } else if let Some(chat_id) = chat_id {
388 to_ids = match mime_parser.was_encrypted() {
389 true => {
390 lookup_key_contacts_by_address_list(
391 context,
392 &mime_parser.recipients,
393 to_member_fingerprints,
394 Some(chat_id),
395 )
396 .await?
397 }
398 false => {
399 add_or_lookup_contacts_by_address_list(
400 context,
401 &mime_parser.recipients,
402 if !mime_parser.incoming {
403 Origin::OutgoingTo
404 } else if incoming_origin.is_known() {
405 Origin::IncomingTo
406 } else {
407 Origin::IncomingUnknownTo
408 },
409 )
410 .await?
411 }
412 }
413 } else {
414 let ids = match mime_parser.was_encrypted() {
415 true => {
416 lookup_key_contacts_by_address_list(
417 context,
418 &mime_parser.recipients,
419 to_member_fingerprints,
420 None,
421 )
422 .await?
423 }
424 false => vec![],
425 };
426 if mime_parser.was_encrypted() && !ids.contains(&None)
427 || ids
430 .iter()
431 .any(|&c| c.is_some() && c != Some(ContactId::SELF))
432 {
433 to_ids = ids;
434 } else {
435 to_ids = add_or_lookup_contacts_by_address_list(
436 context,
437 &mime_parser.recipients,
438 if !mime_parser.incoming {
439 Origin::OutgoingTo
440 } else if incoming_origin.is_known() {
441 Origin::IncomingTo
442 } else {
443 Origin::IncomingUnknownTo
444 },
445 )
446 .await?;
447 }
448 }
449
450 past_ids = add_or_lookup_contacts_by_address_list(
451 context,
452 &mime_parser.past_members,
453 Origin::Hidden,
454 )
455 .await?;
456 }
457 };
458
459 Ok((to_ids, past_ids))
460}
461
462pub(crate) async fn receive_imf_inner(
472 context: &Context,
473 rfc724_mid: &str,
474 imf_raw: &[u8],
475 seen: bool,
476) -> Result<Option<ReceivedMsg>> {
477 ensure!(
478 !context
479 .get_config_bool(Config::SimulateReceiveImfError)
480 .await?
481 );
482 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
483 info!(
484 context,
485 "receive_imf: incoming message mime-body:\n{}",
486 String::from_utf8_lossy(imf_raw),
487 );
488 }
489
490 let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw).await {
491 Err(err) => {
492 warn!(context, "receive_imf: can't parse MIME: {err:#}.");
493 if rfc724_mid.starts_with(GENERATED_PREFIX) {
494 return Ok(None);
496 }
497
498 let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
499
500 return Ok(Some(ReceivedMsg {
501 chat_id: DC_CHAT_ID_TRASH,
502 state: MessageState::Undefined,
503 hidden: false,
504 sort_timestamp: 0,
505 msg_ids,
506 needs_delete_job: false,
507 }));
508 }
509 Ok(mime_parser) => mime_parser,
510 };
511
512 let rfc724_mid_orig = &mime_parser
513 .get_rfc724_mid()
514 .unwrap_or(rfc724_mid.to_string());
515 info!(
516 context,
517 "Receiving message {rfc724_mid_orig:?}, seen={seen}...",
518 );
519
520 let (replace_msg_id, replace_chat_id);
523 if mime_parser.pre_message == mimeparser::PreMessageMode::Post {
524 replace_msg_id = None;
527 replace_chat_id = None;
528 } else if let Some(old_msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
529 replace_msg_id = Some(old_msg_id);
533 replace_chat_id = if let Some(msg) = Message::load_from_db_optional(context, old_msg_id)
534 .await?
535 .filter(|msg| msg.download_state() != DownloadState::Done)
536 {
537 info!(context, "Message already partly in DB, replacing.");
539 Some(msg.chat_id)
540 } else {
541 None
544 };
545 } else {
546 replace_msg_id = if rfc724_mid_orig == rfc724_mid {
547 None
548 } else {
549 message::rfc724_mid_exists(context, rfc724_mid_orig).await?
550 };
551 replace_chat_id = None;
552 }
553
554 if replace_chat_id.is_some() {
555 } else if let Some(msg_id) = replace_msg_id {
557 info!(context, "Message is already downloaded.");
558 if mime_parser.incoming {
559 return Ok(None);
560 }
561 let self_addr = context.get_primary_self_addr().await?;
564 context
565 .sql
566 .execute(
567 "DELETE FROM smtp \
568 WHERE rfc724_mid=?1 AND (recipients LIKE ?2 OR recipients LIKE ('% ' || ?2))",
569 (rfc724_mid_orig, &self_addr),
570 )
571 .await?;
572 if !context
573 .sql
574 .exists(
575 "SELECT COUNT(*) FROM smtp WHERE rfc724_mid=?",
576 (rfc724_mid_orig,),
577 )
578 .await?
579 {
580 msg_id.set_delivered(context).await?;
581 }
582 return Ok(None);
583 };
584
585 let prevent_rename = should_prevent_rename(&mime_parser);
586
587 let fingerprint = mime_parser.signature.as_ref();
599 let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
600 context,
601 &mime_parser.from,
602 fingerprint,
603 prevent_rename,
604 false,
605 )
606 .await?
607 {
608 Some(contact_id_res) => contact_id_res,
609 None => {
610 warn!(
611 context,
612 "receive_imf: From field does not contain an acceptable address."
613 );
614 return Ok(None);
615 }
616 };
617
618 let parent_message = get_parent_message(
629 context,
630 mime_parser.get_header(HeaderDef::References),
631 mime_parser.get_header(HeaderDef::InReplyTo),
632 )
633 .await?
634 .filter(|p| Some(p.id) != replace_msg_id);
635
636 let chat_assignment =
637 decide_chat_assignment(context, &mime_parser, &parent_message, rfc724_mid, from_id).await?;
638 info!(context, "Chat assignment is {chat_assignment:?}.");
639
640 let (to_ids, past_ids) = get_to_and_past_contact_ids(
641 context,
642 &mime_parser,
643 &chat_assignment,
644 &parent_message,
645 incoming_origin,
646 )
647 .await?;
648
649 let received_msg;
650 if let Some(_step) = get_secure_join_step(&mime_parser) {
651 let res = if mime_parser.incoming {
652 handle_securejoin_handshake(context, &mut mime_parser, from_id)
653 .await
654 .with_context(|| {
655 format!(
656 "Error in Secure-Join '{}' message handling",
657 mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
658 )
659 })?
660 } else if let Some(to_id) = to_ids.first().copied().flatten() {
661 observe_securejoin_on_other_device(context, &mime_parser, to_id)
663 .await
664 .with_context(|| {
665 format!(
666 "Error in Secure-Join '{}' watching",
667 mime_parser.get_header(HeaderDef::SecureJoin).unwrap_or("")
668 )
669 })?
670 } else {
671 securejoin::HandshakeMessage::Propagate
672 };
673
674 match res {
675 securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
676 let msg_id = insert_tombstone(context, rfc724_mid).await?;
677 received_msg = Some(ReceivedMsg {
678 chat_id: DC_CHAT_ID_TRASH,
679 state: MessageState::InSeen,
680 hidden: false,
681 sort_timestamp: mime_parser.timestamp_sent,
682 msg_ids: vec![msg_id],
683 needs_delete_job: res == securejoin::HandshakeMessage::Done,
684 });
685 }
686 securejoin::HandshakeMessage::Propagate => {
687 received_msg = None;
688 }
689 }
690 } else {
691 received_msg = None;
692 }
693
694 let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?;
695
696 if verified_encryption == VerifiedEncryption::Verified {
697 mark_recipients_as_verified(context, from_id, &mime_parser).await?;
698 }
699
700 let is_old_contact_request;
701 let received_msg = if let Some(received_msg) = received_msg {
702 is_old_contact_request = false;
703 received_msg
704 } else {
705 let is_dc_message = if mime_parser.has_chat_version() {
706 MessengerMessage::Yes
707 } else if let Some(parent_message) = &parent_message {
708 match parent_message.is_dc_message {
709 MessengerMessage::No => MessengerMessage::No,
710 MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply,
711 }
712 } else {
713 MessengerMessage::No
714 };
715
716 let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
717 .unwrap_or_default();
718
719 let allow_creation = if mime_parser.decrypting_failed {
720 false
721 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
722 && is_dc_message == MessengerMessage::No
723 && !context.get_config_bool(Config::IsChatmail).await?
724 {
725 match show_emails {
728 ShowEmails::Off | ShowEmails::AcceptedContacts => false,
729 ShowEmails::All => true,
730 }
731 } else {
732 !mime_parser.parts.iter().all(|part| part.is_reaction)
733 };
734
735 let to_id = if mime_parser.incoming {
736 ContactId::SELF
737 } else {
738 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
739 };
740
741 let (chat_id, chat_id_blocked, is_created) = do_chat_assignment(
742 context,
743 &chat_assignment,
744 from_id,
745 &to_ids,
746 &past_ids,
747 to_id,
748 allow_creation,
749 &mut mime_parser,
750 parent_message,
751 )
752 .await?;
753 is_old_contact_request = chat_id_blocked == Blocked::Request && !is_created;
754
755 add_parts(
757 context,
758 &mut mime_parser,
759 imf_raw,
760 &to_ids,
761 &past_ids,
762 rfc724_mid_orig,
763 from_id,
764 seen,
765 replace_msg_id,
766 prevent_rename,
767 chat_id,
768 chat_id_blocked,
769 is_dc_message,
770 )
771 .await
772 .context("add_parts error")?
773 };
774
775 if !from_id.is_special() {
776 contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
777 }
778
779 let chat_id = received_msg.chat_id;
783 if !chat_id.is_special() {
784 for gossiped_key in mime_parser.gossiped_keys.values() {
785 context
786 .sql
787 .transaction(move |transaction| {
788 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
789 transaction.execute(
790 "INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
791 VALUES (?, ?, ?)
792 ON CONFLICT (chat_id, fingerprint)
793 DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
794 (chat_id, &fingerprint, mime_parser.timestamp_sent),
795 )?;
796
797 Ok(())
798 })
799 .await?;
800 }
801 }
802
803 let insert_msg_id = if let Some(msg_id) = received_msg.msg_ids.last() {
804 *msg_id
805 } else {
806 MsgId::new_unset()
807 };
808
809 save_locations(context, &mime_parser, chat_id, from_id, insert_msg_id).await?;
810
811 if let Some(ref sync_items) = mime_parser.sync_items {
812 if from_id == ContactId::SELF {
813 if mime_parser.was_encrypted() {
814 let from_addr = &mime_parser.from.addr;
816
817 let transport_changed = context
818 .sql
819 .transaction(|transaction| {
820 let transport_exists = transaction.query_row(
821 "SELECT COUNT(*) FROM transports WHERE addr=?",
822 (from_addr,),
823 |row| {
824 let count: i64 = row.get(0)?;
825 Ok(count > 0)
826 },
827 )?;
828
829 let transport_changed = if transport_exists {
830 transaction.execute(
831 "UPDATE config SET value=? WHERE keyname='configured_addr'",
832 (from_addr,),
833 )? > 0
834 } else {
835 warn!(
836 context,
837 "Received sync message from unknown address {from_addr:?}."
838 );
839 false
840 };
841 Ok(transport_changed)
842 })
843 .await?;
844 if transport_changed {
845 info!(context, "Primary transport changed to {from_addr:?}.");
846 context.sql.uncache_raw_config("configured_addr").await;
847 }
848
849 context
850 .execute_sync_items(sync_items, mime_parser.timestamp_sent)
851 .await;
852 } else {
853 warn!(context, "Sync items are not encrypted.");
854 }
855 } else {
856 warn!(context, "Sync items not sent by self.");
857 }
858 }
859
860 if let Some(ref status_update) = mime_parser.webxdc_status_update {
861 let can_info_msg;
862 let instance = if mime_parser
863 .parts
864 .first()
865 .filter(|part| part.typ == Viewtype::Webxdc)
866 .is_some()
867 {
868 can_info_msg = false;
869 Some(Message::load_from_db(context, insert_msg_id).await?)
870 } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
871 if let Some(instance) =
872 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
873 {
874 can_info_msg = instance.download_state() == DownloadState::Done;
875 Some(instance)
876 } else {
877 can_info_msg = false;
878 None
879 }
880 } else {
881 can_info_msg = false;
882 None
883 };
884
885 if let Some(instance) = instance {
886 if let Err(err) = context
887 .receive_status_update(
888 from_id,
889 &instance,
890 received_msg.sort_timestamp,
891 can_info_msg,
892 status_update,
893 )
894 .await
895 {
896 warn!(context, "receive_imf cannot update status: {err:#}.");
897 }
898 } else {
899 warn!(
900 context,
901 "Received webxdc update, but cannot assign it to message."
902 );
903 }
904 }
905
906 if let Some(avatar_action) = &mime_parser.user_avatar
907 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
908 && context
909 .update_contacts_timestamp(from_id, Param::AvatarTimestamp, mime_parser.timestamp_sent)
910 .await?
911 && let Err(err) = contact::set_profile_image(context, from_id, avatar_action).await
912 {
913 warn!(context, "receive_imf cannot update profile image: {err:#}.");
914 };
915
916 if let Some(footer) = &mime_parser.footer
918 && !mime_parser.is_mailinglist_message()
919 && !matches!(from_id, ContactId::UNDEFINED | ContactId::SELF)
920 && context
921 .update_contacts_timestamp(from_id, Param::StatusTimestamp, mime_parser.timestamp_sent)
922 .await?
923 && let Err(err) = contact::set_status(context, from_id, footer.to_string()).await
924 {
925 warn!(context, "Cannot update contact status: {err:#}.");
926 }
927
928 let delete_server_after = context.get_config_delete_server_after().await?;
930
931 if !received_msg.msg_ids.is_empty() {
932 let target = if received_msg.needs_delete_job || delete_server_after == Some(0) {
933 Some(context.get_delete_msgs_target().await?)
934 } else {
935 None
936 };
937 if target.is_some() || rfc724_mid_orig != rfc724_mid {
938 let target_subst = match &target {
939 Some(_) => "target=?1,",
940 None => "",
941 };
942 context
943 .sql
944 .execute(
945 &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
946 (
947 target.as_deref().unwrap_or_default(),
948 rfc724_mid_orig,
949 rfc724_mid,
950 ),
951 )
952 .await?;
953 context.scheduler.interrupt_inbox().await;
954 }
955 if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
956 {
957 markseen_on_imap_table(context, rfc724_mid_orig).await?;
959 }
960 }
961
962 if mime_parser.is_call() {
963 context
964 .handle_call_msg(insert_msg_id, &mime_parser, from_id)
965 .await?;
966 } else if received_msg.hidden {
967 } else if let Some(replace_chat_id) = replace_chat_id {
969 match replace_chat_id == chat_id {
970 false => context.emit_msgs_changed_without_msg_id(replace_chat_id),
971 true => context.emit_msgs_changed(chat_id, replace_msg_id.unwrap_or_default()),
972 }
973 } else if !chat_id.is_trash() {
974 let fresh = received_msg.state == MessageState::InFresh
975 && mime_parser.is_system_message != SystemMessage::CallAccepted
976 && mime_parser.is_system_message != SystemMessage::CallEnded;
977 let important = mime_parser.incoming && fresh && !is_old_contact_request;
978 for msg_id in &received_msg.msg_ids {
979 chat_id.emit_msg_event(context, *msg_id, important);
980 }
981 }
982 context.new_msgs_notify.notify_one();
983
984 mime_parser
985 .handle_reports(context, from_id, &mime_parser.parts)
986 .await;
987
988 if let Some(is_bot) = mime_parser.is_bot {
989 if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
992 from_id.mark_bot(context, is_bot).await?;
993 }
994 }
995
996 Ok(Some(received_msg))
997}
998
999pub async fn from_field_to_contact_id(
1017 context: &Context,
1018 from: &SingleInfo,
1019 fingerprint: Option<&Fingerprint>,
1020 prevent_rename: bool,
1021 find_key_contact_by_addr: bool,
1022) -> Result<Option<(ContactId, bool, Origin)>> {
1023 let fingerprint = fingerprint.as_ref().map(|fp| fp.hex()).unwrap_or_default();
1024 let display_name = if prevent_rename {
1025 Some("")
1026 } else {
1027 from.display_name.as_deref()
1028 };
1029 let from_addr = match ContactAddress::new(&from.addr) {
1030 Ok(from_addr) => from_addr,
1031 Err(err) => {
1032 warn!(
1033 context,
1034 "Cannot create a contact for the given From field: {err:#}."
1035 );
1036 return Ok(None);
1037 }
1038 };
1039
1040 if fingerprint.is_empty() && find_key_contact_by_addr {
1041 let addr_normalized = addr_normalize(&from_addr);
1042
1043 if let Some((from_id, origin)) = context
1045 .sql
1046 .query_row_optional(
1047 "SELECT id, origin FROM contacts
1048 WHERE addr=?1 COLLATE NOCASE
1049 AND fingerprint<>'' -- Only key-contacts
1050 AND id>?2 AND origin>=?3 AND blocked=?4
1051 ORDER BY last_seen DESC
1052 LIMIT 1",
1053 (
1054 &addr_normalized,
1055 ContactId::LAST_SPECIAL,
1056 Origin::IncomingUnknownFrom,
1057 Blocked::Not,
1058 ),
1059 |row| {
1060 let id: ContactId = row.get(0)?;
1061 let origin: Origin = row.get(1)?;
1062 Ok((id, origin))
1063 },
1064 )
1065 .await?
1066 {
1067 return Ok(Some((from_id, false, origin)));
1068 }
1069 }
1070
1071 let (from_id, _) = Contact::add_or_lookup_ex(
1072 context,
1073 display_name.unwrap_or_default(),
1074 &from_addr,
1075 &fingerprint,
1076 Origin::IncomingUnknownFrom,
1077 )
1078 .await?;
1079
1080 if from_id == ContactId::SELF {
1081 Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
1082 } else {
1083 let contact = Contact::get_by_id(context, from_id).await?;
1084 let from_id_blocked = contact.blocked;
1085 let incoming_origin = contact.origin;
1086
1087 context
1088 .sql
1089 .execute(
1090 "UPDATE contacts SET addr=? WHERE id=?",
1091 (from_addr, from_id),
1092 )
1093 .await?;
1094
1095 Ok(Some((from_id, from_id_blocked, incoming_origin)))
1096 }
1097}
1098
1099async fn decide_chat_assignment(
1100 context: &Context,
1101 mime_parser: &MimeMessage,
1102 parent_message: &Option<Message>,
1103 rfc724_mid: &str,
1104 from_id: ContactId,
1105) -> Result<ChatAssignment> {
1106 let mut should_trash = if !mime_parser.mdn_reports.is_empty() {
1107 info!(context, "Message is an MDN (TRASH).");
1108 true
1109 } else if mime_parser.delivery_report.is_some() {
1110 info!(context, "Message is a DSN (TRASH).");
1111 markseen_on_imap_table(context, rfc724_mid).await.ok();
1112 true
1113 } else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
1114 || mime_parser.get_header(HeaderDef::ChatDelete).is_some()
1115 || mime_parser.get_header(HeaderDef::IrohNodeAddr).is_some()
1116 || mime_parser.sync_items.is_some()
1117 {
1118 info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
1119 true
1120 } else if mime_parser.is_system_message == SystemMessage::CallAccepted
1121 || mime_parser.is_system_message == SystemMessage::CallEnded
1122 {
1123 info!(context, "Call state changed (TRASH).");
1124 true
1125 } else if mime_parser.decrypting_failed && !mime_parser.incoming {
1126 let last_time = context
1128 .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1129 .await?;
1130 let now = tools::time();
1131 let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1132 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.";
1133 let mut msg = Message::new_text(txt.to_string());
1134 chat::add_device_msg(context, None, Some(&mut msg))
1135 .await
1136 .log_err(context)
1137 .ok();
1138 true
1139 } else {
1140 last_time > now
1141 };
1142 if update_config {
1143 context
1144 .set_config_internal(Config::LastCantDecryptOutgoingMsgs, Some(&now.to_string()))
1145 .await?;
1146 }
1147 info!(context, "Outgoing undecryptable message (TRASH).");
1148 true
1149 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
1150 && !mime_parser.has_chat_version()
1151 && parent_message
1152 .as_ref()
1153 .is_none_or(|p| p.is_dc_message == MessengerMessage::No)
1154 && !context.get_config_bool(Config::IsChatmail).await?
1155 && ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
1156 .unwrap_or_default()
1157 == ShowEmails::Off
1158 {
1159 info!(context, "Classical email not shown (TRASH).");
1160 true
1163 } else if mime_parser
1164 .get_header(HeaderDef::XMozillaDraftInfo)
1165 .is_some()
1166 {
1167 info!(context, "Email is probably just a draft (TRASH).");
1173 true
1174 } else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1175 if let Some(part) = mime_parser.parts.first() {
1176 if part.typ == Viewtype::Text && part.msg.is_empty() {
1177 info!(context, "Message is a status update only (TRASH).");
1178 markseen_on_imap_table(context, rfc724_mid).await.ok();
1179 true
1180 } else {
1181 false
1182 }
1183 } else {
1184 false
1185 }
1186 } else {
1187 false
1188 };
1189
1190 should_trash |= if mime_parser.pre_message == PreMessageMode::Post {
1191 let pre_message_exists = msg_is_downloaded_for(context, rfc724_mid).await?;
1193 info!(
1194 context,
1195 "Message {rfc724_mid} is a post-message ({}).",
1196 if pre_message_exists {
1197 "pre-message exists already, so trash after replacing attachment"
1198 } else {
1199 "no pre-message -> Keep"
1200 }
1201 );
1202 pre_message_exists
1203 } else if let PreMessageMode::Pre {
1204 post_msg_rfc724_mid,
1205 ..
1206 } = &mime_parser.pre_message
1207 {
1208 let msg_id = rfc724_mid_exists(context, post_msg_rfc724_mid).await?;
1209 if let Some(msg_id) = msg_id {
1210 context
1211 .sql
1212 .execute(
1213 "UPDATE msgs SET pre_rfc724_mid=? WHERE id=?",
1214 (rfc724_mid, msg_id),
1215 )
1216 .await?;
1217 }
1218 let post_msg_exists = msg_id.is_some();
1219 info!(
1220 context,
1221 "Message {rfc724_mid} is a pre-message for {post_msg_rfc724_mid} (post_msg_exists:{post_msg_exists})."
1222 );
1223 post_msg_exists
1224 } else {
1225 false
1226 };
1227
1228 let mut num_recipients = 0;
1233 let mut has_self_addr = false;
1234 for recipient in &mime_parser.recipients {
1235 has_self_addr |= context.is_self_addr(&recipient.addr).await?;
1236 if addr_cmp(&recipient.addr, &mime_parser.from.addr) {
1237 continue;
1238 }
1239 num_recipients += 1;
1240 }
1241 if from_id != ContactId::SELF && !has_self_addr {
1242 num_recipients += 1;
1243 }
1244 let can_be_11_chat = num_recipients <= 1
1245 && (from_id != ContactId::SELF
1246 || !(mime_parser.recipients.is_empty() || has_self_addr)
1247 || mime_parser.was_encrypted());
1248
1249 let chat_assignment = if should_trash {
1250 ChatAssignment::Trash
1251 } else if mime_parser.get_mailinglist_header().is_some() {
1252 ChatAssignment::MailingListOrBroadcast
1253 } else if let Some(grpid) = mime_parser.get_chat_group_id() {
1254 if mime_parser.was_encrypted() {
1255 ChatAssignment::GroupChat {
1256 grpid: grpid.to_string(),
1257 }
1258 } else if let Some(parent) = &parent_message {
1259 if let Some((chat_id, chat_id_blocked)) =
1260 lookup_chat_by_reply(context, mime_parser, parent).await?
1261 {
1262 ChatAssignment::ExistingChat {
1264 chat_id,
1265 chat_id_blocked,
1266 }
1267 } else {
1268 ChatAssignment::AdHocGroup
1269 }
1270 } else {
1271 ChatAssignment::AdHocGroup
1279 }
1280 } else if let Some(parent) = &parent_message {
1281 if let Some((chat_id, chat_id_blocked)) =
1282 lookup_chat_by_reply(context, mime_parser, parent).await?
1283 {
1284 ChatAssignment::ExistingChat {
1286 chat_id,
1287 chat_id_blocked,
1288 }
1289 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1290 ChatAssignment::AdHocGroup
1291 } else if can_be_11_chat {
1292 ChatAssignment::OneOneChat
1293 } else {
1294 ChatAssignment::AdHocGroup
1295 }
1296 } else if mime_parser.get_header(HeaderDef::ChatGroupName).is_some() {
1297 ChatAssignment::AdHocGroup
1298 } else if can_be_11_chat {
1299 ChatAssignment::OneOneChat
1300 } else {
1301 ChatAssignment::AdHocGroup
1302 };
1303 Ok(chat_assignment)
1304}
1305
1306#[expect(clippy::too_many_arguments)]
1315async fn do_chat_assignment(
1316 context: &Context,
1317 chat_assignment: &ChatAssignment,
1318 from_id: ContactId,
1319 to_ids: &[Option<ContactId>],
1320 past_ids: &[Option<ContactId>],
1321 to_id: ContactId,
1322 allow_creation: bool,
1323 mime_parser: &mut MimeMessage,
1324 parent_message: Option<Message>,
1325) -> Result<(ChatId, Blocked, bool)> {
1326 let is_bot = context.get_config_bool(Config::Bot).await?;
1327
1328 let mut chat_id = None;
1329 let mut chat_id_blocked = Blocked::Not;
1330 let mut chat_created = false;
1331
1332 if mime_parser.incoming {
1333 let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
1334
1335 let create_blocked_default = if is_bot {
1336 Blocked::Not
1337 } else {
1338 Blocked::Request
1339 };
1340 let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
1341 match blocked {
1342 Blocked::Request => create_blocked_default,
1343 Blocked::Not => Blocked::Not,
1344 Blocked::Yes => {
1345 if Contact::is_blocked_load(context, from_id).await? {
1346 Blocked::Yes
1349 } else {
1350 create_blocked_default
1354 }
1355 }
1356 }
1357 } else {
1358 create_blocked_default
1359 };
1360
1361 match &chat_assignment {
1362 ChatAssignment::Trash => {
1363 chat_id = Some(DC_CHAT_ID_TRASH);
1364 }
1365 ChatAssignment::GroupChat { grpid } => {
1366 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1368 chat_id = Some(id);
1369 chat_id_blocked = blocked;
1370 } else if (allow_creation || test_normal_chat.is_some())
1371 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1372 context,
1373 mime_parser,
1374 create_blocked,
1375 from_id,
1376 to_ids,
1377 past_ids,
1378 grpid,
1379 )
1380 .await?
1381 {
1382 chat_id = Some(new_chat_id);
1383 chat_id_blocked = new_chat_id_blocked;
1384 chat_created = true;
1385 }
1386 }
1387 ChatAssignment::MailingListOrBroadcast => {
1388 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header()
1389 && let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1390 create_or_lookup_mailinglist_or_broadcast(
1391 context,
1392 allow_creation,
1393 create_blocked,
1394 mailinglist_header,
1395 from_id,
1396 mime_parser,
1397 )
1398 .await?
1399 {
1400 chat_id = Some(new_chat_id);
1401 chat_id_blocked = new_chat_id_blocked;
1402 chat_created = new_chat_created;
1403
1404 apply_mailinglist_changes(context, mime_parser, new_chat_id).await?;
1405 }
1406 }
1407 ChatAssignment::ExistingChat {
1408 chat_id: new_chat_id,
1409 chat_id_blocked: new_chat_id_blocked,
1410 } => {
1411 chat_id = Some(*new_chat_id);
1412 chat_id_blocked = *new_chat_id_blocked;
1413 }
1414 ChatAssignment::AdHocGroup => {
1415 if let Some((new_chat_id, new_chat_id_blocked, new_created)) =
1416 lookup_or_create_adhoc_group(
1417 context,
1418 mime_parser,
1419 to_ids,
1420 allow_creation || test_normal_chat.is_some(),
1421 create_blocked,
1422 )
1423 .await?
1424 {
1425 chat_id = Some(new_chat_id);
1426 chat_id_blocked = new_chat_id_blocked;
1427 chat_created = new_created;
1428 }
1429 }
1430 ChatAssignment::OneOneChat => {}
1431 }
1432
1433 if chat_id_blocked != Blocked::Not
1436 && create_blocked != Blocked::Yes
1437 && !matches!(chat_assignment, ChatAssignment::MailingListOrBroadcast)
1438 && let Some(chat_id) = chat_id
1439 {
1440 chat_id.set_blocked(context, create_blocked).await?;
1441 chat_id_blocked = create_blocked;
1442 }
1443
1444 if chat_id.is_none() {
1445 let contact = Contact::get_by_id(context, from_id).await?;
1447 let create_blocked = match contact.is_blocked() {
1448 true => Blocked::Yes,
1449 false if is_bot => Blocked::Not,
1450 false => Blocked::Request,
1451 };
1452
1453 if let Some(chat) = test_normal_chat {
1454 chat_id = Some(chat.id);
1455 chat_id_blocked = chat.blocked;
1456 } else if allow_creation {
1457 let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
1458 .await
1459 .context("Failed to get (new) chat for contact")?;
1460 chat_id = Some(chat.id);
1461 chat_id_blocked = chat.blocked;
1462 chat_created = true;
1463 }
1464
1465 if let Some(chat_id) = chat_id
1466 && chat_id_blocked != Blocked::Not
1467 {
1468 if chat_id_blocked != create_blocked {
1469 chat_id.set_blocked(context, create_blocked).await?;
1470 }
1471 if create_blocked == Blocked::Request && parent_message.is_some() {
1472 ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo).await?;
1475 info!(
1476 context,
1477 "Message is a reply to a known message, mark sender as known.",
1478 );
1479 }
1480 }
1481 }
1482 } else {
1483 let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1490
1491 match &chat_assignment {
1492 ChatAssignment::Trash => {
1493 chat_id = Some(DC_CHAT_ID_TRASH);
1494 }
1495 ChatAssignment::GroupChat { grpid } => {
1496 if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
1497 chat_id = Some(id);
1498 chat_id_blocked = blocked;
1499 } else if allow_creation
1500 && let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1501 context,
1502 mime_parser,
1503 Blocked::Not,
1504 from_id,
1505 to_ids,
1506 past_ids,
1507 grpid,
1508 )
1509 .await?
1510 {
1511 chat_id = Some(new_chat_id);
1512 chat_id_blocked = new_chat_id_blocked;
1513 chat_created = true;
1514 }
1515 }
1516 ChatAssignment::ExistingChat {
1517 chat_id: new_chat_id,
1518 chat_id_blocked: new_chat_id_blocked,
1519 } => {
1520 chat_id = Some(*new_chat_id);
1521 chat_id_blocked = *new_chat_id_blocked;
1522 }
1523 ChatAssignment::MailingListOrBroadcast => {
1524 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1527 let listid = mailinglist_header_listid(mailinglist_header)?;
1528 if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await? {
1529 chat_id = Some(id);
1530 } else {
1531 let name =
1533 compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1534 if let Some(secret) = mime_parser
1535 .get_header(HeaderDef::ChatBroadcastSecret)
1536 .filter(|s| validate_broadcast_secret(s))
1537 {
1538 chat_created = true;
1539 chat_id = Some(
1540 chat::create_out_broadcast_ex(
1541 context,
1542 Nosync,
1543 listid,
1544 name,
1545 secret.to_string(),
1546 )
1547 .await?,
1548 );
1549 } else {
1550 warn!(
1551 context,
1552 "Not creating outgoing broadcast with id {listid}, because secret is unknown"
1553 );
1554 }
1555 }
1556 }
1557 }
1558 ChatAssignment::AdHocGroup => {
1559 if let Some((new_chat_id, new_chat_id_blocked, new_chat_created)) =
1560 lookup_or_create_adhoc_group(
1561 context,
1562 mime_parser,
1563 to_ids,
1564 allow_creation,
1565 Blocked::Not,
1566 )
1567 .await?
1568 {
1569 chat_id = Some(new_chat_id);
1570 chat_id_blocked = new_chat_id_blocked;
1571 chat_created = new_chat_created;
1572 }
1573 }
1574 ChatAssignment::OneOneChat => {}
1575 }
1576
1577 if !to_ids.is_empty() {
1578 if chat_id.is_none() && allow_creation {
1579 let to_contact = Contact::get_by_id(context, to_id).await?;
1580 if let Some(list_id) = to_contact.param.get(Param::ListId) {
1581 if let Some((id, blocked)) =
1582 chat::get_chat_id_by_grpid(context, list_id).await?
1583 {
1584 chat_id = Some(id);
1585 chat_id_blocked = blocked;
1586 }
1587 } else {
1588 let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1589 chat_id = Some(chat.id);
1590 chat_id_blocked = chat.blocked;
1591 chat_created = true;
1592 }
1593 }
1594 if chat_id.is_none()
1595 && mime_parser.has_chat_version()
1596 && let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await?
1597 {
1598 chat_id = Some(chat.id);
1599 chat_id_blocked = chat.blocked;
1600 }
1601 }
1602
1603 if chat_id.is_none() && self_sent {
1604 let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1607 .await
1608 .context("Failed to get (new) chat for contact")?;
1609
1610 chat_id = Some(chat.id);
1611 chat_id_blocked = chat.blocked;
1612
1613 if Blocked::Not != chat.blocked {
1614 chat.id.unblock_ex(context, Nosync).await?;
1615 }
1616 }
1617
1618 if chat_id_blocked != Blocked::Not
1620 && let Some(chat_id) = chat_id
1621 {
1622 chat_id.unblock_ex(context, Nosync).await?;
1623 chat_id_blocked = Blocked::Not;
1624 }
1625 }
1626 let chat_id = chat_id.unwrap_or_else(|| {
1627 info!(context, "No chat id for message (TRASH).");
1628 DC_CHAT_ID_TRASH
1629 });
1630 Ok((chat_id, chat_id_blocked, chat_created))
1631}
1632
1633#[expect(clippy::too_many_arguments)]
1637async fn add_parts(
1638 context: &Context,
1639 mime_parser: &mut MimeMessage,
1640 imf_raw: &[u8],
1641 to_ids: &[Option<ContactId>],
1642 past_ids: &[Option<ContactId>],
1643 rfc724_mid: &str,
1644 from_id: ContactId,
1645 seen: bool,
1646 mut replace_msg_id: Option<MsgId>,
1647 prevent_rename: bool,
1648 mut chat_id: ChatId,
1649 mut chat_id_blocked: Blocked,
1650 is_dc_message: MessengerMessage,
1651) -> Result<ReceivedMsg> {
1652 let to_id = if mime_parser.incoming {
1653 ContactId::SELF
1654 } else {
1655 to_ids.first().copied().flatten().unwrap_or(ContactId::SELF)
1656 };
1657
1658 if prevent_rename && let Some(name) = &mime_parser.from.display_name {
1661 for part in &mut mime_parser.parts {
1662 part.param.set(Param::OverrideSenderDisplayname, name);
1663 }
1664 }
1665
1666 let mut chat = Chat::load_from_db(context, chat_id).await?;
1667
1668 if mime_parser.incoming && !chat_id.is_trash() {
1669 if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
1672 let from = &mime_parser.from;
1676 let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
1677 for part in &mut mime_parser.parts {
1678 part.param.set(Param::OverrideSenderDisplayname, name);
1679 }
1680
1681 if chat.typ == Chattype::InBroadcast {
1682 warn!(
1683 context,
1684 "Not assigning msg '{rfc724_mid}' to broadcast {chat_id}: wrong sender: {from_id}."
1685 );
1686 let direct_chat =
1687 ChatIdBlocked::get_for_contact(context, from_id, Blocked::Request).await?;
1688 chat_id = direct_chat.id;
1689 chat_id_blocked = direct_chat.blocked;
1690 chat = Chat::load_from_db(context, chat_id).await?;
1691 }
1692 }
1693 }
1694
1695 let is_location_kml = mime_parser.location_kml.is_some();
1696 let is_mdn = !mime_parser.mdn_reports.is_empty();
1697
1698 let mut group_changes = match chat.typ {
1699 _ if chat.id.is_special() => GroupChangesInfo::default(),
1700 Chattype::Single => GroupChangesInfo::default(),
1701 Chattype::Mailinglist => GroupChangesInfo::default(),
1702 Chattype::OutBroadcast => {
1703 apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1704 }
1705 Chattype::Group => {
1706 apply_group_changes(context, mime_parser, &mut chat, from_id, to_ids, past_ids).await?
1707 }
1708 Chattype::InBroadcast => {
1709 apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1710 }
1711 };
1712
1713 let rfc724_mid_orig = &mime_parser
1714 .get_rfc724_mid()
1715 .unwrap_or(rfc724_mid.to_string());
1716
1717 let mut ephemeral_timer = if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer)
1719 {
1720 match value.parse::<EphemeralTimer>() {
1721 Ok(timer) => timer,
1722 Err(err) => {
1723 warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1724 EphemeralTimer::Disabled
1725 }
1726 }
1727 } else {
1728 EphemeralTimer::Disabled
1729 };
1730
1731 let state = if !mime_parser.incoming {
1732 MessageState::OutDelivered
1733 } else if seen || is_mdn || chat_id_blocked == Blocked::Yes || group_changes.silent
1734 {
1736 MessageState::InSeen
1737 } else if mime_parser.from.addr == STATISTICS_BOT_EMAIL {
1738 MessageState::InNoticed
1739 } else {
1740 MessageState::InFresh
1741 };
1742 let in_fresh = state == MessageState::InFresh;
1743
1744 let sort_to_bottom = false;
1745 let received = true;
1746 let sort_timestamp = chat_id
1747 .calc_sort_timestamp(
1748 context,
1749 mime_parser.timestamp_sent,
1750 sort_to_bottom,
1751 received,
1752 mime_parser.incoming,
1753 )
1754 .await?;
1755
1756 if !chat_id.is_special()
1762 && !mime_parser.parts.is_empty()
1763 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1764 {
1765 let chat_contacts =
1766 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1767 let is_from_in_chat =
1768 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1769
1770 info!(
1771 context,
1772 "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied."
1773 );
1774 if !is_from_in_chat {
1775 warn!(
1776 context,
1777 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1778 );
1779 } else if is_dc_message == MessengerMessage::Yes
1780 && get_previous_message(context, mime_parser)
1781 .await?
1782 .map(|p| p.ephemeral_timer)
1783 == Some(ephemeral_timer)
1784 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1785 {
1786 warn!(
1793 context,
1794 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1795 );
1796 } else if chat_id
1797 .update_timestamp(
1798 context,
1799 Param::EphemeralSettingsTimestamp,
1800 mime_parser.timestamp_sent,
1801 )
1802 .await?
1803 {
1804 if let Err(err) = chat_id
1805 .inner_set_ephemeral_timer(context, ephemeral_timer)
1806 .await
1807 {
1808 warn!(
1809 context,
1810 "Failed to modify timer for chat {chat_id}: {err:#}."
1811 );
1812 } else {
1813 info!(
1814 context,
1815 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1816 );
1817 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1818 chat::add_info_msg_with_cmd(
1819 context,
1820 chat_id,
1821 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1822 SystemMessage::Unknown,
1823 Some(sort_timestamp),
1824 mime_parser.timestamp_sent,
1825 None,
1826 None,
1827 None,
1828 )
1829 .await?;
1830 }
1831 }
1832 } else {
1833 warn!(
1834 context,
1835 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1836 );
1837 }
1838 }
1839
1840 let mut better_msg = if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled
1841 {
1842 Some(stock_str::msg_location_enabled_by(context, from_id).await)
1843 } else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1844 let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
1845
1846 ephemeral_timer = EphemeralTimer::Disabled;
1853
1854 Some(better_msg)
1855 } else {
1856 None
1857 };
1858
1859 drop(chat); let sort_timestamp = tweak_sort_timestamp(
1862 context,
1863 mime_parser,
1864 group_changes.silent,
1865 chat_id,
1866 sort_timestamp,
1867 )
1868 .await?;
1869
1870 let mime_in_reply_to = mime_parser
1871 .get_header(HeaderDef::InReplyTo)
1872 .unwrap_or_default();
1873 let mime_references = mime_parser
1874 .get_header(HeaderDef::References)
1875 .unwrap_or_default();
1876
1877 let icnt = mime_parser.parts.len();
1882
1883 let subject = mime_parser.get_subject().unwrap_or_default();
1884
1885 let is_system_message = mime_parser.is_system_message;
1886
1887 let mut save_mime_modified = false;
1894
1895 let mime_headers = if mime_parser.is_mime_modified {
1896 let headers = if !mime_parser.decoded_data.is_empty() {
1897 mime_parser.decoded_data.clone()
1898 } else {
1899 imf_raw.to_vec()
1900 };
1901 tokio::task::block_in_place(move || buf_compress(&headers))?
1902 } else {
1903 Vec::new()
1904 };
1905
1906 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
1907
1908 if let Some(m) = group_changes.better_msg {
1909 match &better_msg {
1910 None => better_msg = Some(m),
1911 Some(_) => {
1912 if !m.is_empty() {
1913 group_changes.extra_msgs.push((m, is_system_message, None))
1914 }
1915 }
1916 }
1917 }
1918
1919 let chat_id = if better_msg
1920 .as_ref()
1921 .is_some_and(|better_msg| better_msg.is_empty())
1922 {
1923 DC_CHAT_ID_TRASH
1924 } else {
1925 chat_id
1926 };
1927
1928 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
1929 chat::add_info_msg_with_cmd(
1930 context,
1931 chat_id,
1932 &group_changes_msg,
1933 cmd,
1934 Some(sort_timestamp),
1935 mime_parser.timestamp_sent,
1936 None,
1937 None,
1938 added_removed_id,
1939 )
1940 .await?;
1941 }
1942
1943 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
1944 match mime_parser.get_header(HeaderDef::InReplyTo) {
1945 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
1946 Some(instance_id) => {
1947 if let Err(err) =
1948 add_gossip_peer_from_header(context, instance_id, node_addr).await
1949 {
1950 warn!(context, "Failed to add iroh peer from header: {err:#}.");
1951 }
1952 }
1953 None => {
1954 warn!(
1955 context,
1956 "Cannot add iroh peer because WebXDC instance does not exist."
1957 );
1958 }
1959 },
1960 None => {
1961 warn!(
1962 context,
1963 "Cannot add iroh peer because the message has no In-Reply-To."
1964 );
1965 }
1966 }
1967 }
1968
1969 handle_edit_delete(context, mime_parser, from_id).await?;
1970 handle_post_message(context, mime_parser, from_id, state).await?;
1971
1972 if mime_parser.is_system_message == SystemMessage::CallAccepted
1973 || mime_parser.is_system_message == SystemMessage::CallEnded
1974 {
1975 if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
1976 if let Some(call) =
1977 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
1978 {
1979 context
1980 .handle_call_msg(call.get_id(), mime_parser, from_id)
1981 .await?;
1982 } else {
1983 warn!(context, "Call: Cannot load parent.")
1984 }
1985 } else {
1986 warn!(context, "Call: Not a reply.")
1987 }
1988 }
1989
1990 let hidden = mime_parser.parts.iter().all(|part| part.is_reaction);
1991 let mut parts = mime_parser.parts.iter().peekable();
1992 while let Some(part) = parts.next() {
1993 let hidden = part.is_reaction;
1994 if part.is_reaction {
1995 let reaction_str = simplify::remove_footers(part.msg.as_str());
1996 let is_incoming_fresh = mime_parser.incoming && !seen;
1997 set_msg_reaction(
1998 context,
1999 mime_in_reply_to,
2000 chat_id,
2001 from_id,
2002 sort_timestamp,
2003 Reaction::from(reaction_str.as_str()),
2004 is_incoming_fresh,
2005 )
2006 .await?;
2007 }
2008
2009 let mut param = part.param.clone();
2010 if is_system_message != SystemMessage::Unknown {
2011 param.set_int(Param::Cmd, is_system_message as i32);
2012 }
2013
2014 if let Some(replace_msg_id) = replace_msg_id {
2015 let placeholder = Message::load_from_db(context, replace_msg_id).await?;
2016 for key in [
2017 Param::WebxdcSummary,
2018 Param::WebxdcSummaryTimestamp,
2019 Param::WebxdcDocument,
2020 Param::WebxdcDocumentTimestamp,
2021 ] {
2022 if let Some(value) = placeholder.param.get(key) {
2023 param.set(key, value);
2024 }
2025 }
2026 }
2027
2028 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
2029 (better_msg, Viewtype::Text)
2030 } else {
2031 (&part.msg, part.typ)
2032 };
2033 let part_is_empty =
2034 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
2035
2036 if let Some(contact_id) = group_changes.added_removed_id {
2037 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
2038 }
2039
2040 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
2041 let save_mime_modified = save_mime_modified && parts.peek().is_none();
2042
2043 let ephemeral_timestamp = if in_fresh {
2044 0
2045 } else {
2046 match ephemeral_timer {
2047 EphemeralTimer::Disabled => 0,
2048 EphemeralTimer::Enabled { duration } => {
2049 mime_parser.timestamp_rcvd.saturating_add(duration.into())
2050 }
2051 }
2052 };
2053
2054 if let PreMessageMode::Pre {
2055 metadata: Some(metadata),
2056 ..
2057 } = &mime_parser.pre_message
2058 {
2059 param.apply_post_msg_metadata(metadata);
2060 };
2061
2062 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
2065
2066 let row_id = context
2067 .sql
2068 .call_write(|conn| {
2069 let mut stmt = conn.prepare_cached(
2070 r#"
2071INSERT INTO msgs
2072 (
2073 id,
2074 rfc724_mid, pre_rfc724_mid, chat_id,
2075 from_id, to_id, timestamp, timestamp_sent,
2076 timestamp_rcvd, type, state, msgrmsg,
2077 txt, txt_normalized, subject, param, hidden,
2078 bytes, mime_headers, mime_compressed, mime_in_reply_to,
2079 mime_references, mime_modified, error, ephemeral_timer,
2080 ephemeral_timestamp, download_state, hop_info
2081 )
2082 VALUES (
2083 ?,
2084 ?, ?, ?, ?, ?,
2085 ?, ?, ?, ?,
2086 ?, ?, ?, ?,
2087 ?, ?, ?, ?, ?, 1,
2088 ?, ?, ?, ?,
2089 ?, ?, ?, ?
2090 )
2091ON CONFLICT (id) DO UPDATE
2092SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
2093 from_id=excluded.from_id, to_id=excluded.to_id, timestamp_sent=excluded.timestamp_sent,
2094 type=excluded.type, state=max(state,excluded.state), msgrmsg=excluded.msgrmsg,
2095 txt=excluded.txt, txt_normalized=excluded.txt_normalized, subject=excluded.subject,
2096 param=excluded.param,
2097 hidden=excluded.hidden,bytes=excluded.bytes, mime_headers=excluded.mime_headers,
2098 mime_compressed=excluded.mime_compressed, mime_in_reply_to=excluded.mime_in_reply_to,
2099 mime_references=excluded.mime_references, mime_modified=excluded.mime_modified, error=excluded.error, ephemeral_timer=excluded.ephemeral_timer,
2100 ephemeral_timestamp=excluded.ephemeral_timestamp, download_state=excluded.download_state, hop_info=excluded.hop_info
2101RETURNING id
2102"#)?;
2103 let row_id: MsgId = stmt.query_row(params![
2104 replace_msg_id,
2105 if let PreMessageMode::Pre {post_msg_rfc724_mid, ..} = &mime_parser.pre_message {
2106 post_msg_rfc724_mid
2107 } else { rfc724_mid_orig },
2108 if let PreMessageMode::Pre {..} = &mime_parser.pre_message {
2109 rfc724_mid_orig
2110 } else { "" },
2111 if trash { DC_CHAT_ID_TRASH } else { chat_id },
2112 if trash { ContactId::UNDEFINED } else { from_id },
2113 if trash { ContactId::UNDEFINED } else { to_id },
2114 sort_timestamp,
2115 if trash { 0 } else { mime_parser.timestamp_sent },
2116 if trash { 0 } else { mime_parser.timestamp_rcvd },
2117 if trash {
2118 Viewtype::Unknown
2119 } else if let PreMessageMode::Pre {..} = mime_parser.pre_message {
2120 Viewtype::Text
2121 } else { typ },
2122 if trash { MessageState::Undefined } else { state },
2123 if trash { MessengerMessage::No } else { is_dc_message },
2124 if trash || hidden { "" } else { msg },
2125 if trash || hidden { None } else { normalize_text(msg) },
2126 if trash || hidden { "" } else { &subject },
2127 if trash {
2128 "".to_string()
2129 } else {
2130 param.to_string()
2131 },
2132 !trash && hidden,
2133 if trash { 0 } else { part.bytes as isize },
2134 if save_mime_modified && !(trash || hidden) {
2135 mime_headers.clone()
2136 } else {
2137 Vec::new()
2138 },
2139 if trash { "" } else { mime_in_reply_to },
2140 if trash { "" } else { mime_references },
2141 !trash && save_mime_modified,
2142 if trash { "" } else { part.error.as_deref().unwrap_or_default() },
2143 if trash { 0 } else { ephemeral_timer.to_u32() },
2144 if trash { 0 } else { ephemeral_timestamp },
2145 if trash {
2146 DownloadState::Done
2147 } else if mime_parser.decrypting_failed {
2148 DownloadState::Undecipherable
2149 } else if let PreMessageMode::Pre {..} = mime_parser.pre_message {
2150 DownloadState::Available
2151 } else {
2152 DownloadState::Done
2153 },
2154 if trash { "" } else { &mime_parser.hop_info },
2155 ],
2156 |row| {
2157 let msg_id: MsgId = row.get(0)?;
2158 Ok(msg_id)
2159 }
2160 )?;
2161 Ok(row_id)
2162 })
2163 .await?;
2164
2165 replace_msg_id = None;
2168
2169 ensure_and_debug_assert!(!row_id.is_special(), "Rowid {row_id} is special");
2170 created_db_entries.push(row_id);
2171 }
2172
2173 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
2175 if part.typ == Viewtype::Webxdc {
2177 if let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic) {
2178 let mut topic_raw = [0u8; 32];
2180 BASE32_NOPAD
2181 .decode_mut(topic.to_ascii_uppercase().as_bytes(), &mut topic_raw)
2182 .map_err(|e| e.error)
2183 .context("Wrong gossip topic header")?;
2184
2185 let topic = TopicId::from_bytes(topic_raw);
2186 insert_topic_stub(context, *msg_id, topic).await?;
2187 } else {
2188 warn!(context, "webxdc doesn't have a gossip topic")
2189 }
2190 }
2191
2192 maybe_set_logging_xdc_inner(
2193 context,
2194 part.typ,
2195 chat_id,
2196 part.param.get(Param::Filename),
2197 *msg_id,
2198 )
2199 .await?;
2200 }
2201
2202 if let Some(replace_msg_id) = replace_msg_id {
2203 let on_server = rfc724_mid == rfc724_mid_orig;
2207 replace_msg_id.trash(context, on_server).await?;
2208 }
2209
2210 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2211 Some(addr) => context.is_self_addr(addr).await?,
2212 None => true,
2213 };
2214 if unarchive {
2215 chat_id.unarchive_if_not_muted(context, state).await?;
2216 }
2217
2218 info!(
2219 context,
2220 "Message has {icnt} parts and is assigned to chat #{chat_id}."
2221 );
2222
2223 if !chat_id.is_trash() && !hidden {
2224 let mut chat = Chat::load_from_db(context, chat_id).await?;
2225 let mut update_param = false;
2226
2227 if chat
2231 .param
2232 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
2233 {
2234 let subject = mime_parser.get_subject().unwrap_or_default();
2237
2238 chat.param.set(Param::LastSubject, subject);
2239 update_param = true;
2240 }
2241
2242 if chat.is_unpromoted() {
2243 chat.param.remove(Param::Unpromoted);
2244 update_param = true;
2245 }
2246 if update_param {
2247 chat.update_param(context).await?;
2248 }
2249 }
2250
2251 let needs_delete_job =
2255 !mime_parser.incoming && is_mdn && is_dc_message == MessengerMessage::Yes;
2256
2257 Ok(ReceivedMsg {
2258 chat_id,
2259 state,
2260 hidden,
2261 sort_timestamp,
2262 msg_ids: created_db_entries,
2263 needs_delete_job,
2264 })
2265}
2266
2267async fn handle_edit_delete(
2272 context: &Context,
2273 mime_parser: &MimeMessage,
2274 from_id: ContactId,
2275) -> Result<()> {
2276 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
2277 if let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? {
2278 if let Some(mut original_msg) =
2279 Message::load_from_db_optional(context, original_msg_id).await?
2280 {
2281 if original_msg.from_id == from_id {
2282 if let Some(part) = mime_parser.parts.first() {
2283 let edit_msg_showpadlock = part
2284 .param
2285 .get_bool(Param::GuaranteeE2ee)
2286 .unwrap_or_default();
2287 if edit_msg_showpadlock || !original_msg.get_showpadlock() {
2288 let new_text =
2289 part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
2290 chat::save_text_edit_to_db(context, &mut original_msg, new_text)
2291 .await?;
2292 } else {
2293 warn!(context, "Edit message: Not encrypted.");
2294 }
2295 }
2296 } else {
2297 warn!(context, "Edit message: Bad sender.");
2298 }
2299 } else {
2300 warn!(context, "Edit message: Database entry does not exist.");
2301 }
2302 } else {
2303 warn!(
2304 context,
2305 "Edit message: rfc724_mid {rfc724_mid:?} not found."
2306 );
2307 }
2308 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
2309 && let Some(part) = mime_parser.parts.first()
2310 {
2311 if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
2314 let mut modified_chat_ids = HashSet::new();
2315 let mut msg_ids = Vec::new();
2316
2317 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
2318 for rfc724_mid in rfc724_mid_vec {
2319 if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
2320 if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
2321 if msg.from_id == from_id {
2322 message::delete_msg_locally(context, &msg).await?;
2323 msg_ids.push(msg.id);
2324 modified_chat_ids.insert(msg.chat_id);
2325 } else {
2326 warn!(context, "Delete message: Bad sender.");
2327 }
2328 } else {
2329 warn!(context, "Delete message: Database entry does not exist.");
2330 }
2331 } else {
2332 warn!(context, "Delete message: {rfc724_mid:?} not found.");
2333 }
2334 }
2335 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
2336 } else {
2337 warn!(context, "Delete message: Not encrypted.");
2338 }
2339 }
2340 Ok(())
2341}
2342
2343async fn handle_post_message(
2344 context: &Context,
2345 mime_parser: &MimeMessage,
2346 from_id: ContactId,
2347 state: MessageState,
2348) -> Result<()> {
2349 let PreMessageMode::Post = &mime_parser.pre_message else {
2350 return Ok(());
2351 };
2352 let rfc724_mid = mime_parser
2355 .get_rfc724_mid()
2356 .context("expected Post-Message to have a message id")?;
2357
2358 let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
2359 warn!(
2360 context,
2361 "handle_post_message: {rfc724_mid}: Database entry does not exist."
2362 );
2363 return Ok(());
2364 };
2365 let Some(original_msg) = Message::load_from_db_optional(context, msg_id).await? else {
2366 warn!(
2368 context,
2369 "handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
2370 );
2371 return Ok(());
2372 };
2373 let Some(part) = mime_parser.parts.first() else {
2374 return Ok(());
2375 };
2376
2377 if from_id != original_msg.from_id {
2380 warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
2381 return Ok(());
2382 }
2383 let post_msg_showpadlock = part
2384 .param
2385 .get_bool(Param::GuaranteeE2ee)
2386 .unwrap_or_default();
2387 if !post_msg_showpadlock && original_msg.get_showpadlock() {
2388 warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
2389 return Ok(());
2390 }
2391
2392 if !part.typ.has_file() {
2393 warn!(
2394 context,
2395 "handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
2396 );
2397 return Ok(());
2398 }
2399
2400 let mut new_params = original_msg.param.clone();
2401 new_params
2402 .merge_in_params(part.param.clone())
2403 .remove(Param::PostMessageFileBytes)
2404 .remove(Param::PostMessageViewtype);
2405 context
2408 .sql
2409 .execute(
2410 "
2411UPDATE msgs SET param=?, type=?, bytes=?, error=?, state=max(state,?), download_state=?
2412WHERE id=?
2413 ",
2414 (
2415 new_params.to_string(),
2416 part.typ,
2417 part.bytes as isize,
2418 part.error.as_deref().unwrap_or_default(),
2419 state,
2420 DownloadState::Done as u32,
2421 original_msg.id,
2422 ),
2423 )
2424 .await?;
2425 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2426
2427 Ok(())
2428}
2429
2430async fn tweak_sort_timestamp(
2431 context: &Context,
2432 mime_parser: &mut MimeMessage,
2433 silent: bool,
2434 chat_id: ChatId,
2435 sort_timestamp: i64,
2436) -> Result<i64> {
2437 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
2446 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
2447 std::cmp::max(sort_timestamp, parent_timestamp)
2448 });
2449
2450 if silent {
2454 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
2455 t
2456 } else {
2457 chat_id.created_timestamp(context).await?
2458 };
2459 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
2460 }
2461 Ok(sort_timestamp)
2462}
2463
2464async fn save_locations(
2468 context: &Context,
2469 mime_parser: &MimeMessage,
2470 chat_id: ChatId,
2471 from_id: ContactId,
2472 msg_id: MsgId,
2473) -> Result<()> {
2474 if chat_id.is_special() {
2475 return Ok(());
2477 }
2478
2479 let mut send_event = false;
2480
2481 if let Some(message_kml) = &mime_parser.message_kml
2482 && let Some(newest_location_id) =
2483 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
2484 {
2485 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
2486 send_event = true;
2487 }
2488
2489 if let Some(location_kml) = &mime_parser.location_kml
2490 && let Some(addr) = &location_kml.addr
2491 {
2492 let contact = Contact::get_by_id(context, from_id).await?;
2493 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
2494 if location::save(context, chat_id, from_id, &location_kml.locations, false)
2495 .await?
2496 .is_some()
2497 {
2498 send_event = true;
2499 }
2500 } else {
2501 warn!(
2502 context,
2503 "Address in location.kml {:?} is not the same as the sender address {:?}.",
2504 addr,
2505 contact.get_addr()
2506 );
2507 }
2508 }
2509 if send_event {
2510 context.emit_location_changed(Some(from_id)).await?;
2511 }
2512 Ok(())
2513}
2514
2515async fn lookup_chat_by_reply(
2516 context: &Context,
2517 mime_parser: &MimeMessage,
2518 parent: &Message,
2519) -> Result<Option<(ChatId, Blocked)>> {
2520 ensure_and_debug_assert!(
2525 mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted(),
2526 "Encrypted message has group ID {}",
2527 mime_parser.get_chat_group_id().unwrap_or_default(),
2528 );
2529
2530 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
2532 return Ok(None);
2533 };
2534
2535 if is_probably_private_reply(context, mime_parser, parent_chat_id).await? {
2538 return Ok(None);
2539 }
2540
2541 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
2545 if parent_chat.typ == Chattype::Single && mime_parser.recipients.len() > 1 {
2546 return Ok(None);
2547 }
2548
2549 if parent_chat.is_encrypted(context).await? && !mime_parser.was_encrypted() {
2551 return Ok(None);
2552 }
2553
2554 info!(
2555 context,
2556 "Assigning message to {parent_chat_id} as it's a reply to {}.", parent.rfc724_mid
2557 );
2558 Ok(Some((parent_chat.id, parent_chat.blocked)))
2559}
2560
2561async fn lookup_or_create_adhoc_group(
2562 context: &Context,
2563 mime_parser: &MimeMessage,
2564 to_ids: &[Option<ContactId>],
2565 allow_creation: bool,
2566 create_blocked: Blocked,
2567) -> Result<Option<(ChatId, Blocked, bool)>> {
2568 if mime_parser.decrypting_failed {
2569 warn!(
2570 context,
2571 "Not creating ad-hoc group for message that cannot be decrypted."
2572 );
2573 return Ok(None);
2574 }
2575
2576 let fingerprint = None;
2578 let find_key_contact_by_addr = false;
2579 let prevent_rename = should_prevent_rename(mime_parser);
2580 let (from_id, _from_id_blocked, _incoming_origin) = from_field_to_contact_id(
2581 context,
2582 &mime_parser.from,
2583 fingerprint,
2584 prevent_rename,
2585 find_key_contact_by_addr,
2586 )
2587 .await?
2588 .context("Cannot lookup address-contact by the From field")?;
2589
2590 let grpname = mime_parser
2591 .get_header(HeaderDef::ChatGroupName)
2592 .map(|s| s.to_string())
2593 .unwrap_or_else(|| {
2594 mime_parser
2595 .get_subject()
2596 .map(|s| remove_subject_prefix(&s))
2597 .unwrap_or_else(|| "👥📧".to_string())
2598 });
2599 let to_ids: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2600 let mut contact_ids = BTreeSet::<ContactId>::from_iter(to_ids.iter().copied());
2601 contact_ids.insert(from_id);
2602 let trans_fn = |t: &mut rusqlite::Transaction| {
2603 t.pragma_update(None, "query_only", "0")?;
2604 t.execute(
2605 "CREATE TEMP TABLE temp.contacts (
2606 id INTEGER PRIMARY KEY
2607 ) STRICT",
2608 (),
2609 )
2610 .context("CREATE TEMP TABLE temp.contacts")?;
2611 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2612 for &id in &contact_ids {
2613 stmt.execute((id,)).context("INSERT INTO temp.contacts")?;
2614 }
2615 let val = t
2616 .query_row(
2617 "SELECT c.id, c.blocked
2618 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2619 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2620 AND (SELECT COUNT(*) FROM chats_contacts
2621 WHERE chat_id=c.id
2622 AND add_timestamp >= remove_timestamp)=?
2623 AND (SELECT COUNT(*) FROM chats_contacts
2624 WHERE chat_id=c.id
2625 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2626 AND add_timestamp >= remove_timestamp)=0
2627 ORDER BY m.timestamp DESC",
2628 (&grpname, contact_ids.len()),
2629 |row| {
2630 let id: ChatId = row.get(0)?;
2631 let blocked: Blocked = row.get(1)?;
2632 Ok((id, blocked))
2633 },
2634 )
2635 .optional()
2636 .context("Select chat with matching name and members")?;
2637 t.execute("DROP TABLE temp.contacts", ())
2638 .context("DROP TABLE temp.contacts")?;
2639 Ok(val)
2640 };
2641 let query_only = true;
2642 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2643 info!(
2644 context,
2645 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2646 );
2647 return Ok(Some((chat_id, blocked, false)));
2648 }
2649 if !allow_creation {
2650 return Ok(None);
2651 }
2652 Ok(create_adhoc_group(
2653 context,
2654 mime_parser,
2655 create_blocked,
2656 from_id,
2657 &to_ids,
2658 &grpname,
2659 )
2660 .await
2661 .context("Could not create ad hoc group")?
2662 .map(|(chat_id, blocked)| (chat_id, blocked, true)))
2663}
2664
2665async fn is_probably_private_reply(
2668 context: &Context,
2669 mime_parser: &MimeMessage,
2670 parent_chat_id: ChatId,
2671) -> Result<bool> {
2672 if mime_parser.get_chat_group_id().is_some() {
2674 return Ok(false);
2675 }
2676
2677 if mime_parser.recipients.len() != 1 {
2685 return Ok(false);
2686 }
2687
2688 if !mime_parser.has_chat_version() {
2689 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2690 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2691 return Ok(false);
2692 }
2693 }
2694
2695 Ok(true)
2696}
2697
2698async fn create_group(
2704 context: &Context,
2705 mime_parser: &mut MimeMessage,
2706 create_blocked: Blocked,
2707 from_id: ContactId,
2708 to_ids: &[Option<ContactId>],
2709 past_ids: &[Option<ContactId>],
2710 grpid: &str,
2711) -> Result<Option<(ChatId, Blocked)>> {
2712 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2713 let mut chat_id = None;
2714 let mut chat_id_blocked = Default::default();
2715
2716 if chat_id.is_none()
2717 && !mime_parser.is_mailinglist_message()
2718 && !grpid.is_empty()
2719 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2720 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2722 {
2723 let grpname = mime_parser
2725 .get_header(HeaderDef::ChatGroupName)
2726 .context("Chat-Group-Name vanished")?
2727 .trim();
2731 let new_chat_id = ChatId::create_multiuser_record(
2732 context,
2733 Chattype::Group,
2734 grpid,
2735 grpname,
2736 create_blocked,
2737 None,
2738 mime_parser.timestamp_sent,
2739 )
2740 .await
2741 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2742
2743 chat_id = Some(new_chat_id);
2744 chat_id_blocked = create_blocked;
2745
2746 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2748 let mut new_to_ids = to_ids.to_vec();
2749 if !new_to_ids.contains(&Some(from_id)) {
2750 new_to_ids.insert(0, Some(from_id));
2751 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2752 }
2753
2754 update_chats_contacts_timestamps(
2755 context,
2756 new_chat_id,
2757 None,
2758 &new_to_ids,
2759 past_ids,
2760 &chat_group_member_timestamps,
2761 )
2762 .await?;
2763 } else {
2764 let mut members = vec![ContactId::SELF];
2765 if !from_id.is_special() {
2766 members.push(from_id);
2767 }
2768 members.extend(to_ids_flat);
2769
2770 let timestamp = 0;
2776
2777 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2778 }
2779
2780 context.emit_event(EventType::ChatModified(new_chat_id));
2781 chatlist_events::emit_chatlist_changed(context);
2782 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2783 }
2784
2785 if let Some(chat_id) = chat_id {
2786 Ok(Some((chat_id, chat_id_blocked)))
2787 } else if mime_parser.decrypting_failed {
2788 Ok(None)
2795 } else {
2796 info!(context, "Message belongs to unwanted group (TRASH).");
2799 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2800 }
2801}
2802
2803async fn update_chats_contacts_timestamps(
2804 context: &Context,
2805 chat_id: ChatId,
2806 ignored_id: Option<ContactId>,
2807 to_ids: &[Option<ContactId>],
2808 past_ids: &[Option<ContactId>],
2809 chat_group_member_timestamps: &[i64],
2810) -> Result<bool> {
2811 let expected_timestamps_count = to_ids.len() + past_ids.len();
2812
2813 if chat_group_member_timestamps.len() != expected_timestamps_count {
2814 warn!(
2815 context,
2816 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2817 chat_group_member_timestamps.len(),
2818 expected_timestamps_count
2819 );
2820 return Ok(false);
2821 }
2822
2823 let mut modified = false;
2824
2825 context
2826 .sql
2827 .transaction(|transaction| {
2828 let mut add_statement = transaction.prepare(
2829 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2830 VALUES (?1, ?2, ?3)
2831 ON CONFLICT (chat_id, contact_id)
2832 DO
2833 UPDATE SET add_timestamp=?3
2834 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2835 )?;
2836
2837 for (contact_id, ts) in iter::zip(
2838 to_ids.iter(),
2839 chat_group_member_timestamps.iter().take(to_ids.len()),
2840 ) {
2841 if let Some(contact_id) = contact_id
2842 && Some(*contact_id) != ignored_id
2843 {
2844 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2848 }
2849 }
2850
2851 let mut remove_statement = transaction.prepare(
2852 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2853 VALUES (?1, ?2, ?3)
2854 ON CONFLICT (chat_id, contact_id)
2855 DO
2856 UPDATE SET remove_timestamp=?3
2857 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2858 )?;
2859
2860 for (contact_id, ts) in iter::zip(
2861 past_ids.iter(),
2862 chat_group_member_timestamps.iter().skip(to_ids.len()),
2863 ) {
2864 if let Some(contact_id) = contact_id {
2865 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2869 }
2870 }
2871
2872 Ok(())
2873 })
2874 .await?;
2875
2876 Ok(modified)
2877}
2878
2879#[derive(Default)]
2883struct GroupChangesInfo {
2884 better_msg: Option<String>,
2887 added_removed_id: Option<ContactId>,
2889 silent: bool,
2891 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
2893}
2894
2895async fn apply_group_changes(
2902 context: &Context,
2903 mime_parser: &mut MimeMessage,
2904 chat: &mut Chat,
2905 from_id: ContactId,
2906 to_ids: &[Option<ContactId>],
2907 past_ids: &[Option<ContactId>],
2908) -> Result<GroupChangesInfo> {
2909 let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
2910 ensure!(chat.typ == Chattype::Group);
2911 ensure!(!chat.id.is_special());
2912
2913 let mut send_event_chat_modified = false;
2914 let (mut removed_id, mut added_id) = (None, None);
2915 let mut better_msg = None;
2916 let mut silent = false;
2917 let chat_contacts =
2918 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat.id).await?);
2919 let is_from_in_chat =
2920 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
2921
2922 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2923 if !is_from_in_chat {
2924 better_msg = Some(String::new());
2925 } else if let Some(removed_fpr) =
2926 mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr)
2927 {
2928 removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
2929 } else {
2930 removed_id =
2932 lookup_key_contact_by_address(context, removed_addr, Some(chat.id)).await?;
2933 }
2934 if let Some(id) = removed_id {
2935 better_msg = if id == from_id {
2936 silent = true;
2937 Some(stock_str::msg_group_left_local(context, from_id).await)
2938 } else {
2939 Some(stock_str::msg_del_member_local(context, id, from_id).await)
2940 };
2941 } else {
2942 warn!(context, "Removed {removed_addr:?} has no contact id.")
2943 }
2944 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2945 if !is_from_in_chat {
2946 better_msg = Some(String::new());
2947 } else if let Some(key) = mime_parser.gossiped_keys.get(added_addr) {
2948 if !chat_contacts.contains(&from_id) {
2949 chat::add_to_chat_contacts_table(
2950 context,
2951 mime_parser.timestamp_sent,
2952 chat.id,
2953 &[from_id],
2954 )
2955 .await?;
2956 }
2957
2958 let fingerprint = key.public_key.dc_fingerprint().hex();
2965 if let Some(contact_id) =
2966 lookup_key_contact_by_fingerprint(context, &fingerprint).await?
2967 {
2968 added_id = Some(contact_id);
2969 better_msg =
2970 Some(stock_str::msg_add_member_local(context, contact_id, from_id).await);
2971 } else {
2972 warn!(context, "Added {added_addr:?} has no contact id.");
2973 }
2974 } else {
2975 warn!(context, "Added {added_addr:?} has no gossiped key.");
2976 }
2977 }
2978
2979 if is_from_in_chat {
2980 apply_chat_name_and_avatar_changes(
2981 context,
2982 mime_parser,
2983 from_id,
2984 chat,
2985 &mut send_event_chat_modified,
2986 &mut better_msg,
2987 )
2988 .await?;
2989
2990 if chat.member_list_is_stale(context).await? {
2991 info!(context, "Member list is stale.");
2992 let mut new_members: HashSet<ContactId> =
2993 HashSet::from_iter(to_ids_flat.iter().copied());
2994 new_members.insert(ContactId::SELF);
2995 if !from_id.is_special() {
2996 new_members.insert(from_id);
2997 }
2998
2999 context
3000 .sql
3001 .transaction(|transaction| {
3002 transaction.execute(
3004 "DELETE FROM chats_contacts
3005 WHERE chat_id=?",
3006 (chat.id,),
3007 )?;
3008
3009 let mut statement = transaction.prepare(
3011 "INSERT INTO chats_contacts (chat_id, contact_id)
3012 VALUES (?, ?)",
3013 )?;
3014 for contact_id in &new_members {
3015 statement.execute((chat.id, contact_id))?;
3016 }
3017
3018 Ok(())
3019 })
3020 .await?;
3021 send_event_chat_modified = true;
3022 } else if let Some(ref chat_group_member_timestamps) =
3023 mime_parser.chat_group_member_timestamps()
3024 {
3025 send_event_chat_modified |= update_chats_contacts_timestamps(
3026 context,
3027 chat.id,
3028 Some(from_id),
3029 to_ids,
3030 past_ids,
3031 chat_group_member_timestamps,
3032 )
3033 .await?;
3034 } else {
3035 let mut new_members: HashSet<ContactId>;
3036 let self_added =
3039 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
3040 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
3041 && !chat_contacts.contains(&ContactId::SELF)
3042 } else {
3043 false
3044 };
3045 if self_added {
3046 new_members = HashSet::from_iter(to_ids_flat.iter().copied());
3047 new_members.insert(ContactId::SELF);
3048 if !from_id.is_special() {
3049 new_members.insert(from_id);
3050 }
3051 } else {
3052 new_members = chat_contacts.clone();
3053 }
3054
3055 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
3057 new_members.extend(to_ids_flat.iter());
3060 }
3061
3062 if let Some(added_id) = added_id {
3064 new_members.insert(added_id);
3065 }
3066
3067 if let Some(removed_id) = removed_id {
3069 new_members.remove(&removed_id);
3070 }
3071
3072 if new_members != chat_contacts {
3073 chat::update_chat_contacts_table(
3074 context,
3075 mime_parser.timestamp_sent,
3076 chat.id,
3077 &new_members,
3078 )
3079 .await?;
3080 send_event_chat_modified = true;
3081 }
3082 }
3083
3084 chat.id
3085 .update_timestamp(
3086 context,
3087 Param::MemberListTimestamp,
3088 mime_parser.timestamp_sent,
3089 )
3090 .await?;
3091 }
3092
3093 let new_chat_contacts = HashSet::<ContactId>::from_iter(
3094 chat::get_chat_contacts(context, chat.id)
3095 .await?
3096 .iter()
3097 .copied(),
3098 );
3099
3100 let mut added_ids: HashSet<ContactId> = new_chat_contacts
3102 .difference(&chat_contacts)
3103 .copied()
3104 .collect();
3105 let mut removed_ids: HashSet<ContactId> = chat_contacts
3106 .difference(&new_chat_contacts)
3107 .copied()
3108 .collect();
3109
3110 if let Some(added_id) = added_id
3111 && !added_ids.remove(&added_id)
3112 && added_id != ContactId::SELF
3113 {
3114 info!(context, "No-op 'Member added' message (TRASH)");
3117 better_msg = Some(String::new());
3118 }
3119 if let Some(removed_id) = removed_id {
3120 removed_ids.remove(&removed_id);
3121 }
3122 let group_changes_msgs = if !chat_contacts.contains(&ContactId::SELF)
3123 && new_chat_contacts.contains(&ContactId::SELF)
3124 {
3125 Vec::new()
3126 } else {
3127 group_changes_msgs(context, &added_ids, &removed_ids, chat.id).await?
3128 };
3129
3130 if send_event_chat_modified {
3131 context.emit_event(EventType::ChatModified(chat.id));
3132 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3133 }
3134 Ok(GroupChangesInfo {
3135 better_msg,
3136 added_removed_id: if added_id.is_some() {
3137 added_id
3138 } else {
3139 removed_id
3140 },
3141 silent,
3142 extra_msgs: group_changes_msgs,
3143 })
3144}
3145
3146async fn apply_chat_name_and_avatar_changes(
3151 context: &Context,
3152 mime_parser: &MimeMessage,
3153 from_id: ContactId,
3154 chat: &mut Chat,
3155 send_event_chat_modified: &mut bool,
3156 better_msg: &mut Option<String>,
3157) -> Result<()> {
3158 let group_name_timestamp = mime_parser
3161 .get_header(HeaderDef::ChatGroupNameTimestamp)
3162 .and_then(|s| s.parse::<i64>().ok());
3163
3164 if let Some(old_name) = mime_parser
3165 .get_header(HeaderDef::ChatGroupNameChanged)
3166 .map(|s| s.trim())
3167 .or(match group_name_timestamp {
3168 Some(0) => None,
3169 Some(_) => Some(chat.name.as_str()),
3170 None => None,
3171 })
3172 && let Some(grpname) = mime_parser
3173 .get_header(HeaderDef::ChatGroupName)
3174 .map(|grpname| grpname.trim())
3175 .filter(|grpname| grpname.len() < 200)
3176 {
3177 let grpname = &sanitize_single_line(grpname);
3178
3179 let chat_group_name_timestamp = chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
3180 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
3181 if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
3183 && chat
3184 .id
3185 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
3186 .await?
3187 && grpname != &chat.name
3188 {
3189 info!(context, "Updating grpname for chat {}.", chat.id);
3190 context
3191 .sql
3192 .execute(
3193 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3194 (grpname, normalize_text(grpname), chat.id),
3195 )
3196 .await?;
3197 *send_event_chat_modified = true;
3198 }
3199 if mime_parser
3200 .get_header(HeaderDef::ChatGroupNameChanged)
3201 .is_some()
3202 {
3203 let old_name = &sanitize_single_line(old_name);
3204 better_msg
3205 .get_or_insert(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
3206 }
3207 }
3208
3209 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
3212 && value == "group-avatar-changed"
3213 && let Some(avatar_action) = &mime_parser.group_avatar
3214 {
3215 better_msg.get_or_insert(match avatar_action {
3218 AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
3219 AvatarAction::Change(_) => stock_str::msg_grp_img_changed(context, from_id).await,
3220 });
3221 }
3222
3223 if let Some(avatar_action) = &mime_parser.group_avatar {
3224 info!(context, "Group-avatar change for {}.", chat.id);
3225 if chat
3226 .param
3227 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
3228 {
3229 match avatar_action {
3230 AvatarAction::Change(profile_image) => {
3231 chat.param.set(Param::ProfileImage, profile_image);
3232 }
3233 AvatarAction::Delete => {
3234 chat.param.remove(Param::ProfileImage);
3235 }
3236 };
3237 chat.update_param(context).await?;
3238 *send_event_chat_modified = true;
3239 }
3240 }
3241
3242 Ok(())
3243}
3244
3245async fn group_changes_msgs(
3247 context: &Context,
3248 added_ids: &HashSet<ContactId>,
3249 removed_ids: &HashSet<ContactId>,
3250 chat_id: ChatId,
3251) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
3252 let mut group_changes_msgs: Vec<(String, SystemMessage, Option<ContactId>)> = Vec::new();
3253 if !added_ids.is_empty() {
3254 warn!(
3255 context,
3256 "Implicit addition of {added_ids:?} to chat {chat_id}."
3257 );
3258 }
3259 if !removed_ids.is_empty() {
3260 warn!(
3261 context,
3262 "Implicit removal of {removed_ids:?} from chat {chat_id}."
3263 );
3264 }
3265 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
3266 for contact_id in added_ids {
3267 group_changes_msgs.push((
3268 stock_str::msg_add_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3269 SystemMessage::MemberAddedToGroup,
3270 Some(*contact_id),
3271 ));
3272 }
3273 for contact_id in removed_ids {
3274 group_changes_msgs.push((
3275 stock_str::msg_del_member_local(context, *contact_id, ContactId::UNDEFINED).await,
3276 SystemMessage::MemberRemovedFromGroup,
3277 Some(*contact_id),
3278 ));
3279 }
3280
3281 Ok(group_changes_msgs)
3282}
3283
3284static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
3285
3286fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
3287 Ok(match LIST_ID_REGEX.captures(list_id_header) {
3288 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
3289 None => list_id_header
3290 .trim()
3291 .trim_start_matches('<')
3292 .trim_end_matches('>'),
3293 }
3294 .to_string())
3295}
3296
3297async fn create_or_lookup_mailinglist_or_broadcast(
3312 context: &Context,
3313 allow_creation: bool,
3314 create_blocked: Blocked,
3315 list_id_header: &str,
3316 from_id: ContactId,
3317 mime_parser: &MimeMessage,
3318) -> Result<Option<(ChatId, Blocked, bool)>> {
3319 let listid = mailinglist_header_listid(list_id_header)?;
3320
3321 if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
3322 return Ok(Some((chat_id, blocked, false)));
3323 }
3324
3325 let chattype = if mime_parser.was_encrypted() {
3326 Chattype::InBroadcast
3327 } else {
3328 Chattype::Mailinglist
3329 };
3330
3331 let name = if chattype == Chattype::InBroadcast {
3332 mime_parser
3333 .get_header(HeaderDef::ChatGroupName)
3334 .unwrap_or("Broadcast Channel")
3335 } else {
3336 &compute_mailinglist_name(list_id_header, &listid, mime_parser)
3337 };
3338
3339 if allow_creation {
3340 let param = mime_parser.list_post.as_ref().map(|list_post| {
3342 let mut p = Params::new();
3343 p.set(Param::ListPost, list_post);
3344 p.to_string()
3345 });
3346
3347 let chat_id = ChatId::create_multiuser_record(
3348 context,
3349 chattype,
3350 &listid,
3351 name,
3352 create_blocked,
3353 param,
3354 mime_parser.timestamp_sent,
3355 )
3356 .await
3357 .with_context(|| {
3358 format!(
3359 "failed to create mailinglist '{}' for grpid={}",
3360 &name, &listid
3361 )
3362 })?;
3363
3364 if chattype == Chattype::InBroadcast {
3365 chat::add_to_chat_contacts_table(
3366 context,
3367 mime_parser.timestamp_sent,
3368 chat_id,
3369 &[from_id],
3370 )
3371 .await?;
3372 }
3373
3374 context.emit_event(EventType::ChatModified(chat_id));
3375 chatlist_events::emit_chatlist_changed(context);
3376 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3377
3378 Ok(Some((chat_id, create_blocked, true)))
3379 } else {
3380 info!(context, "Creating list forbidden by caller.");
3381 Ok(None)
3382 }
3383}
3384
3385fn compute_mailinglist_name(
3386 list_id_header: &str,
3387 listid: &str,
3388 mime_parser: &MimeMessage,
3389) -> String {
3390 let mut name = match LIST_ID_REGEX
3391 .captures(list_id_header)
3392 .and_then(|caps| caps.get(1))
3393 {
3394 Some(cap) => cap.as_str().trim().to_string(),
3395 None => "".to_string(),
3396 };
3397
3398 if listid.ends_with(".list-id.mcsv.net")
3402 && let Some(display_name) = &mime_parser.from.display_name
3403 {
3404 name.clone_from(display_name);
3405 }
3406
3407 let subject = mime_parser.get_subject().unwrap_or_default();
3411 static SUBJECT: LazyLock<Regex> =
3412 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
3414 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
3415 }
3416
3417 if name.is_empty()
3424 && (mime_parser.from.addr.contains("noreply")
3425 || mime_parser.from.addr.contains("no-reply")
3426 || mime_parser.from.addr.starts_with("notifications@")
3427 || mime_parser.from.addr.starts_with("newsletter@")
3428 || listid.ends_with(".xt.local"))
3429 && let Some(display_name) = &mime_parser.from.display_name
3430 {
3431 name.clone_from(display_name);
3432 }
3433
3434 if name.is_empty() {
3437 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
3439 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
3440 if let Some(cap) = PREFIX_32_CHARS_HEX
3441 .captures(listid)
3442 .and_then(|caps| caps.get(2))
3443 {
3444 name = cap.as_str().to_string();
3445 } else {
3446 name = listid.to_string();
3447 }
3448 }
3449
3450 sanitize_single_line(&name)
3451}
3452
3453async fn apply_mailinglist_changes(
3457 context: &Context,
3458 mime_parser: &MimeMessage,
3459 chat_id: ChatId,
3460) -> Result<()> {
3461 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
3462 return Ok(());
3463 };
3464
3465 let mut chat = Chat::load_from_db(context, chat_id).await?;
3466 if chat.typ != Chattype::Mailinglist {
3467 return Ok(());
3468 }
3469 let listid = &chat.grpid;
3470
3471 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
3472 if chat.name != new_name
3473 && chat_id
3474 .update_timestamp(
3475 context,
3476 Param::GroupNameTimestamp,
3477 mime_parser.timestamp_sent,
3478 )
3479 .await?
3480 {
3481 info!(context, "Updating listname for chat {chat_id}.");
3482 context
3483 .sql
3484 .execute(
3485 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
3486 (&new_name, normalize_text(&new_name), chat_id),
3487 )
3488 .await?;
3489 context.emit_event(EventType::ChatModified(chat_id));
3490 }
3491
3492 let Some(list_post) = &mime_parser.list_post else {
3493 return Ok(());
3494 };
3495
3496 let list_post = match ContactAddress::new(list_post) {
3497 Ok(list_post) => list_post,
3498 Err(err) => {
3499 warn!(context, "Invalid List-Post: {:#}.", err);
3500 return Ok(());
3501 }
3502 };
3503 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
3504 let mut contact = Contact::get_by_id(context, contact_id).await?;
3505 if contact.param.get(Param::ListId) != Some(listid) {
3506 contact.param.set(Param::ListId, listid);
3507 contact.update_param(context).await?;
3508 }
3509
3510 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
3511 if list_post.as_ref() != old_list_post {
3512 chat.param.remove(Param::ListPost);
3515 chat.update_param(context).await?;
3516 }
3517 } else {
3518 chat.param.set(Param::ListPost, list_post);
3519 chat.update_param(context).await?;
3520 }
3521
3522 Ok(())
3523}
3524
3525async fn apply_out_broadcast_changes(
3526 context: &Context,
3527 mime_parser: &MimeMessage,
3528 chat: &mut Chat,
3529 from_id: ContactId,
3530) -> Result<GroupChangesInfo> {
3531 ensure!(chat.typ == Chattype::OutBroadcast);
3532
3533 let mut send_event_chat_modified = false;
3534 let mut better_msg = None;
3535
3536 if from_id == ContactId::SELF {
3537 apply_chat_name_and_avatar_changes(
3538 context,
3539 mime_parser,
3540 from_id,
3541 chat,
3542 &mut send_event_chat_modified,
3543 &mut better_msg,
3544 )
3545 .await?;
3546 }
3547
3548 if let Some(added_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAddedFpr) {
3549 if from_id == ContactId::SELF {
3550 let added_id = lookup_key_contact_by_fingerprint(context, added_fpr).await?;
3551 if let Some(added_id) = added_id {
3552 if chat::is_contact_in_chat(context, chat.id, added_id).await? {
3553 info!(context, "No-op broadcast addition (TRASH)");
3554 better_msg.get_or_insert("".to_string());
3555 } else {
3556 chat::add_to_chat_contacts_table(
3557 context,
3558 mime_parser.timestamp_sent,
3559 chat.id,
3560 &[added_id],
3561 )
3562 .await?;
3563 let msg =
3564 stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED)
3565 .await;
3566 better_msg.get_or_insert(msg);
3567 send_event_chat_modified = true;
3568 }
3569 } else {
3570 warn!(context, "Failed to find contact with fpr {added_fpr}");
3571 }
3572 }
3573 } else if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3574 send_event_chat_modified = true;
3575 let removed_id = lookup_key_contact_by_fingerprint(context, removed_fpr).await?;
3576 if removed_id == Some(from_id) {
3577 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, from_id).await?;
3580 info!(context, "Broadcast leave message (TRASH)");
3581 better_msg = Some("".to_string());
3582 } else if from_id == ContactId::SELF
3583 && let Some(removed_id) = removed_id
3584 {
3585 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
3586 .await?;
3587
3588 better_msg.get_or_insert(
3589 stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
3590 );
3591 }
3592 }
3593
3594 if send_event_chat_modified {
3595 context.emit_event(EventType::ChatModified(chat.id));
3596 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3597 }
3598 Ok(GroupChangesInfo {
3599 better_msg,
3600 added_removed_id: None,
3601 silent: false,
3602 extra_msgs: vec![],
3603 })
3604}
3605
3606async fn apply_in_broadcast_changes(
3607 context: &Context,
3608 mime_parser: &MimeMessage,
3609 chat: &mut Chat,
3610 from_id: ContactId,
3611) -> Result<GroupChangesInfo> {
3612 ensure!(chat.typ == Chattype::InBroadcast);
3613
3614 if let Some(part) = mime_parser.parts.first()
3615 && let Some(error) = &part.error
3616 {
3617 warn!(
3618 context,
3619 "Not applying broadcast changes from message with error: {error}"
3620 );
3621 return Ok(GroupChangesInfo::default());
3622 }
3623
3624 let mut send_event_chat_modified = false;
3625 let mut better_msg = None;
3626
3627 apply_chat_name_and_avatar_changes(
3628 context,
3629 mime_parser,
3630 from_id,
3631 chat,
3632 &mut send_event_chat_modified,
3633 &mut better_msg,
3634 )
3635 .await?;
3636
3637 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded)
3638 && context.is_self_addr(added_addr).await?
3639 {
3640 let msg = if chat.is_self_in_chat(context).await? {
3641 info!(context, "No-op broadcast 'Member added' message (TRASH)");
3645 "".to_string()
3646 } else {
3647 stock_str::msg_you_joined_broadcast(context).await
3648 };
3649
3650 better_msg.get_or_insert(msg);
3651 send_event_chat_modified = true;
3652 }
3653
3654 if let Some(removed_fpr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemovedFpr) {
3655 if removed_fpr != self_fingerprint(context).await? {
3657 logged_debug_assert!(context, false, "Ignoring unexpected removal message");
3658 return Ok(GroupChangesInfo::default());
3659 }
3660 chat::delete_broadcast_secret(context, chat.id).await?;
3661
3662 if from_id == ContactId::SELF {
3663 better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
3664 } else {
3665 better_msg.get_or_insert(
3666 stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
3667 );
3668 }
3669
3670 chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
3671 .await?;
3672 send_event_chat_modified = true;
3673 } else if !chat.is_self_in_chat(context).await? {
3674 chat::add_to_chat_contacts_table(
3675 context,
3676 mime_parser.timestamp_sent,
3677 chat.id,
3678 &[ContactId::SELF],
3679 )
3680 .await?;
3681 send_event_chat_modified = true;
3682 }
3683
3684 if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
3685 if validate_broadcast_secret(secret) {
3686 save_broadcast_secret(context, chat.id, secret).await?;
3687 } else {
3688 warn!(context, "Not saving invalid broadcast secret");
3689 }
3690 }
3691
3692 if send_event_chat_modified {
3693 context.emit_event(EventType::ChatModified(chat.id));
3694 chatlist_events::emit_chatlist_item_changed(context, chat.id);
3695 }
3696 Ok(GroupChangesInfo {
3697 better_msg,
3698 added_removed_id: None,
3699 silent: false,
3700 extra_msgs: vec![],
3701 })
3702}
3703
3704async fn create_adhoc_group(
3706 context: &Context,
3707 mime_parser: &MimeMessage,
3708 create_blocked: Blocked,
3709 from_id: ContactId,
3710 to_ids: &[ContactId],
3711 grpname: &str,
3712) -> Result<Option<(ChatId, Blocked)>> {
3713 let mut member_ids: Vec<ContactId> = to_ids.to_vec();
3714 if !member_ids.contains(&(from_id)) {
3715 member_ids.push(from_id);
3716 }
3717 if !member_ids.contains(&(ContactId::SELF)) {
3718 member_ids.push(ContactId::SELF);
3719 }
3720
3721 if mime_parser.is_mailinglist_message() {
3722 return Ok(None);
3723 }
3724 if mime_parser
3725 .get_header(HeaderDef::ChatGroupMemberRemoved)
3726 .is_some()
3727 {
3728 info!(
3729 context,
3730 "Message removes member from unknown ad-hoc group (TRASH)."
3731 );
3732 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
3733 }
3734
3735 let new_chat_id: ChatId = ChatId::create_multiuser_record(
3736 context,
3737 Chattype::Group,
3738 "", grpname,
3740 create_blocked,
3741 None,
3742 mime_parser.timestamp_sent,
3743 )
3744 .await?;
3745
3746 info!(
3747 context,
3748 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
3749 );
3750 chat::add_to_chat_contacts_table(
3751 context,
3752 mime_parser.timestamp_sent,
3753 new_chat_id,
3754 &member_ids,
3755 )
3756 .await?;
3757
3758 context.emit_event(EventType::ChatModified(new_chat_id));
3759 chatlist_events::emit_chatlist_changed(context);
3760 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
3761
3762 Ok(Some((new_chat_id, create_blocked)))
3763}
3764
3765#[derive(Debug, PartialEq, Eq)]
3766enum VerifiedEncryption {
3767 Verified,
3768 NotVerified(String), }
3770
3771async fn has_verified_encryption(
3775 context: &Context,
3776 mimeparser: &MimeMessage,
3777 from_id: ContactId,
3778) -> Result<VerifiedEncryption> {
3779 use VerifiedEncryption::*;
3780
3781 if !mimeparser.was_encrypted() {
3782 return Ok(NotVerified("This message is not encrypted".to_string()));
3783 };
3784
3785 if from_id == ContactId::SELF {
3786 return Ok(Verified);
3787 }
3788
3789 let from_contact = Contact::get_by_id(context, from_id).await?;
3790
3791 let Some(fingerprint) = from_contact.fingerprint() else {
3792 return Ok(NotVerified(
3793 "The message was sent without encryption".to_string(),
3794 ));
3795 };
3796
3797 if from_contact.get_verifier_id(context).await?.is_none() {
3798 return Ok(NotVerified(
3799 "The message was sent by non-verified contact".to_string(),
3800 ));
3801 }
3802
3803 let signed_with_verified_key = mimeparser
3804 .signature
3805 .as_ref()
3806 .is_some_and(|signature| *signature == fingerprint);
3807 if signed_with_verified_key {
3808 Ok(Verified)
3809 } else {
3810 Ok(NotVerified(
3811 "The message was sent with non-verified encryption".to_string(),
3812 ))
3813 }
3814}
3815
3816async fn mark_recipients_as_verified(
3817 context: &Context,
3818 from_id: ContactId,
3819 mimeparser: &MimeMessage,
3820) -> Result<()> {
3821 let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
3822
3823 let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some();
3827
3828 for gossiped_key in mimeparser
3829 .gossiped_keys
3830 .values()
3831 .filter(|gossiped_key| gossiped_key.verified || chat_verified)
3832 {
3833 let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
3834 let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
3835 continue;
3836 };
3837
3838 if to_id == ContactId::SELF || to_id == from_id {
3839 continue;
3840 }
3841
3842 mark_contact_id_as_verified(context, to_id, verifier_id).await?;
3843 }
3844
3845 Ok(())
3846}
3847
3848async fn get_previous_message(
3852 context: &Context,
3853 mime_parser: &MimeMessage,
3854) -> Result<Option<Message>> {
3855 if let Some(field) = mime_parser.get_header(HeaderDef::References)
3856 && let Some(rfc724mid) = parse_message_ids(field).last()
3857 && let Some(msg_id) = rfc724_mid_exists(context, rfc724mid).await?
3858 {
3859 return Message::load_from_db_optional(context, msg_id).await;
3860 }
3861 Ok(None)
3862}
3863
3864async fn get_parent_message(
3869 context: &Context,
3870 references: Option<&str>,
3871 in_reply_to: Option<&str>,
3872) -> Result<Option<Message>> {
3873 let mut mids = Vec::new();
3874 if let Some(field) = in_reply_to {
3875 mids = parse_message_ids(field);
3876 }
3877 if let Some(field) = references {
3878 mids.append(&mut parse_message_ids(field));
3879 }
3880 message::get_by_rfc724_mids(context, &mids).await
3881}
3882
3883pub(crate) async fn get_prefetch_parent_message(
3884 context: &Context,
3885 headers: &[mailparse::MailHeader<'_>],
3886) -> Result<Option<Message>> {
3887 get_parent_message(
3888 context,
3889 headers.get_header_value(HeaderDef::References).as_deref(),
3890 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
3891 )
3892 .await
3893}
3894
3895async fn add_or_lookup_contacts_by_address_list(
3897 context: &Context,
3898 address_list: &[SingleInfo],
3899 origin: Origin,
3900) -> Result<Vec<Option<ContactId>>> {
3901 let mut contact_ids = Vec::new();
3902 for info in address_list {
3903 let addr = &info.addr;
3904 if !may_be_valid_addr(addr) {
3905 contact_ids.push(None);
3906 continue;
3907 }
3908 let display_name = info.display_name.as_deref();
3909 if let Ok(addr) = ContactAddress::new(addr) {
3910 let (contact_id, _) =
3911 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
3912 .await?;
3913 contact_ids.push(Some(contact_id));
3914 } else {
3915 warn!(context, "Contact with address {:?} cannot exist.", addr);
3916 contact_ids.push(None);
3917 }
3918 }
3919
3920 Ok(contact_ids)
3921}
3922
3923async fn add_or_lookup_key_contacts(
3925 context: &Context,
3926 address_list: &[SingleInfo],
3927 gossiped_keys: &BTreeMap<String, GossipedKey>,
3928 fingerprints: &[Fingerprint],
3929 origin: Origin,
3930) -> Result<Vec<Option<ContactId>>> {
3931 let mut contact_ids = Vec::new();
3932 let mut fingerprint_iter = fingerprints.iter();
3933 for info in address_list {
3934 let addr = &info.addr;
3935 if !may_be_valid_addr(addr) {
3936 contact_ids.push(None);
3937 continue;
3938 }
3939 let fingerprint: String = if let Some(fp) = fingerprint_iter.next() {
3940 fp.hex()
3942 } else if let Some(key) = gossiped_keys.get(addr) {
3943 key.public_key.dc_fingerprint().hex()
3944 } else if context.is_self_addr(addr).await? {
3945 contact_ids.push(Some(ContactId::SELF));
3946 continue;
3947 } else {
3948 contact_ids.push(None);
3949 continue;
3950 };
3951 let display_name = info.display_name.as_deref();
3952 if let Ok(addr) = ContactAddress::new(addr) {
3953 let (contact_id, _) = Contact::add_or_lookup_ex(
3954 context,
3955 display_name.unwrap_or_default(),
3956 &addr,
3957 &fingerprint,
3958 origin,
3959 )
3960 .await?;
3961 contact_ids.push(Some(contact_id));
3962 } else {
3963 warn!(context, "Contact with address {:?} cannot exist.", addr);
3964 contact_ids.push(None);
3965 }
3966 }
3967
3968 ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
3969 Ok(contact_ids)
3970}
3971
3972async fn lookup_key_contact_by_address(
3977 context: &Context,
3978 addr: &str,
3979 chat_id: Option<ChatId>,
3980) -> Result<Option<ContactId>> {
3981 if context.is_self_addr(addr).await? {
3982 if chat_id.is_none() {
3983 return Ok(Some(ContactId::SELF));
3984 }
3985 let is_self_in_chat = context
3986 .sql
3987 .exists(
3988 "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=? AND contact_id=1",
3989 (chat_id,),
3990 )
3991 .await?;
3992 if is_self_in_chat {
3993 return Ok(Some(ContactId::SELF));
3994 }
3995 }
3996 let contact_id: Option<ContactId> = match chat_id {
3997 Some(chat_id) => {
3998 context
3999 .sql
4000 .query_row_optional(
4001 "SELECT id FROM contacts
4002 WHERE contacts.addr=?
4003 AND EXISTS (SELECT 1 FROM chats_contacts
4004 WHERE contact_id=contacts.id
4005 AND chat_id=?)
4006 AND fingerprint<>'' -- Should always be true
4007 ",
4008 (addr, chat_id),
4009 |row| {
4010 let contact_id: ContactId = row.get(0)?;
4011 Ok(contact_id)
4012 },
4013 )
4014 .await?
4015 }
4016 None => {
4017 context
4018 .sql
4019 .query_row_optional(
4020 "SELECT id FROM contacts
4021 WHERE addr=?
4022 AND fingerprint<>''
4023 ORDER BY
4024 (
4025 SELECT COUNT(*) FROM chats c
4026 INNER JOIN chats_contacts cc
4027 ON c.id=cc.chat_id
4028 WHERE c.type=?
4029 AND c.id>?
4030 AND c.blocked=?
4031 AND cc.contact_id=contacts.id
4032 ) DESC,
4033 last_seen DESC, id DESC
4034 ",
4035 (
4036 addr,
4037 Chattype::Single,
4038 constants::DC_CHAT_ID_LAST_SPECIAL,
4039 Blocked::Not,
4040 ),
4041 |row| {
4042 let contact_id: ContactId = row.get(0)?;
4043 Ok(contact_id)
4044 },
4045 )
4046 .await?
4047 }
4048 };
4049 Ok(contact_id)
4050}
4051
4052async fn lookup_key_contact_by_fingerprint(
4053 context: &Context,
4054 fingerprint: &str,
4055) -> Result<Option<ContactId>> {
4056 logged_debug_assert!(
4057 context,
4058 !fingerprint.is_empty(),
4059 "lookup_key_contact_by_fingerprint: fingerprint is empty."
4060 );
4061 if fingerprint.is_empty() {
4062 return Ok(None);
4064 }
4065 if let Some(contact_id) = context
4066 .sql
4067 .query_row_optional(
4068 "SELECT id FROM contacts
4069 WHERE fingerprint=? AND fingerprint!=''",
4070 (fingerprint,),
4071 |row| {
4072 let contact_id: ContactId = row.get(0)?;
4073 Ok(contact_id)
4074 },
4075 )
4076 .await?
4077 {
4078 Ok(Some(contact_id))
4079 } else if let Some(self_fp) = self_fingerprint_opt(context).await? {
4080 if self_fp == fingerprint {
4081 Ok(Some(ContactId::SELF))
4082 } else {
4083 Ok(None)
4084 }
4085 } else {
4086 Ok(None)
4087 }
4088}
4089
4090async fn lookup_key_contacts_by_address_list(
4106 context: &Context,
4107 address_list: &[SingleInfo],
4108 fingerprints: &[Fingerprint],
4109 chat_id: Option<ChatId>,
4110) -> Result<Vec<Option<ContactId>>> {
4111 let mut contact_ids = Vec::new();
4112 let mut fingerprint_iter = fingerprints.iter();
4113 for info in address_list {
4114 let addr = &info.addr;
4115 if !may_be_valid_addr(addr) {
4116 contact_ids.push(None);
4117 continue;
4118 }
4119
4120 if let Some(fp) = fingerprint_iter.next() {
4121 let display_name = info.display_name.as_deref();
4123 let fingerprint: String = fp.hex();
4124
4125 if let Ok(addr) = ContactAddress::new(addr) {
4126 let (contact_id, _) = Contact::add_or_lookup_ex(
4127 context,
4128 display_name.unwrap_or_default(),
4129 &addr,
4130 &fingerprint,
4131 Origin::Hidden,
4132 )
4133 .await?;
4134 contact_ids.push(Some(contact_id));
4135 } else {
4136 warn!(context, "Contact with address {:?} cannot exist.", addr);
4137 contact_ids.push(None);
4138 }
4139 } else {
4140 let contact_id = lookup_key_contact_by_address(context, addr, chat_id).await?;
4141 contact_ids.push(contact_id);
4142 }
4143 }
4144 ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
4145 Ok(contact_ids)
4146}
4147
4148fn should_prevent_rename(mime_parser: &MimeMessage) -> bool {
4151 (mime_parser.is_mailinglist_message() && !mime_parser.was_encrypted())
4152 || mime_parser.get_header(HeaderDef::Sender).is_some()
4153}
4154
4155#[cfg(test)]
4156mod receive_imf_tests;