1use std::collections::HashSet;
4use std::iter;
5use std::sync::LazyLock;
6
7use anyhow::{Context as _, Result};
8use data_encoding::BASE32_NOPAD;
9use deltachat_contact_tools::{addr_cmp, may_be_valid_addr, sanitize_single_line, ContactAddress};
10use iroh_gossip::proto::TopicId;
11use mailparse::SingleInfo;
12use num_traits::FromPrimitive;
13use regex::Regex;
14
15use crate::aheader::EncryptPreference;
16use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
17use crate::config::Config;
18use crate::constants::{Blocked, Chattype, ShowEmails, DC_CHAT_ID_TRASH, EDITED_PREFIX};
19use crate::contact::{Contact, ContactId, Origin};
20use crate::context::Context;
21use crate::debug_logging::maybe_set_logging_xdc_inner;
22use crate::download::DownloadState;
23use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
24use crate::events::EventType;
25use crate::headerdef::{HeaderDef, HeaderDefMap};
26use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
27use crate::key::DcKey;
28use crate::log::LogExt;
29use crate::message::{
30 self, rfc724_mid_exists, Message, MessageState, MessengerMessage, MsgId, Viewtype,
31};
32use crate::mimeparser::{parse_message_ids, AvatarAction, MimeMessage, SystemMessage};
33use crate::param::{Param, Params};
34use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub};
35use crate::peerstate::Peerstate;
36use crate::reaction::{set_msg_reaction, Reaction};
37use crate::rusqlite::OptionalExtension;
38use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on_other_device};
39use crate::simplify;
40use crate::stock_str;
41use crate::sync::Sync::*;
42use crate::tools::{self, buf_compress, remove_subject_prefix};
43use crate::{chatlist_events, location};
44use crate::{contact, imap};
45
46#[derive(Debug)]
51pub struct ReceivedMsg {
52 pub chat_id: ChatId,
54
55 pub state: MessageState,
57
58 pub hidden: bool,
60
61 pub sort_timestamp: i64,
63
64 pub msg_ids: Vec<MsgId>,
66
67 pub needs_delete_job: bool,
69
70 #[cfg(test)]
73 pub(crate) from_is_signed: bool,
74}
75
76#[cfg(any(test, feature = "internals"))]
81pub async fn receive_imf(
82 context: &Context,
83 imf_raw: &[u8],
84 seen: bool,
85) -> Result<Option<ReceivedMsg>> {
86 let mail = mailparse::parse_mail(imf_raw).context("can't parse mail")?;
87 let rfc724_mid =
88 imap::prefetch_get_message_id(&mail.headers).unwrap_or_else(imap::create_message_id);
89 if let Some(download_limit) = context.download_limit().await? {
90 let download_limit: usize = download_limit.try_into()?;
91 if imf_raw.len() > download_limit {
92 let head = std::str::from_utf8(imf_raw)?
93 .split("\r\n\r\n")
94 .next()
95 .context("No empty line in the message")?;
96 return receive_imf_from_inbox(
97 context,
98 &rfc724_mid,
99 head.as_bytes(),
100 seen,
101 Some(imf_raw.len().try_into()?),
102 )
103 .await;
104 }
105 }
106 receive_imf_from_inbox(context, &rfc724_mid, imf_raw, seen, None).await
107}
108
109#[cfg(any(test, feature = "internals"))]
113pub(crate) async fn receive_imf_from_inbox(
114 context: &Context,
115 rfc724_mid: &str,
116 imf_raw: &[u8],
117 seen: bool,
118 is_partial_download: Option<u32>,
119) -> Result<Option<ReceivedMsg>> {
120 receive_imf_inner(
121 context,
122 "INBOX",
123 0,
124 0,
125 rfc724_mid,
126 imf_raw,
127 seen,
128 is_partial_download,
129 )
130 .await
131}
132
133async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
138 let row_id = context
139 .sql
140 .insert(
141 "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
142 (rfc724_mid, DC_CHAT_ID_TRASH),
143 )
144 .await?;
145 let msg_id = MsgId::new(u32::try_from(row_id)?);
146 Ok(msg_id)
147}
148
149#[expect(clippy::too_many_arguments)]
163pub(crate) async fn receive_imf_inner(
164 context: &Context,
165 folder: &str,
166 uidvalidity: u32,
167 uid: u32,
168 rfc724_mid: &str,
169 imf_raw: &[u8],
170 seen: bool,
171 is_partial_download: Option<u32>,
172) -> Result<Option<ReceivedMsg>> {
173 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
174 info!(
175 context,
176 "receive_imf: incoming message mime-body:\n{}",
177 String::from_utf8_lossy(imf_raw),
178 );
179 }
180
181 let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw, is_partial_download).await
182 {
183 Err(err) => {
184 warn!(context, "receive_imf: can't parse MIME: {err:#}.");
185 if rfc724_mid.starts_with(GENERATED_PREFIX) {
186 return Ok(None);
188 }
189
190 let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
191
192 return Ok(Some(ReceivedMsg {
193 chat_id: DC_CHAT_ID_TRASH,
194 state: MessageState::Undefined,
195 hidden: false,
196 sort_timestamp: 0,
197 msg_ids,
198 needs_delete_job: false,
199 #[cfg(test)]
200 from_is_signed: false,
201 }));
202 }
203 Ok(mime_parser) => mime_parser,
204 };
205
206 crate::peerstate::maybe_do_aeap_transition(context, &mut mime_parser).await?;
207 if let Some(peerstate) = &mime_parser.peerstate {
208 peerstate
209 .handle_fingerprint_change(context, mime_parser.timestamp_sent)
210 .await?;
211 if peerstate.prefer_encrypt != EncryptPreference::Mutual {
216 peerstate.save_to_db(&context.sql).await?;
217 }
218 }
219
220 let rfc724_mid_orig = &mime_parser
221 .get_rfc724_mid()
222 .unwrap_or(rfc724_mid.to_string());
223 info!(
224 context,
225 "Receiving message {rfc724_mid_orig:?}, seen={seen}...",
226 );
227
228 let (replace_msg_id, replace_chat_id);
231 if let Some((old_msg_id, _)) = message::rfc724_mid_exists(context, rfc724_mid).await? {
232 if is_partial_download.is_some() {
233 info!(
235 context,
236 "Got a partial download and message is already in DB."
237 );
238 return Ok(None);
239 }
240 let msg = Message::load_from_db(context, old_msg_id).await?;
241 replace_msg_id = Some(old_msg_id);
242 replace_chat_id = if msg.download_state() != DownloadState::Done {
243 info!(
245 context,
246 "Message already partly in DB, replacing by full message."
247 );
248 Some(msg.chat_id)
249 } else {
250 None
251 };
252 } else {
253 replace_msg_id = if rfc724_mid_orig == rfc724_mid {
254 None
255 } else if let Some((old_msg_id, old_ts_sent)) =
256 message::rfc724_mid_exists(context, rfc724_mid_orig).await?
257 {
258 if imap::is_dup_msg(
259 mime_parser.has_chat_version(),
260 mime_parser.timestamp_sent,
261 old_ts_sent,
262 ) {
263 info!(context, "Deleting duplicate message {rfc724_mid_orig}.");
264 let target = context.get_delete_msgs_target().await?;
265 context
266 .sql
267 .execute(
268 "UPDATE imap SET target=? WHERE folder=? AND uidvalidity=? AND uid=?",
269 (target, folder, uidvalidity, uid),
270 )
271 .await?;
272 }
273 Some(old_msg_id)
274 } else {
275 None
276 };
277 replace_chat_id = None;
278 }
279
280 if replace_chat_id.is_some() {
281 } else if let Some(msg_id) = replace_msg_id {
283 info!(context, "Message is already downloaded.");
284 if mime_parser.incoming {
285 return Ok(None);
286 }
287 let self_addr = context.get_primary_self_addr().await?;
290 context
291 .sql
292 .execute(
293 "DELETE FROM smtp \
294 WHERE rfc724_mid=?1 AND (recipients LIKE ?2 OR recipients LIKE ('% ' || ?2))",
295 (rfc724_mid_orig, &self_addr),
296 )
297 .await?;
298 if !context
299 .sql
300 .exists(
301 "SELECT COUNT(*) FROM smtp WHERE rfc724_mid=?",
302 (rfc724_mid_orig,),
303 )
304 .await?
305 {
306 msg_id.set_delivered(context).await?;
307 }
308 return Ok(None);
309 };
310
311 let prevent_rename =
312 mime_parser.is_mailinglist_message() || mime_parser.get_header(HeaderDef::Sender).is_some();
313
314 let (from_id, _from_id_blocked, incoming_origin) =
326 match from_field_to_contact_id(context, &mime_parser.from, prevent_rename).await? {
327 Some(contact_id_res) => contact_id_res,
328 None => {
329 warn!(
330 context,
331 "receive_imf: From field does not contain an acceptable address."
332 );
333 return Ok(None);
334 }
335 };
336
337 let to_ids = add_or_lookup_contacts_by_address_list(
338 context,
339 &mime_parser.recipients,
340 if !mime_parser.incoming {
341 Origin::OutgoingTo
342 } else if incoming_origin.is_known() {
343 Origin::IncomingTo
344 } else {
345 Origin::IncomingUnknownTo
346 },
347 )
348 .await?;
349 let past_ids = add_or_lookup_contacts_by_address_list(
350 context,
351 &mime_parser.past_members,
352 if !mime_parser.incoming {
353 Origin::OutgoingTo
354 } else if incoming_origin.is_known() {
355 Origin::IncomingTo
356 } else {
357 Origin::IncomingUnknownTo
358 },
359 )
360 .await?;
361
362 update_verified_keys(context, &mut mime_parser, from_id).await?;
363
364 let received_msg;
365 if mime_parser.get_header(HeaderDef::SecureJoin).is_some() {
366 let res;
367 if mime_parser.incoming {
368 res = handle_securejoin_handshake(context, &mut mime_parser, from_id)
369 .await
370 .context("error in Secure-Join message handling")?;
371
372 let contact = Contact::get_by_id(context, from_id).await?;
374 mime_parser.peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
375 } else {
376 let to_id = to_ids.first().copied().unwrap_or(ContactId::SELF);
377 res = observe_securejoin_on_other_device(context, &mime_parser, to_id)
379 .await
380 .context("error in Secure-Join watching")?
381 }
382
383 match res {
384 securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
385 let msg_id = insert_tombstone(context, rfc724_mid).await?;
386 received_msg = Some(ReceivedMsg {
387 chat_id: DC_CHAT_ID_TRASH,
388 state: MessageState::InSeen,
389 hidden: false,
390 sort_timestamp: mime_parser.timestamp_sent,
391 msg_ids: vec![msg_id],
392 needs_delete_job: res == securejoin::HandshakeMessage::Done,
393 #[cfg(test)]
394 from_is_signed: mime_parser.from_is_signed,
395 });
396 }
397 securejoin::HandshakeMessage::Propagate => {
398 received_msg = None;
399 }
400 }
401 } else {
402 received_msg = None;
403 }
404
405 let verified_encryption = has_verified_encryption(&mime_parser, from_id)?;
406
407 if verified_encryption == VerifiedEncryption::Verified {
408 mark_recipients_as_verified(context, from_id, &to_ids, &mime_parser).await?;
409 }
410
411 if verified_encryption == VerifiedEncryption::Verified
412 && mime_parser.get_header(HeaderDef::ChatVerified).is_some()
413 {
414 if let Some(peerstate) = &mut mime_parser.peerstate {
415 peerstate.backward_verified_key_id =
424 Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
425 peerstate.save_to_db(&context.sql).await?;
426 }
427 }
428
429 let received_msg = if let Some(received_msg) = received_msg {
430 received_msg
431 } else {
432 add_parts(
434 context,
435 &mut mime_parser,
436 imf_raw,
437 &to_ids,
438 &past_ids,
439 rfc724_mid_orig,
440 from_id,
441 seen,
442 is_partial_download,
443 replace_msg_id,
444 prevent_rename,
445 verified_encryption,
446 )
447 .await
448 .context("add_parts error")?
449 };
450
451 if !from_id.is_special() {
452 contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
453 }
454
455 let chat_id = received_msg.chat_id;
459 if !chat_id.is_special() {
460 for gossiped_key in mime_parser.gossiped_keys.values() {
461 context
462 .sql
463 .transaction(move |transaction| {
464 let fingerprint = gossiped_key.dc_fingerprint().hex();
465 transaction.execute(
466 "INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
467 VALUES (?, ?, ?)
468 ON CONFLICT (chat_id, fingerprint)
469 DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
470 (chat_id, &fingerprint, mime_parser.timestamp_sent),
471 )?;
472
473 Ok(())
474 })
475 .await?;
476 }
477 }
478
479 let insert_msg_id = if let Some(msg_id) = received_msg.msg_ids.last() {
480 *msg_id
481 } else {
482 MsgId::new_unset()
483 };
484
485 save_locations(context, &mime_parser, chat_id, from_id, insert_msg_id).await?;
486
487 if let Some(ref sync_items) = mime_parser.sync_items {
488 if from_id == ContactId::SELF {
489 if mime_parser.was_encrypted() {
490 context.execute_sync_items(sync_items).await;
491 } else {
492 warn!(context, "Sync items are not encrypted.");
493 }
494 } else {
495 warn!(context, "Sync items not sent by self.");
496 }
497 }
498
499 if let Some(ref status_update) = mime_parser.webxdc_status_update {
500 let can_info_msg;
501 let instance = if mime_parser
502 .parts
503 .first()
504 .filter(|part| part.typ == Viewtype::Webxdc)
505 .is_some()
506 {
507 can_info_msg = false;
508 Some(Message::load_from_db(context, insert_msg_id).await?)
509 } else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
510 if let Some(instance) =
511 message::get_by_rfc724_mids(context, &parse_message_ids(field)).await?
512 {
513 can_info_msg = instance.download_state() == DownloadState::Done;
514 Some(instance)
515 } else {
516 can_info_msg = false;
517 None
518 }
519 } else {
520 can_info_msg = false;
521 None
522 };
523
524 if let Some(instance) = instance {
525 if let Err(err) = context
526 .receive_status_update(
527 from_id,
528 &instance,
529 received_msg.sort_timestamp,
530 can_info_msg,
531 status_update,
532 )
533 .await
534 {
535 warn!(context, "receive_imf cannot update status: {err:#}.");
536 }
537 } else {
538 warn!(
539 context,
540 "Received webxdc update, but cannot assign it to message."
541 );
542 }
543 }
544
545 if let Some(avatar_action) = &mime_parser.user_avatar {
546 if from_id != ContactId::UNDEFINED
547 && context
548 .update_contacts_timestamp(
549 from_id,
550 Param::AvatarTimestamp,
551 mime_parser.timestamp_sent,
552 )
553 .await?
554 {
555 if let Err(err) = contact::set_profile_image(
556 context,
557 from_id,
558 avatar_action,
559 mime_parser.was_encrypted(),
560 )
561 .await
562 {
563 warn!(context, "receive_imf cannot update profile image: {err:#}.");
564 };
565 }
566 }
567
568 if let Some(footer) = &mime_parser.footer {
570 if !mime_parser.is_mailinglist_message()
571 && from_id != ContactId::UNDEFINED
572 && context
573 .update_contacts_timestamp(
574 from_id,
575 Param::StatusTimestamp,
576 mime_parser.timestamp_sent,
577 )
578 .await?
579 {
580 if let Err(err) = contact::set_status(
581 context,
582 from_id,
583 footer.to_string(),
584 mime_parser.was_encrypted(),
585 mime_parser.has_chat_version(),
586 )
587 .await
588 {
589 warn!(context, "Cannot update contact status: {err:#}.");
590 }
591 }
592 }
593
594 let delete_server_after = context.get_config_delete_server_after().await?;
596
597 if !received_msg.msg_ids.is_empty() {
598 let target = if received_msg.needs_delete_job
599 || (delete_server_after == Some(0) && is_partial_download.is_none())
600 {
601 Some(context.get_delete_msgs_target().await?)
602 } else {
603 None
604 };
605 if target.is_some() || rfc724_mid_orig != rfc724_mid {
606 let target_subst = match &target {
607 Some(_) => "target=?1,",
608 None => "",
609 };
610 context
611 .sql
612 .execute(
613 &format!("UPDATE imap SET {target_subst} rfc724_mid=?2 WHERE rfc724_mid=?3"),
614 (
615 target.as_deref().unwrap_or_default(),
616 rfc724_mid_orig,
617 rfc724_mid,
618 ),
619 )
620 .await?;
621 }
622 if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
623 {
624 markseen_on_imap_table(context, rfc724_mid_orig).await?;
626 }
627 }
628
629 if received_msg.hidden {
630 } else if let Some(replace_chat_id) = replace_chat_id {
632 context.emit_msgs_changed_without_msg_id(replace_chat_id);
633 } else if !chat_id.is_trash() {
634 let fresh = received_msg.state == MessageState::InFresh;
635 for msg_id in &received_msg.msg_ids {
636 chat_id.emit_msg_event(context, *msg_id, mime_parser.incoming && fresh);
637 }
638 }
639 context.new_msgs_notify.notify_one();
640
641 mime_parser
642 .handle_reports(context, from_id, &mime_parser.parts)
643 .await;
644
645 if let Some(is_bot) = mime_parser.is_bot {
646 if mime_parser.get_header(HeaderDef::ChatVersion).is_some() {
649 from_id.mark_bot(context, is_bot).await?;
650 }
651 }
652
653 Ok(Some(received_msg))
654}
655
656pub async fn from_field_to_contact_id(
667 context: &Context,
668 from: &SingleInfo,
669 prevent_rename: bool,
670) -> Result<Option<(ContactId, bool, Origin)>> {
671 let display_name = if prevent_rename {
672 Some("")
673 } else {
674 from.display_name.as_deref()
675 };
676 let from_addr = match ContactAddress::new(&from.addr) {
677 Ok(from_addr) => from_addr,
678 Err(err) => {
679 warn!(
680 context,
681 "Cannot create a contact for the given From field: {err:#}."
682 );
683 return Ok(None);
684 }
685 };
686
687 let (from_id, _) = Contact::add_or_lookup(
688 context,
689 display_name.unwrap_or_default(),
690 &from_addr,
691 Origin::IncomingUnknownFrom,
692 )
693 .await?;
694
695 if from_id == ContactId::SELF {
696 Ok(Some((ContactId::SELF, false, Origin::OutgoingBcc)))
697 } else {
698 let contact = Contact::get_by_id(context, from_id).await?;
699 let from_id_blocked = contact.blocked;
700 let incoming_origin = contact.origin;
701 Ok(Some((from_id, from_id_blocked, incoming_origin)))
702 }
703}
704
705#[expect(clippy::too_many_arguments)]
709async fn add_parts(
710 context: &Context,
711 mime_parser: &mut MimeMessage,
712 imf_raw: &[u8],
713 to_ids: &[ContactId],
714 past_ids: &[ContactId],
715 rfc724_mid: &str,
716 from_id: ContactId,
717 seen: bool,
718 is_partial_download: Option<u32>,
719 mut replace_msg_id: Option<MsgId>,
720 prevent_rename: bool,
721 verified_encryption: VerifiedEncryption,
722) -> Result<ReceivedMsg> {
723 let is_bot = context.get_config_bool(Config::Bot).await?;
724 let rfc724_mid_orig = &mime_parser
725 .get_rfc724_mid()
726 .unwrap_or(rfc724_mid.to_string());
727
728 let mut chat_id = None;
729 let mut chat_id_blocked = Blocked::Not;
730
731 let mut better_msg = None;
732 let mut group_changes = GroupChangesInfo::default();
733 if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
734 better_msg = Some(stock_str::msg_location_enabled_by(context, from_id).await);
735 }
736
737 let parent = get_parent_message(
738 context,
739 mime_parser.get_header(HeaderDef::References),
740 mime_parser.get_header(HeaderDef::InReplyTo),
741 )
742 .await?
743 .filter(|p| Some(p.id) != replace_msg_id);
744
745 let is_dc_message = if mime_parser.has_chat_version() {
746 MessengerMessage::Yes
747 } else if let Some(parent) = &parent {
748 match parent.is_dc_message {
749 MessengerMessage::No => MessengerMessage::No,
750 MessengerMessage::Yes | MessengerMessage::Reply => MessengerMessage::Reply,
751 }
752 } else {
753 MessengerMessage::No
754 };
755 let is_location_kml = mime_parser.location_kml.is_some();
758 let is_mdn = !mime_parser.mdn_reports.is_empty();
759 let is_reaction = mime_parser.parts.iter().any(|part| part.is_reaction);
760 let show_emails =
761 ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?).unwrap_or_default();
762
763 let allow_creation;
764 if mime_parser.decrypting_failed {
765 allow_creation = false;
766 } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
767 && is_dc_message == MessengerMessage::No
768 && !context.get_config_bool(Config::IsChatmail).await?
769 {
770 match show_emails {
773 ShowEmails::Off => {
774 info!(context, "Classical email not shown (TRASH).");
775 chat_id = Some(DC_CHAT_ID_TRASH);
776 allow_creation = false;
777 }
778 ShowEmails::AcceptedContacts => allow_creation = false,
779 ShowEmails::All => allow_creation = !is_mdn,
780 }
781 } else {
782 allow_creation = !is_mdn && !is_reaction;
783 }
784
785 let to_id: ContactId;
790 let state: MessageState;
791 let mut hidden = is_reaction;
792 let mut needs_delete_job = false;
793 let mut restore_protection = false;
794
795 if prevent_rename {
798 if let Some(name) = &mime_parser.from.display_name {
799 for part in &mut mime_parser.parts {
800 part.param.set(Param::OverrideSenderDisplayname, name);
801 }
802 }
803 }
804
805 if chat_id.is_none() && is_mdn {
806 chat_id = Some(DC_CHAT_ID_TRASH);
807 info!(context, "Message is an MDN (TRASH).",);
808 }
809
810 if mime_parser.incoming {
811 to_id = ContactId::SELF;
812
813 let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
814
815 if chat_id.is_none() && mime_parser.delivery_report.is_some() {
816 chat_id = Some(DC_CHAT_ID_TRASH);
817 info!(context, "Message is a DSN (TRASH).",);
818 markseen_on_imap_table(context, rfc724_mid).await.ok();
819 }
820
821 let create_blocked_default = if is_bot {
822 Blocked::Not
823 } else {
824 Blocked::Request
825 };
826 let create_blocked = if let Some(ChatIdBlocked { id: _, blocked }) = test_normal_chat {
827 match blocked {
828 Blocked::Request => create_blocked_default,
829 Blocked::Not => Blocked::Not,
830 Blocked::Yes => {
831 if Contact::is_blocked_load(context, from_id).await? {
832 Blocked::Yes
835 } else {
836 create_blocked_default
840 }
841 }
842 }
843 } else {
844 create_blocked_default
845 };
846
847 if chat_id.is_none() {
849 if let Some(grpid) = mime_parser.get_chat_group_id().map(|s| s.to_string()) {
850 if let Some((id, _protected, blocked)) =
851 chat::get_chat_id_by_grpid(context, &grpid).await?
852 {
853 chat_id = Some(id);
854 chat_id_blocked = blocked;
855 } else if allow_creation || test_normal_chat.is_some() {
856 if let Some((new_chat_id, new_chat_id_blocked)) = create_group(
857 context,
858 mime_parser,
859 is_partial_download.is_some(),
860 create_blocked,
861 from_id,
862 to_ids,
863 past_ids,
864 &verified_encryption,
865 &grpid,
866 )
867 .await?
868 {
869 chat_id = Some(new_chat_id);
870 chat_id_blocked = new_chat_id_blocked;
871 }
872 }
873 }
874 }
875
876 if chat_id.is_none() {
877 if let Some((new_chat_id, new_chat_id_blocked)) = lookup_chat_or_create_adhoc_group(
878 context,
879 mime_parser,
880 &parent,
881 to_ids,
882 from_id,
883 allow_creation || test_normal_chat.is_some(),
884 create_blocked,
885 is_partial_download.is_some(),
886 )
887 .await?
888 {
889 chat_id = Some(new_chat_id);
890 chat_id_blocked = new_chat_id_blocked;
891 }
892 }
893
894 if chat_id_blocked != Blocked::Not && create_blocked != Blocked::Yes {
897 if let Some(chat_id) = chat_id {
898 chat_id.set_blocked(context, create_blocked).await?;
899 chat_id_blocked = create_blocked;
900 }
901 }
902
903 if let Some(group_chat_id) = chat_id {
906 if !chat::is_contact_in_chat(context, group_chat_id, from_id).await? {
907 let chat = Chat::load_from_db(context, group_chat_id).await?;
908 if chat.is_protected() && chat.typ == Chattype::Single {
909 chat_id = None;
911 } else {
912 let from = &mime_parser.from;
915 let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
916 for part in &mut mime_parser.parts {
917 part.param.set(Param::OverrideSenderDisplayname, name);
918
919 if chat.is_protected() {
920 let s = stock_str::unknown_sender_for_chat(context).await;
922 part.error = Some(s);
923 }
924 }
925 }
926 }
927
928 group_changes = apply_group_changes(
929 context,
930 mime_parser,
931 group_chat_id,
932 from_id,
933 to_ids,
934 past_ids,
935 &verified_encryption,
936 )
937 .await?;
938 }
939
940 if chat_id.is_none() {
941 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
943 if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_mailinglist(
944 context,
945 allow_creation,
946 mailinglist_header,
947 mime_parser,
948 )
949 .await?
950 {
951 chat_id = Some(new_chat_id);
952 chat_id_blocked = new_chat_id_blocked;
953 }
954 }
955 }
956
957 if let Some(chat_id) = chat_id {
958 apply_mailinglist_changes(context, mime_parser, chat_id).await?;
959 }
960
961 if chat_id.is_none() {
962 let contact = Contact::get_by_id(context, from_id).await?;
964 let create_blocked = match contact.is_blocked() {
965 true => Blocked::Yes,
966 false if is_bot => Blocked::Not,
967 false => Blocked::Request,
968 };
969
970 if let Some(chat) = test_normal_chat {
971 chat_id = Some(chat.id);
972 chat_id_blocked = chat.blocked;
973 } else if allow_creation {
974 let chat = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
975 .await
976 .context("Failed to get (new) chat for contact")?;
977 chat_id = Some(chat.id);
978 chat_id_blocked = chat.blocked;
979 }
980
981 if let Some(chat_id) = chat_id {
982 if chat_id_blocked != Blocked::Not {
983 if chat_id_blocked != create_blocked {
984 chat_id.set_blocked(context, create_blocked).await?;
985 }
986 if create_blocked == Blocked::Request && parent.is_some() {
987 ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo)
990 .await?;
991 info!(
992 context,
993 "Message is a reply to a known message, mark sender as known.",
994 );
995 }
996 }
997
998 let chat = match is_partial_download.is_none()
1001 && mime_parser.get_header(HeaderDef::SecureJoin).is_none()
1002 {
1003 true => Some(Chat::load_from_db(context, chat_id).await?)
1004 .filter(|chat| chat.typ == Chattype::Single),
1005 false => None,
1006 };
1007 if let Some(chat) = chat {
1008 debug_assert!(chat.typ == Chattype::Single);
1009 let mut new_protection = match verified_encryption {
1010 VerifiedEncryption::Verified => ProtectionStatus::Protected,
1011 VerifiedEncryption::NotVerified(_) => ProtectionStatus::Unprotected,
1012 };
1013
1014 if chat.protected != ProtectionStatus::Unprotected
1015 && new_protection == ProtectionStatus::Unprotected
1016 && context.get_config_bool(Config::VerifiedOneOnOneChats).await?
1019 {
1020 new_protection = ProtectionStatus::ProtectionBroken;
1021 }
1022 if chat.protected != new_protection {
1023 chat_id
1027 .set_protection(
1028 context,
1029 new_protection,
1030 mime_parser.timestamp_sent,
1031 Some(from_id),
1032 )
1033 .await?;
1034 }
1035 if let Some(peerstate) = &mime_parser.peerstate {
1036 restore_protection = new_protection != ProtectionStatus::Protected
1037 && peerstate.prefer_encrypt == EncryptPreference::Mutual
1038 && contact.is_verified(context).await?;
1041 }
1042 }
1043 }
1044 }
1045
1046 state = if seen || is_mdn || chat_id_blocked == Blocked::Yes || group_changes.silent
1047 {
1049 MessageState::InSeen
1050 } else {
1051 MessageState::InFresh
1052 };
1053 } else {
1054 state = MessageState::OutDelivered;
1059 to_id = to_ids.first().copied().unwrap_or(ContactId::SELF);
1060
1061 let self_sent = to_ids.len() <= 1 && to_id == ContactId::SELF;
1066
1067 if mime_parser.sync_items.is_some() && self_sent {
1068 chat_id = Some(DC_CHAT_ID_TRASH);
1069 }
1070
1071 let is_draft = mime_parser
1075 .get_header(HeaderDef::XMozillaDraftInfo)
1076 .is_some();
1077
1078 if is_draft {
1079 info!(context, "Email is probably just a draft (TRASH).");
1081 chat_id = Some(DC_CHAT_ID_TRASH);
1082 }
1083
1084 if chat_id.is_none() {
1086 if let Some(grpid) = mime_parser.get_chat_group_id().map(|s| s.to_string()) {
1087 if let Some((id, _protected, blocked)) =
1088 chat::get_chat_id_by_grpid(context, &grpid).await?
1089 {
1090 chat_id = Some(id);
1091 chat_id_blocked = blocked;
1092 } else if allow_creation {
1093 if let Some((new_chat_id, new_chat_id_blocked)) = create_group(
1094 context,
1095 mime_parser,
1096 is_partial_download.is_some(),
1097 Blocked::Not,
1098 from_id,
1099 to_ids,
1100 past_ids,
1101 &verified_encryption,
1102 &grpid,
1103 )
1104 .await?
1105 {
1106 chat_id = Some(new_chat_id);
1107 chat_id_blocked = new_chat_id_blocked;
1108 }
1109 }
1110 }
1111 }
1112
1113 if mime_parser.decrypting_failed {
1114 if chat_id.is_none() {
1115 chat_id = Some(DC_CHAT_ID_TRASH);
1116 } else {
1117 hidden = true;
1118 }
1119 let last_time = context
1120 .get_config_i64(Config::LastCantDecryptOutgoingMsgs)
1121 .await?;
1122 let now = tools::time();
1123 let update_config = if last_time.saturating_add(24 * 60 * 60) <= now {
1124 let mut msg =
1125 Message::new_text(stock_str::cant_decrypt_outgoing_msgs(context).await);
1126 chat::add_device_msg(context, None, Some(&mut msg))
1127 .await
1128 .log_err(context)
1129 .ok();
1130 true
1131 } else {
1132 last_time > now
1133 };
1134 if update_config {
1135 context
1136 .set_config_internal(
1137 Config::LastCantDecryptOutgoingMsgs,
1138 Some(&now.to_string()),
1139 )
1140 .await?;
1141 }
1142 }
1143
1144 if chat_id.is_none() {
1145 if let Some((new_chat_id, new_chat_id_blocked)) = lookup_chat_or_create_adhoc_group(
1146 context,
1147 mime_parser,
1148 &parent,
1149 to_ids,
1150 from_id,
1151 allow_creation,
1152 Blocked::Not,
1153 is_partial_download.is_some(),
1154 )
1155 .await?
1156 {
1157 chat_id = Some(new_chat_id);
1158 chat_id_blocked = new_chat_id_blocked;
1159 }
1160 }
1161
1162 if !to_ids.is_empty() {
1163 if chat_id.is_none() && allow_creation {
1164 let to_contact = Contact::get_by_id(context, to_id).await?;
1165 if let Some(list_id) = to_contact.param.get(Param::ListId) {
1166 if let Some((id, _, blocked)) =
1167 chat::get_chat_id_by_grpid(context, list_id).await?
1168 {
1169 chat_id = Some(id);
1170 chat_id_blocked = blocked;
1171 }
1172 } else {
1173 let chat = ChatIdBlocked::get_for_contact(context, to_id, Blocked::Not).await?;
1174 chat_id = Some(chat.id);
1175 chat_id_blocked = chat.blocked;
1176 }
1177 }
1178 if chat_id.is_none() && is_dc_message == MessengerMessage::Yes {
1179 if let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await? {
1180 chat_id = Some(chat.id);
1181 chat_id_blocked = chat.blocked;
1182 }
1183 }
1184
1185 if chat_id_blocked != Blocked::Not {
1187 if let Some(chat_id) = chat_id {
1188 chat_id.unblock_ex(context, Nosync).await?;
1189 }
1191 }
1192 }
1193
1194 if let Some(chat_id) = chat_id {
1195 group_changes = apply_group_changes(
1196 context,
1197 mime_parser,
1198 chat_id,
1199 from_id,
1200 to_ids,
1201 past_ids,
1202 &verified_encryption,
1203 )
1204 .await?;
1205 }
1206
1207 if chat_id.is_none() {
1208 if let Some(mailinglist_header) = mime_parser.get_mailinglist_header() {
1210 let listid = mailinglist_header_listid(mailinglist_header)?;
1211 chat_id = Some(
1212 if let Some((id, ..)) = chat::get_chat_id_by_grpid(context, &listid).await? {
1213 id
1214 } else {
1215 let name =
1216 compute_mailinglist_name(mailinglist_header, &listid, mime_parser);
1217 chat::create_broadcast_list_ex(context, Nosync, listid, name).await?
1218 },
1219 );
1220 }
1221 }
1222
1223 if chat_id.is_none() && self_sent {
1224 let chat = ChatIdBlocked::get_for_contact(context, ContactId::SELF, Blocked::Not)
1227 .await
1228 .context("Failed to get (new) chat for contact")?;
1229
1230 chat_id = Some(chat.id);
1231 if Blocked::Not != chat.blocked {
1234 chat.id.unblock_ex(context, Nosync).await?;
1235 }
1236 }
1237 }
1238
1239 if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
1240 if let Some(part) = mime_parser.parts.first() {
1241 if part.typ == Viewtype::Text && part.msg.is_empty() {
1242 chat_id = Some(DC_CHAT_ID_TRASH);
1243 info!(context, "Message is a status update only (TRASH).");
1244 markseen_on_imap_table(context, rfc724_mid).await.ok();
1245 }
1246 }
1247 }
1248
1249 let orig_chat_id = chat_id;
1250 let mut chat_id = chat_id.unwrap_or_else(|| {
1251 info!(context, "No chat id for message (TRASH).");
1252 DC_CHAT_ID_TRASH
1253 });
1254
1255 let mut ephemeral_timer = if is_partial_download.is_some() {
1257 chat_id.get_ephemeral_timer(context).await?
1258 } else if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer) {
1259 match value.parse::<EphemeralTimer>() {
1260 Ok(timer) => timer,
1261 Err(err) => {
1262 warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
1263 EphemeralTimer::Disabled
1264 }
1265 }
1266 } else {
1267 EphemeralTimer::Disabled
1268 };
1269
1270 let in_fresh = state == MessageState::InFresh;
1271 let sort_to_bottom = false;
1272 let received = true;
1273 let sort_timestamp = chat_id
1274 .calc_sort_timestamp(
1275 context,
1276 mime_parser.timestamp_sent,
1277 sort_to_bottom,
1278 received,
1279 mime_parser.incoming,
1280 )
1281 .await?;
1282
1283 if !chat_id.is_special()
1289 && !mime_parser.parts.is_empty()
1290 && chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
1291 {
1292 let chat_contacts =
1293 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
1294 let is_from_in_chat =
1295 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
1296
1297 info!(context, "Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied.");
1298 if !is_from_in_chat {
1299 warn!(
1300 context,
1301 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} because sender {from_id} is not a member.",
1302 );
1303 } else if is_dc_message == MessengerMessage::Yes
1304 && get_previous_message(context, mime_parser)
1305 .await?
1306 .map(|p| p.ephemeral_timer)
1307 == Some(ephemeral_timer)
1308 && mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged
1309 {
1310 warn!(
1317 context,
1318 "Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
1319 );
1320 } else if chat_id
1321 .update_timestamp(
1322 context,
1323 Param::EphemeralSettingsTimestamp,
1324 mime_parser.timestamp_sent,
1325 )
1326 .await?
1327 {
1328 if let Err(err) = chat_id
1329 .inner_set_ephemeral_timer(context, ephemeral_timer)
1330 .await
1331 {
1332 warn!(
1333 context,
1334 "Failed to modify timer for chat {chat_id}: {err:#}."
1335 );
1336 } else {
1337 info!(
1338 context,
1339 "Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
1340 );
1341 if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
1342 chat::add_info_msg(
1343 context,
1344 chat_id,
1345 &stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
1346 sort_timestamp,
1347 )
1348 .await?;
1349 }
1350 }
1351 } else {
1352 warn!(
1353 context,
1354 "Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
1355 );
1356 }
1357 }
1358
1359 if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
1360 better_msg = Some(stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await);
1361
1362 ephemeral_timer = EphemeralTimer::Disabled;
1369 }
1370
1371 if !chat_id.is_special() && is_partial_download.is_none() {
1373 let chat = Chat::load_from_db(context, chat_id).await?;
1374
1375 if chat.is_protected() && (mime_parser.incoming || chat.typ != Chattype::Single) {
1384 if let VerifiedEncryption::NotVerified(err) = verified_encryption {
1385 warn!(context, "Verification problem: {err:#}.");
1386 let s = format!("{err}. See 'Info' for more details");
1387 mime_parser.replace_msg_by_error(&s);
1388 }
1389 }
1390 }
1391
1392 let sort_timestamp = tweak_sort_timestamp(
1393 context,
1394 mime_parser,
1395 group_changes.silent,
1396 chat_id,
1397 sort_timestamp,
1398 )
1399 .await?;
1400
1401 let mime_in_reply_to = mime_parser
1402 .get_header(HeaderDef::InReplyTo)
1403 .unwrap_or_default();
1404 let mime_references = mime_parser
1405 .get_header(HeaderDef::References)
1406 .unwrap_or_default();
1407
1408 let icnt = mime_parser.parts.len();
1413
1414 let subject = mime_parser.get_subject().unwrap_or_default();
1415
1416 let is_system_message = mime_parser.is_system_message;
1417
1418 let mut save_mime_modified = false;
1425
1426 let mime_headers = if mime_parser.is_mime_modified {
1427 let headers = if !mime_parser.decoded_data.is_empty() {
1428 mime_parser.decoded_data.clone()
1429 } else {
1430 imf_raw.to_vec()
1431 };
1432 tokio::task::block_in_place(move || buf_compress(&headers))?
1433 } else {
1434 Vec::new()
1435 };
1436
1437 let mut created_db_entries = Vec::with_capacity(mime_parser.parts.len());
1438
1439 if let Some(m) = group_changes.better_msg {
1440 match &better_msg {
1441 None => better_msg = Some(m),
1442 Some(_) => {
1443 if !m.is_empty() {
1444 group_changes.extra_msgs.push((m, is_system_message, None))
1445 }
1446 }
1447 }
1448 }
1449
1450 for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs {
1451 chat::add_info_msg_with_cmd(
1452 context,
1453 chat_id,
1454 &group_changes_msg,
1455 cmd,
1456 sort_timestamp,
1457 None,
1458 None,
1459 None,
1460 added_removed_id,
1461 )
1462 .await?;
1463 }
1464
1465 if let Some(node_addr) = mime_parser.get_header(HeaderDef::IrohNodeAddr) {
1466 chat_id = DC_CHAT_ID_TRASH;
1467 match mime_parser.get_header(HeaderDef::InReplyTo) {
1468 Some(in_reply_to) => match rfc724_mid_exists(context, in_reply_to).await? {
1469 Some((instance_id, _ts_sent)) => {
1470 if let Err(err) =
1471 add_gossip_peer_from_header(context, instance_id, node_addr).await
1472 {
1473 warn!(context, "Failed to add iroh peer from header: {err:#}.");
1474 }
1475 }
1476 None => {
1477 warn!(
1478 context,
1479 "Cannot add iroh peer because WebXDC instance does not exist."
1480 );
1481 }
1482 },
1483 None => {
1484 warn!(
1485 context,
1486 "Cannot add iroh peer because the message has no In-Reply-To."
1487 );
1488 }
1489 }
1490 }
1491
1492 if handle_edit_delete(context, mime_parser, from_id).await? {
1493 chat_id = DC_CHAT_ID_TRASH;
1494 info!(context, "Message edits/deletes existing message (TRASH).");
1495 }
1496
1497 let mut parts = mime_parser.parts.iter().peekable();
1498 while let Some(part) = parts.next() {
1499 if part.is_reaction {
1500 let reaction_str = simplify::remove_footers(part.msg.as_str());
1501 let is_incoming_fresh = mime_parser.incoming && !seen;
1502 set_msg_reaction(
1503 context,
1504 mime_in_reply_to,
1505 orig_chat_id.unwrap_or_default(),
1506 from_id,
1507 sort_timestamp,
1508 Reaction::from(reaction_str.as_str()),
1509 is_incoming_fresh,
1510 )
1511 .await?;
1512 }
1513
1514 let mut param = part.param.clone();
1515 if is_system_message != SystemMessage::Unknown {
1516 param.set_int(Param::Cmd, is_system_message as i32);
1517 }
1518
1519 if let Some(replace_msg_id) = replace_msg_id {
1520 let placeholder = Message::load_from_db(context, replace_msg_id).await?;
1521 for key in [
1522 Param::WebxdcSummary,
1523 Param::WebxdcSummaryTimestamp,
1524 Param::WebxdcDocument,
1525 Param::WebxdcDocumentTimestamp,
1526 ] {
1527 if let Some(value) = placeholder.param.get(key) {
1528 param.set(key, value);
1529 }
1530 }
1531 }
1532
1533 let (msg, typ): (&str, Viewtype) = if let Some(better_msg) = &better_msg {
1534 if better_msg.is_empty() && is_partial_download.is_none() {
1535 chat_id = DC_CHAT_ID_TRASH;
1536 }
1537 (better_msg, Viewtype::Text)
1538 } else {
1539 (&part.msg, part.typ)
1540 };
1541 let part_is_empty =
1542 typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
1543
1544 if let Some(contact_id) = group_changes.added_removed_id {
1545 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
1546 }
1547
1548 save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
1549 let save_mime_modified = save_mime_modified && parts.peek().is_none();
1550
1551 let ephemeral_timestamp = if in_fresh {
1552 0
1553 } else {
1554 match ephemeral_timer {
1555 EphemeralTimer::Disabled => 0,
1556 EphemeralTimer::Enabled { duration } => {
1557 mime_parser.timestamp_rcvd.saturating_add(duration.into())
1558 }
1559 }
1560 };
1561
1562 let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
1565
1566 let row_id = context
1567 .sql
1568 .call_write(|conn| {
1569 let mut stmt = conn.prepare_cached(
1570 r#"
1571INSERT INTO msgs
1572 (
1573 id,
1574 rfc724_mid, chat_id,
1575 from_id, to_id, timestamp, timestamp_sent,
1576 timestamp_rcvd, type, state, msgrmsg,
1577 txt, txt_normalized, subject, param, hidden,
1578 bytes, mime_headers, mime_compressed, mime_in_reply_to,
1579 mime_references, mime_modified, error, ephemeral_timer,
1580 ephemeral_timestamp, download_state, hop_info
1581 )
1582 VALUES (
1583 ?,
1584 ?, ?, ?, ?,
1585 ?, ?, ?, ?,
1586 ?, ?, ?, ?,
1587 ?, ?, ?, ?, ?, 1,
1588 ?, ?, ?, ?,
1589 ?, ?, ?, ?
1590 )
1591ON CONFLICT (id) DO UPDATE
1592SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
1593 from_id=excluded.from_id, to_id=excluded.to_id, timestamp_sent=excluded.timestamp_sent,
1594 type=excluded.type, state=max(state,excluded.state), msgrmsg=excluded.msgrmsg,
1595 txt=excluded.txt, txt_normalized=excluded.txt_normalized, subject=excluded.subject,
1596 param=excluded.param,
1597 hidden=excluded.hidden,bytes=excluded.bytes, mime_headers=excluded.mime_headers,
1598 mime_compressed=excluded.mime_compressed, mime_in_reply_to=excluded.mime_in_reply_to,
1599 mime_references=excluded.mime_references, mime_modified=excluded.mime_modified, error=excluded.error, ephemeral_timer=excluded.ephemeral_timer,
1600 ephemeral_timestamp=excluded.ephemeral_timestamp, download_state=excluded.download_state, hop_info=excluded.hop_info
1601RETURNING id
1602"#)?;
1603 let row_id: MsgId = stmt.query_row(params![
1604 replace_msg_id,
1605 rfc724_mid_orig,
1606 if trash { DC_CHAT_ID_TRASH } else { chat_id },
1607 if trash { ContactId::UNDEFINED } else { from_id },
1608 if trash { ContactId::UNDEFINED } else { to_id },
1609 sort_timestamp,
1610 mime_parser.timestamp_sent,
1611 mime_parser.timestamp_rcvd,
1612 typ,
1613 state,
1614 is_dc_message,
1615 if trash || hidden { "" } else { msg },
1616 if trash || hidden { None } else { message::normalize_text(msg) },
1617 if trash || hidden { "" } else { &subject },
1618 if trash {
1619 "".to_string()
1620 } else {
1621 param.to_string()
1622 },
1623 hidden,
1624 part.bytes as isize,
1625 if save_mime_modified && !(trash || hidden) {
1626 mime_headers.clone()
1627 } else {
1628 Vec::new()
1629 },
1630 mime_in_reply_to,
1631 mime_references,
1632 save_mime_modified,
1633 part.error.as_deref().unwrap_or_default(),
1634 ephemeral_timer,
1635 ephemeral_timestamp,
1636 if is_partial_download.is_some() {
1637 DownloadState::Available
1638 } else if mime_parser.decrypting_failed {
1639 DownloadState::Undecipherable
1640 } else {
1641 DownloadState::Done
1642 },
1643 mime_parser.hop_info
1644 ],
1645 |row| {
1646 let msg_id: MsgId = row.get(0)?;
1647 Ok(msg_id)
1648 }
1649 )?;
1650 Ok(row_id)
1651 })
1652 .await?;
1653
1654 replace_msg_id = None;
1657
1658 debug_assert!(!row_id.is_special());
1659 created_db_entries.push(row_id);
1660 }
1661
1662 for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
1664 if part.typ == Viewtype::Webxdc {
1666 if let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic) {
1667 let mut topic_raw = [0u8; 32];
1669 BASE32_NOPAD
1670 .decode_mut(topic.to_ascii_uppercase().as_bytes(), &mut topic_raw)
1671 .map_err(|e| e.error)
1672 .context("Wrong gossip topic header")?;
1673
1674 let topic = TopicId::from_bytes(topic_raw);
1675 insert_topic_stub(context, *msg_id, topic).await?;
1676 } else {
1677 warn!(context, "webxdc doesn't have a gossip topic")
1678 }
1679 }
1680
1681 maybe_set_logging_xdc_inner(
1682 context,
1683 part.typ,
1684 chat_id,
1685 part.param.get(Param::Filename),
1686 *msg_id,
1687 )
1688 .await?;
1689 }
1690
1691 if let Some(replace_msg_id) = replace_msg_id {
1692 let on_server = rfc724_mid == rfc724_mid_orig;
1696 replace_msg_id.trash(context, on_server).await?;
1697 }
1698
1699 let unarchive = match mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
1700 Some(addr) => context.is_self_addr(addr).await?,
1701 None => true,
1702 };
1703 if unarchive {
1704 chat_id.unarchive_if_not_muted(context, state).await?;
1705 }
1706
1707 info!(
1708 context,
1709 "Message has {icnt} parts and is assigned to chat #{chat_id}."
1710 );
1711
1712 if !chat_id.is_trash() && !hidden {
1713 let mut chat = Chat::load_from_db(context, chat_id).await?;
1714
1715 if chat
1719 .param
1720 .update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
1721 {
1722 let subject = mime_parser.get_subject().unwrap_or_default();
1725
1726 chat.param.set(Param::LastSubject, subject);
1727 chat.update_param(context).await?;
1728 }
1729 }
1730
1731 if !mime_parser.incoming && is_mdn && is_dc_message == MessengerMessage::Yes {
1732 needs_delete_job = true;
1736 }
1737 if restore_protection {
1738 chat_id
1739 .set_protection(
1740 context,
1741 ProtectionStatus::Protected,
1742 mime_parser.timestamp_rcvd,
1743 Some(from_id),
1744 )
1745 .await?;
1746 }
1747 Ok(ReceivedMsg {
1748 chat_id,
1749 state,
1750 hidden,
1751 sort_timestamp,
1752 msg_ids: created_db_entries,
1753 needs_delete_job,
1754 #[cfg(test)]
1755 from_is_signed: mime_parser.from_is_signed,
1756 })
1757}
1758
1759async fn handle_edit_delete(
1764 context: &Context,
1765 mime_parser: &MimeMessage,
1766 from_id: ContactId,
1767) -> Result<bool> {
1768 if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
1769 if let Some((original_msg_id, _)) = rfc724_mid_exists(context, rfc724_mid).await? {
1770 if let Some(mut original_msg) =
1771 Message::load_from_db_optional(context, original_msg_id).await?
1772 {
1773 if original_msg.from_id == from_id {
1774 if let Some(part) = mime_parser.parts.first() {
1775 let edit_msg_showpadlock = part
1776 .param
1777 .get_bool(Param::GuaranteeE2ee)
1778 .unwrap_or_default();
1779 if edit_msg_showpadlock || !original_msg.get_showpadlock() {
1780 let new_text =
1781 part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
1782 chat::save_text_edit_to_db(context, &mut original_msg, new_text)
1783 .await?;
1784 } else {
1785 warn!(context, "Edit message: Not encrypted.");
1786 }
1787 }
1788 } else {
1789 warn!(context, "Edit message: Bad sender.");
1790 }
1791 } else {
1792 warn!(context, "Edit message: Database entry does not exist.");
1793 }
1794 } else {
1795 warn!(
1796 context,
1797 "Edit message: rfc724_mid {rfc724_mid:?} not found."
1798 );
1799 }
1800
1801 Ok(true)
1802 } else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete) {
1803 if let Some(part) = mime_parser.parts.first() {
1804 if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
1807 let mut modified_chat_ids = HashSet::new();
1808 let mut msg_ids = Vec::new();
1809
1810 let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
1811 for rfc724_mid in rfc724_mid_vec {
1812 if let Some((msg_id, _)) =
1813 message::rfc724_mid_exists(context, rfc724_mid).await?
1814 {
1815 if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
1816 if msg.from_id == from_id {
1817 message::delete_msg_locally(context, &msg).await?;
1818 msg_ids.push(msg.id);
1819 modified_chat_ids.insert(msg.chat_id);
1820 } else {
1821 warn!(context, "Delete message: Bad sender.");
1822 }
1823 } else {
1824 warn!(context, "Delete message: Database entry does not exist.");
1825 }
1826 } else {
1827 warn!(context, "Delete message: {rfc724_mid:?} not found.");
1828 }
1829 }
1830 message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
1831 } else {
1832 warn!(context, "Delete message: Not encrypted.");
1833 }
1834 }
1835
1836 Ok(true)
1837 } else {
1838 Ok(false)
1839 }
1840}
1841
1842async fn tweak_sort_timestamp(
1843 context: &Context,
1844 mime_parser: &mut MimeMessage,
1845 silent: bool,
1846 chat_id: ChatId,
1847 sort_timestamp: i64,
1848) -> Result<i64> {
1849 let parent_timestamp = mime_parser.get_parent_timestamp(context).await?;
1858 let mut sort_timestamp = parent_timestamp.map_or(sort_timestamp, |parent_timestamp| {
1859 std::cmp::max(sort_timestamp, parent_timestamp)
1860 });
1861
1862 if silent {
1866 let last_msg_timestamp = if let Some(t) = chat_id.get_timestamp(context).await? {
1867 t
1868 } else {
1869 chat_id.created_timestamp(context).await?
1870 };
1871 sort_timestamp = std::cmp::min(sort_timestamp, last_msg_timestamp);
1872 }
1873 Ok(sort_timestamp)
1874}
1875
1876async fn save_locations(
1880 context: &Context,
1881 mime_parser: &MimeMessage,
1882 chat_id: ChatId,
1883 from_id: ContactId,
1884 msg_id: MsgId,
1885) -> Result<()> {
1886 if chat_id.is_special() {
1887 return Ok(());
1889 }
1890
1891 let mut send_event = false;
1892
1893 if let Some(message_kml) = &mime_parser.message_kml {
1894 if let Some(newest_location_id) =
1895 location::save(context, chat_id, from_id, &message_kml.locations, true).await?
1896 {
1897 location::set_msg_location_id(context, msg_id, newest_location_id).await?;
1898 send_event = true;
1899 }
1900 }
1901
1902 if let Some(location_kml) = &mime_parser.location_kml {
1903 if let Some(addr) = &location_kml.addr {
1904 let contact = Contact::get_by_id(context, from_id).await?;
1905 if contact.get_addr().to_lowercase() == addr.to_lowercase() {
1906 if location::save(context, chat_id, from_id, &location_kml.locations, false)
1907 .await?
1908 .is_some()
1909 {
1910 send_event = true;
1911 }
1912 } else {
1913 warn!(
1914 context,
1915 "Address in location.kml {:?} is not the same as the sender address {:?}.",
1916 addr,
1917 contact.get_addr()
1918 );
1919 }
1920 }
1921 }
1922 if send_event {
1923 context.emit_location_changed(Some(from_id)).await?;
1924 }
1925 Ok(())
1926}
1927
1928async fn lookup_chat_by_reply(
1929 context: &Context,
1930 mime_parser: &MimeMessage,
1931 parent: &Option<Message>,
1932 to_ids: &[ContactId],
1933 from_id: ContactId,
1934) -> Result<Option<(ChatId, Blocked)>> {
1935 let Some(parent) = parent else {
1938 return Ok(None);
1939 };
1940 let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
1941 return Ok(None);
1942 };
1943 let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
1944
1945 if is_probably_private_reply(context, to_ids, from_id, mime_parser, parent_chat.id).await? {
1948 return Ok(None);
1949 }
1950
1951 if parent_chat.typ == Chattype::Single && !mime_parser.has_chat_version() && to_ids.len() > 1 {
1955 let mut chat_contacts = chat::get_chat_contacts(context, parent_chat.id).await?;
1956 chat_contacts.push(ContactId::SELF);
1957 if to_ids.iter().any(|id| !chat_contacts.contains(id)) {
1958 return Ok(None);
1959 }
1960 }
1961
1962 info!(
1963 context,
1964 "Assigning message to {} as it's a reply to {}.", parent_chat.id, parent.rfc724_mid
1965 );
1966 Ok(Some((parent_chat.id, parent_chat.blocked)))
1967}
1968
1969#[expect(clippy::too_many_arguments)]
1970async fn lookup_chat_or_create_adhoc_group(
1971 context: &Context,
1972 mime_parser: &MimeMessage,
1973 parent: &Option<Message>,
1974 to_ids: &[ContactId],
1975 from_id: ContactId,
1976 allow_creation: bool,
1977 create_blocked: Blocked,
1978 is_partial_download: bool,
1979) -> Result<Option<(ChatId, Blocked)>> {
1980 if let Some((new_chat_id, new_chat_id_blocked)) =
1981 lookup_chat_by_reply(context, mime_parser, parent, to_ids, from_id).await?
1983 {
1984 return Ok(Some((new_chat_id, new_chat_id_blocked)));
1985 }
1986 if is_partial_download {
1990 info!(
1991 context,
1992 "Ad-hoc group cannot be created from partial download."
1993 );
1994 return Ok(None);
1995 }
1996 if mime_parser.decrypting_failed {
1997 warn!(
1998 context,
1999 "Not creating ad-hoc group for message that cannot be decrypted."
2000 );
2001 return Ok(None);
2002 }
2003
2004 let grpname = mime_parser
2005 .get_subject()
2006 .map(|s| remove_subject_prefix(&s))
2007 .unwrap_or_else(|| "👥📧".to_string());
2008 let mut contact_ids = Vec::with_capacity(to_ids.len() + 1);
2009 contact_ids.extend(to_ids);
2010 if !contact_ids.contains(&from_id) {
2011 contact_ids.push(from_id);
2012 }
2013 let trans_fn = |t: &mut rusqlite::Transaction| {
2014 t.pragma_update(None, "query_only", "0")?;
2015 t.execute(
2016 "CREATE TEMP TABLE temp.contacts (
2017 id INTEGER PRIMARY KEY
2018 ) STRICT",
2019 (),
2020 )?;
2021 let mut stmt = t.prepare("INSERT INTO temp.contacts(id) VALUES (?)")?;
2022 for &id in &contact_ids {
2023 stmt.execute((id,))?;
2024 }
2025 let val = t
2026 .query_row(
2027 "SELECT c.id, c.blocked
2028 FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
2029 WHERE m.hidden=0 AND c.grpid='' AND c.name=?
2030 AND (SELECT COUNT(*) FROM chats_contacts
2031 WHERE chat_id=c.id
2032 AND add_timestamp >= remove_timestamp)=?
2033 AND (SELECT COUNT(*) FROM chats_contacts
2034 WHERE chat_id=c.id
2035 AND contact_id NOT IN (SELECT id FROM temp.contacts)
2036 AND add_timestamp >= remove_timestamp)=0
2037 ORDER BY m.timestamp DESC",
2038 (&grpname, contact_ids.len()),
2039 |row| {
2040 let id: ChatId = row.get(0)?;
2041 let blocked: Blocked = row.get(1)?;
2042 Ok((id, blocked))
2043 },
2044 )
2045 .optional()?;
2046 t.execute("DROP TABLE temp.contacts", ())?;
2047 Ok(val)
2048 };
2049 let query_only = true;
2050 if let Some((chat_id, blocked)) = context.sql.transaction_ex(query_only, trans_fn).await? {
2051 info!(
2052 context,
2053 "Assigning message to ad-hoc group {chat_id} with matching name and members."
2054 );
2055 return Ok(Some((chat_id, blocked)));
2056 }
2057 if !allow_creation {
2058 return Ok(None);
2059 }
2060 create_adhoc_group(
2061 context,
2062 mime_parser,
2063 create_blocked,
2064 from_id,
2065 to_ids,
2066 &grpname,
2067 )
2068 .await
2069 .context("Could not create ad hoc group")
2070}
2071
2072async fn is_probably_private_reply(
2075 context: &Context,
2076 to_ids: &[ContactId],
2077 from_id: ContactId,
2078 mime_parser: &MimeMessage,
2079 parent_chat_id: ChatId,
2080) -> Result<bool> {
2081 let private_message =
2089 (to_ids == [ContactId::SELF]) || (from_id == ContactId::SELF && to_ids.len() == 1);
2090 if !private_message {
2091 return Ok(false);
2092 }
2093
2094 if mime_parser.get_chat_group_id().is_some() {
2096 return Ok(false);
2097 }
2098
2099 if !mime_parser.has_chat_version() {
2100 let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
2101 if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
2102 return Ok(false);
2103 }
2104 }
2105
2106 Ok(true)
2107}
2108
2109#[expect(clippy::too_many_arguments)]
2115async fn create_group(
2116 context: &Context,
2117 mime_parser: &mut MimeMessage,
2118 is_partial_download: bool,
2119 create_blocked: Blocked,
2120 from_id: ContactId,
2121 to_ids: &[ContactId],
2122 past_ids: &[ContactId],
2123 verified_encryption: &VerifiedEncryption,
2124 grpid: &str,
2125) -> Result<Option<(ChatId, Blocked)>> {
2126 let mut chat_id = None;
2127 let mut chat_id_blocked = Default::default();
2128
2129 if let Some(chat_id) = chat_id {
2132 if !mime_parser.has_chat_version()
2133 && is_probably_private_reply(context, to_ids, from_id, mime_parser, chat_id).await?
2134 {
2135 return Ok(None);
2136 }
2137 }
2138
2139 let create_protected = if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
2140 if let VerifiedEncryption::NotVerified(err) = verified_encryption {
2141 warn!(
2142 context,
2143 "Creating unprotected group because of the verification problem: {err:#}."
2144 );
2145 ProtectionStatus::Unprotected
2146 } else {
2147 ProtectionStatus::Protected
2148 }
2149 } else {
2150 ProtectionStatus::Unprotected
2151 };
2152
2153 async fn self_explicitly_added(
2154 context: &Context,
2155 mime_parser: &&mut MimeMessage,
2156 ) -> Result<bool> {
2157 let ret = match mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2158 Some(member_addr) => context.is_self_addr(member_addr).await?,
2159 None => false,
2160 };
2161 Ok(ret)
2162 }
2163
2164 if chat_id.is_none()
2165 && !mime_parser.is_mailinglist_message()
2166 && !grpid.is_empty()
2167 && mime_parser.get_header(HeaderDef::ChatGroupName).is_some()
2168 && mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved).is_none()
2170 && (!chat::is_group_explicitly_left(context, grpid).await?
2172 || self_explicitly_added(context, &mime_parser).await?)
2173 {
2174 let grpname = mime_parser
2176 .get_header(HeaderDef::ChatGroupName)
2177 .context("Chat-Group-Name vanished")?
2178 .trim();
2182 let new_chat_id = ChatId::create_multiuser_record(
2183 context,
2184 Chattype::Group,
2185 grpid,
2186 grpname,
2187 create_blocked,
2188 create_protected,
2189 None,
2190 mime_parser.timestamp_sent,
2191 )
2192 .await
2193 .with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
2194
2195 chat_id = Some(new_chat_id);
2196 chat_id_blocked = create_blocked;
2197
2198 if let Some(mut chat_group_member_timestamps) = mime_parser.chat_group_member_timestamps() {
2200 let mut new_to_ids = to_ids.to_vec();
2201 if !new_to_ids.contains(&from_id) {
2202 new_to_ids.insert(0, from_id);
2203 chat_group_member_timestamps.insert(0, mime_parser.timestamp_sent);
2204 }
2205
2206 update_chats_contacts_timestamps(
2207 context,
2208 new_chat_id,
2209 None,
2210 &new_to_ids,
2211 past_ids,
2212 &chat_group_member_timestamps,
2213 )
2214 .await?;
2215 } else {
2216 let mut members = vec![ContactId::SELF];
2217 if !from_id.is_special() {
2218 members.push(from_id);
2219 }
2220 members.extend(to_ids);
2221
2222 let timestamp = 0;
2228
2229 chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
2230 }
2231
2232 context.emit_event(EventType::ChatModified(new_chat_id));
2233 chatlist_events::emit_chatlist_changed(context);
2234 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2235 }
2236
2237 if let Some(chat_id) = chat_id {
2238 Ok(Some((chat_id, chat_id_blocked)))
2239 } else if is_partial_download || mime_parser.decrypting_failed {
2240 Ok(None)
2247 } else {
2248 info!(context, "Message belongs to unwanted group (TRASH).");
2251 Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
2252 }
2253}
2254
2255async fn update_chats_contacts_timestamps(
2256 context: &Context,
2257 chat_id: ChatId,
2258 ignored_id: Option<ContactId>,
2259 to_ids: &[ContactId],
2260 past_ids: &[ContactId],
2261 chat_group_member_timestamps: &[i64],
2262) -> Result<bool> {
2263 let expected_timestamps_count = to_ids.len() + past_ids.len();
2264
2265 if chat_group_member_timestamps.len() != expected_timestamps_count {
2266 warn!(
2267 context,
2268 "Chat-Group-Member-Timestamps has wrong number of timestamps, got {}, expected {}.",
2269 chat_group_member_timestamps.len(),
2270 expected_timestamps_count
2271 );
2272 return Ok(false);
2273 }
2274
2275 let mut modified = false;
2276
2277 context
2278 .sql
2279 .transaction(|transaction| {
2280 let mut add_statement = transaction.prepare(
2281 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
2282 VALUES (?1, ?2, ?3)
2283 ON CONFLICT (chat_id, contact_id)
2284 DO
2285 UPDATE SET add_timestamp=?3
2286 WHERE ?3>add_timestamp AND ?3>=remove_timestamp",
2287 )?;
2288
2289 for (contact_id, ts) in iter::zip(
2290 to_ids.iter(),
2291 chat_group_member_timestamps.iter().take(to_ids.len()),
2292 ) {
2293 if Some(*contact_id) != ignored_id {
2294 modified |= add_statement.execute((chat_id, contact_id, ts))? > 0;
2298 }
2299 }
2300
2301 let mut remove_statement = transaction.prepare(
2302 "INSERT INTO chats_contacts (chat_id, contact_id, remove_timestamp)
2303 VALUES (?1, ?2, ?3)
2304 ON CONFLICT (chat_id, contact_id)
2305 DO
2306 UPDATE SET remove_timestamp=?3
2307 WHERE ?3>remove_timestamp AND ?3>add_timestamp",
2308 )?;
2309
2310 for (contact_id, ts) in iter::zip(
2311 past_ids.iter(),
2312 chat_group_member_timestamps.iter().skip(to_ids.len()),
2313 ) {
2314 modified |= remove_statement.execute((chat_id, contact_id, ts))? > 0;
2318 }
2319
2320 Ok(())
2321 })
2322 .await?;
2323
2324 Ok(modified)
2325}
2326
2327#[derive(Default)]
2331struct GroupChangesInfo {
2332 better_msg: Option<String>,
2335 added_removed_id: Option<ContactId>,
2337 silent: bool,
2339 extra_msgs: Vec<(String, SystemMessage, Option<ContactId>)>,
2341}
2342
2343async fn apply_group_changes(
2350 context: &Context,
2351 mime_parser: &mut MimeMessage,
2352 chat_id: ChatId,
2353 from_id: ContactId,
2354 to_ids: &[ContactId],
2355 past_ids: &[ContactId],
2356 verified_encryption: &VerifiedEncryption,
2357) -> Result<GroupChangesInfo> {
2358 if chat_id.is_special() {
2359 return Ok(GroupChangesInfo::default());
2361 }
2362 let mut chat = Chat::load_from_db(context, chat_id).await?;
2363 if chat.typ != Chattype::Group {
2364 return Ok(GroupChangesInfo::default());
2365 }
2366
2367 let mut send_event_chat_modified = false;
2368 let (mut removed_id, mut added_id) = (None, None);
2369 let mut better_msg = None;
2370 let mut silent = false;
2371
2372 let self_added =
2374 if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2375 addr_cmp(&context.get_primary_self_addr().await?, added_addr)
2376 } else {
2377 false
2378 };
2379
2380 let chat_contacts =
2381 HashSet::<ContactId>::from_iter(chat::get_chat_contacts(context, chat_id).await?);
2382 let is_from_in_chat =
2383 !chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
2384
2385 if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
2386 if let VerifiedEncryption::NotVerified(err) = verified_encryption {
2387 if chat.is_protected() {
2388 warn!(context, "Verification problem: {err:#}.");
2389 let s = format!("{err}. See 'Info' for more details");
2390 mime_parser.replace_msg_by_error(&s);
2391 } else {
2392 warn!(
2393 context,
2394 "Not marking chat {chat_id} as protected due to verification problem: {err:#}."
2395 );
2396 }
2397 } else if !chat.is_protected() {
2398 chat_id
2399 .set_protection(
2400 context,
2401 ProtectionStatus::Protected,
2402 mime_parser.timestamp_sent,
2403 Some(from_id),
2404 )
2405 .await?;
2406 }
2407 }
2408
2409 if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
2410 removed_id = Contact::lookup_id_by_addr(context, removed_addr, Origin::Unknown).await?;
2411 if let Some(id) = removed_id {
2412 better_msg = if id == from_id {
2413 silent = true;
2414 Some(stock_str::msg_group_left_local(context, from_id).await)
2415 } else {
2416 Some(stock_str::msg_del_member_local(context, removed_addr, from_id).await)
2417 };
2418 } else {
2419 warn!(context, "Removed {removed_addr:?} has no contact id.")
2420 }
2421 } else if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) {
2422 if let Some(contact_id) =
2423 Contact::lookup_id_by_addr(context, added_addr, Origin::Unknown).await?
2424 {
2425 added_id = Some(contact_id);
2426 } else {
2427 warn!(context, "Added {added_addr:?} has no contact id.");
2428 }
2429
2430 better_msg = Some(stock_str::msg_add_member_local(context, added_addr, from_id).await);
2431 }
2432
2433 let group_name_timestamp = mime_parser
2434 .get_header(HeaderDef::ChatGroupNameTimestamp)
2435 .and_then(|s| s.parse::<i64>().ok());
2436 if let Some(old_name) = mime_parser
2437 .get_header(HeaderDef::ChatGroupNameChanged)
2438 .map(|s| s.trim())
2439 .or(match group_name_timestamp {
2440 Some(0) => None,
2441 Some(_) => Some(chat.name.as_str()),
2442 None => None,
2443 })
2444 {
2445 if let Some(grpname) = mime_parser
2446 .get_header(HeaderDef::ChatGroupName)
2447 .map(|grpname| grpname.trim())
2448 .filter(|grpname| grpname.len() < 200)
2449 {
2450 let grpname = &sanitize_single_line(grpname);
2451
2452 let chat_group_name_timestamp =
2453 chat.param.get_i64(Param::GroupNameTimestamp).unwrap_or(0);
2454 let group_name_timestamp = group_name_timestamp.unwrap_or(mime_parser.timestamp_sent);
2455 if (chat_group_name_timestamp, grpname) < (group_name_timestamp, &chat.name)
2457 && chat_id
2458 .update_timestamp(context, Param::GroupNameTimestamp, group_name_timestamp)
2459 .await?
2460 && grpname != &chat.name
2461 {
2462 info!(context, "Updating grpname for chat {chat_id}.");
2463 context
2464 .sql
2465 .execute("UPDATE chats SET name=? WHERE id=?;", (grpname, chat_id))
2466 .await?;
2467 send_event_chat_modified = true;
2468 }
2469 if mime_parser
2470 .get_header(HeaderDef::ChatGroupNameChanged)
2471 .is_some()
2472 {
2473 let old_name = &sanitize_single_line(old_name);
2474 better_msg.get_or_insert(
2475 stock_str::msg_grp_name(context, old_name, grpname, from_id).await,
2476 );
2477 }
2478 }
2479 }
2480
2481 if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg) {
2482 if value == "group-avatar-changed" {
2483 if let Some(avatar_action) = &mime_parser.group_avatar {
2484 better_msg = match avatar_action {
2487 AvatarAction::Delete => {
2488 Some(stock_str::msg_grp_img_deleted(context, from_id).await)
2489 }
2490 AvatarAction::Change(_) => {
2491 Some(stock_str::msg_grp_img_changed(context, from_id).await)
2492 }
2493 };
2494 }
2495 }
2496 }
2497
2498 if is_from_in_chat {
2499 if chat.member_list_is_stale(context).await? {
2500 info!(context, "Member list is stale.");
2501 let mut new_members: HashSet<ContactId> = HashSet::from_iter(to_ids.iter().copied());
2502 new_members.insert(ContactId::SELF);
2503 if !from_id.is_special() {
2504 new_members.insert(from_id);
2505 }
2506
2507 context
2508 .sql
2509 .transaction(|transaction| {
2510 transaction.execute(
2512 "DELETE FROM chats_contacts
2513 WHERE chat_id=?",
2514 (chat_id,),
2515 )?;
2516
2517 let mut statement = transaction.prepare(
2519 "INSERT INTO chats_contacts (chat_id, contact_id)
2520 VALUES (?, ?)",
2521 )?;
2522 for contact_id in &new_members {
2523 statement.execute((chat_id, contact_id))?;
2524 }
2525
2526 Ok(())
2527 })
2528 .await?;
2529 send_event_chat_modified = true;
2530 } else if let Some(ref chat_group_member_timestamps) =
2531 mime_parser.chat_group_member_timestamps()
2532 {
2533 send_event_chat_modified |= update_chats_contacts_timestamps(
2534 context,
2535 chat_id,
2536 Some(from_id),
2537 to_ids,
2538 past_ids,
2539 chat_group_member_timestamps,
2540 )
2541 .await?;
2542 } else {
2543 let mut new_members;
2544 if self_added {
2545 new_members = HashSet::from_iter(to_ids.iter().copied());
2546 new_members.insert(ContactId::SELF);
2547 if !from_id.is_special() {
2548 new_members.insert(from_id);
2549 }
2550 } else {
2551 new_members = chat_contacts.clone();
2552 }
2553
2554 if mime_parser.get_header(HeaderDef::ChatVersion).is_none() {
2556 new_members.extend(to_ids.iter());
2559 }
2560
2561 if let Some(added_id) = added_id {
2563 new_members.insert(added_id);
2564 }
2565
2566 if let Some(removed_id) = removed_id {
2568 new_members.remove(&removed_id);
2569 }
2570
2571 if new_members != chat_contacts {
2572 chat::update_chat_contacts_table(
2573 context,
2574 mime_parser.timestamp_sent,
2575 chat_id,
2576 &new_members,
2577 )
2578 .await?;
2579 send_event_chat_modified = true;
2580 }
2581 }
2582
2583 chat_id
2584 .update_timestamp(
2585 context,
2586 Param::MemberListTimestamp,
2587 mime_parser.timestamp_sent,
2588 )
2589 .await?;
2590 }
2591
2592 let new_chat_contacts = HashSet::<ContactId>::from_iter(
2593 chat::get_chat_contacts(context, chat_id)
2594 .await?
2595 .iter()
2596 .copied(),
2597 );
2598
2599 let mut added_ids: HashSet<ContactId> = new_chat_contacts
2601 .difference(&chat_contacts)
2602 .copied()
2603 .collect();
2604 let mut removed_ids: HashSet<ContactId> = chat_contacts
2605 .difference(&new_chat_contacts)
2606 .copied()
2607 .collect();
2608
2609 if let Some(added_id) = added_id {
2610 if !added_ids.remove(&added_id) && !self_added {
2611 better_msg = Some(String::new());
2615 }
2616 }
2617 if let Some(removed_id) = removed_id {
2618 removed_ids.remove(&removed_id);
2619 }
2620 let group_changes_msgs = if self_added {
2621 Vec::new()
2622 } else {
2623 group_changes_msgs(context, &added_ids, &removed_ids, chat_id).await?
2624 };
2625
2626 if let Some(avatar_action) = &mime_parser.group_avatar {
2627 if !new_chat_contacts.contains(&ContactId::SELF) {
2628 warn!(
2629 context,
2630 "Received group avatar update for group chat {chat_id} we are not a member of."
2631 );
2632 } else if !new_chat_contacts.contains(&from_id) {
2633 warn!(
2634 context,
2635 "Contact {from_id} attempts to modify group chat {chat_id} avatar without being a member.",
2636 );
2637 } else {
2638 info!(context, "Group-avatar change for {chat_id}.");
2639 if chat
2640 .param
2641 .update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
2642 {
2643 match avatar_action {
2644 AvatarAction::Change(profile_image) => {
2645 chat.param.set(Param::ProfileImage, profile_image);
2646 }
2647 AvatarAction::Delete => {
2648 chat.param.remove(Param::ProfileImage);
2649 }
2650 };
2651 chat.update_param(context).await?;
2652 send_event_chat_modified = true;
2653 }
2654 }
2655 }
2656
2657 if send_event_chat_modified {
2658 context.emit_event(EventType::ChatModified(chat_id));
2659 chatlist_events::emit_chatlist_item_changed(context, chat_id);
2660 }
2661 Ok(GroupChangesInfo {
2662 better_msg,
2663 added_removed_id: if added_id.is_some() {
2664 added_id
2665 } else {
2666 removed_id
2667 },
2668 silent,
2669 extra_msgs: group_changes_msgs,
2670 })
2671}
2672
2673async fn group_changes_msgs(
2675 context: &Context,
2676 added_ids: &HashSet<ContactId>,
2677 removed_ids: &HashSet<ContactId>,
2678 chat_id: ChatId,
2679) -> Result<Vec<(String, SystemMessage, Option<ContactId>)>> {
2680 let mut group_changes_msgs = Vec::new();
2681 if !added_ids.is_empty() {
2682 warn!(
2683 context,
2684 "Implicit addition of {added_ids:?} to chat {chat_id}."
2685 );
2686 }
2687 if !removed_ids.is_empty() {
2688 warn!(
2689 context,
2690 "Implicit removal of {removed_ids:?} from chat {chat_id}."
2691 );
2692 }
2693 group_changes_msgs.reserve(added_ids.len() + removed_ids.len());
2694 for contact_id in added_ids {
2695 let contact = Contact::get_by_id(context, *contact_id).await?;
2696 group_changes_msgs.push((
2697 stock_str::msg_add_member_local(context, contact.get_addr(), ContactId::UNDEFINED)
2698 .await,
2699 SystemMessage::MemberAddedToGroup,
2700 Some(contact.id),
2701 ));
2702 }
2703 for contact_id in removed_ids {
2704 let contact = Contact::get_by_id(context, *contact_id).await?;
2705 group_changes_msgs.push((
2706 stock_str::msg_del_member_local(context, contact.get_addr(), ContactId::UNDEFINED)
2707 .await,
2708 SystemMessage::MemberRemovedFromGroup,
2709 Some(contact.id),
2710 ));
2711 }
2712
2713 Ok(group_changes_msgs)
2714}
2715
2716static LIST_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
2717
2718fn mailinglist_header_listid(list_id_header: &str) -> Result<String> {
2719 Ok(match LIST_ID_REGEX.captures(list_id_header) {
2720 Some(cap) => cap.get(2).context("no match??")?.as_str().trim(),
2721 None => list_id_header
2722 .trim()
2723 .trim_start_matches('<')
2724 .trim_end_matches('>'),
2725 }
2726 .to_string())
2727}
2728
2729async fn create_or_lookup_mailinglist(
2739 context: &Context,
2740 allow_creation: bool,
2741 list_id_header: &str,
2742 mime_parser: &MimeMessage,
2743) -> Result<Option<(ChatId, Blocked)>> {
2744 let listid = mailinglist_header_listid(list_id_header)?;
2745
2746 if let Some((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
2747 return Ok(Some((chat_id, blocked)));
2748 }
2749
2750 let name = compute_mailinglist_name(list_id_header, &listid, mime_parser);
2751
2752 if allow_creation {
2753 let param = mime_parser.list_post.as_ref().map(|list_post| {
2755 let mut p = Params::new();
2756 p.set(Param::ListPost, list_post);
2757 p.to_string()
2758 });
2759
2760 let is_bot = context.get_config_bool(Config::Bot).await?;
2761 let blocked = if is_bot {
2762 Blocked::Not
2763 } else {
2764 Blocked::Request
2765 };
2766 let chat_id = ChatId::create_multiuser_record(
2767 context,
2768 Chattype::Mailinglist,
2769 &listid,
2770 &name,
2771 blocked,
2772 ProtectionStatus::Unprotected,
2773 param,
2774 mime_parser.timestamp_sent,
2775 )
2776 .await
2777 .with_context(|| {
2778 format!(
2779 "failed to create mailinglist '{}' for grpid={}",
2780 &name, &listid
2781 )
2782 })?;
2783
2784 chat::add_to_chat_contacts_table(
2785 context,
2786 mime_parser.timestamp_sent,
2787 chat_id,
2788 &[ContactId::SELF],
2789 )
2790 .await?;
2791 Ok(Some((chat_id, blocked)))
2792 } else {
2793 info!(context, "Creating list forbidden by caller.");
2794 Ok(None)
2795 }
2796}
2797
2798fn compute_mailinglist_name(
2799 list_id_header: &str,
2800 listid: &str,
2801 mime_parser: &MimeMessage,
2802) -> String {
2803 let mut name = match LIST_ID_REGEX
2804 .captures(list_id_header)
2805 .and_then(|caps| caps.get(1))
2806 {
2807 Some(cap) => cap.as_str().trim().to_string(),
2808 None => "".to_string(),
2809 };
2810
2811 if listid.ends_with(".list-id.mcsv.net") {
2815 if let Some(display_name) = &mime_parser.from.display_name {
2816 name.clone_from(display_name);
2817 }
2818 }
2819
2820 let subject = mime_parser.get_subject().unwrap_or_default();
2824 static SUBJECT: LazyLock<Regex> =
2825 LazyLock::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); if let Some(cap) = SUBJECT.captures(&subject) {
2827 name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
2828 }
2829
2830 if name.is_empty()
2837 && (mime_parser.from.addr.contains("noreply")
2838 || mime_parser.from.addr.contains("no-reply")
2839 || mime_parser.from.addr.starts_with("notifications@")
2840 || mime_parser.from.addr.starts_with("newsletter@")
2841 || listid.ends_with(".xt.local"))
2842 {
2843 if let Some(display_name) = &mime_parser.from.display_name {
2844 name.clone_from(display_name);
2845 }
2846 }
2847
2848 if name.is_empty() {
2851 static PREFIX_32_CHARS_HEX: LazyLock<Regex> =
2853 LazyLock::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
2854 if let Some(cap) = PREFIX_32_CHARS_HEX
2855 .captures(listid)
2856 .and_then(|caps| caps.get(2))
2857 {
2858 name = cap.as_str().to_string();
2859 } else {
2860 name = listid.to_string();
2861 }
2862 }
2863
2864 sanitize_single_line(&name)
2865}
2866
2867async fn apply_mailinglist_changes(
2871 context: &Context,
2872 mime_parser: &MimeMessage,
2873 chat_id: ChatId,
2874) -> Result<()> {
2875 let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
2876 return Ok(());
2877 };
2878
2879 let mut chat = Chat::load_from_db(context, chat_id).await?;
2880 if chat.typ != Chattype::Mailinglist {
2881 return Ok(());
2882 }
2883 let listid = &chat.grpid;
2884
2885 let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
2886 if chat.name != new_name
2887 && chat_id
2888 .update_timestamp(
2889 context,
2890 Param::GroupNameTimestamp,
2891 mime_parser.timestamp_sent,
2892 )
2893 .await?
2894 {
2895 info!(context, "Updating listname for chat {chat_id}.");
2896 context
2897 .sql
2898 .execute("UPDATE chats SET name=? WHERE id=?;", (new_name, chat_id))
2899 .await?;
2900 context.emit_event(EventType::ChatModified(chat_id));
2901 }
2902
2903 let Some(list_post) = &mime_parser.list_post else {
2904 return Ok(());
2905 };
2906
2907 let list_post = match ContactAddress::new(list_post) {
2908 Ok(list_post) => list_post,
2909 Err(err) => {
2910 warn!(context, "Invalid List-Post: {:#}.", err);
2911 return Ok(());
2912 }
2913 };
2914 let (contact_id, _) = Contact::add_or_lookup(context, "", &list_post, Origin::Hidden).await?;
2915 let mut contact = Contact::get_by_id(context, contact_id).await?;
2916 if contact.param.get(Param::ListId) != Some(listid) {
2917 contact.param.set(Param::ListId, listid);
2918 contact.update_param(context).await?;
2919 }
2920
2921 if let Some(old_list_post) = chat.param.get(Param::ListPost) {
2922 if list_post.as_ref() != old_list_post {
2923 chat.param.remove(Param::ListPost);
2926 chat.update_param(context).await?;
2927 }
2928 } else {
2929 chat.param.set(Param::ListPost, list_post);
2930 chat.update_param(context).await?;
2931 }
2932
2933 Ok(())
2934}
2935
2936async fn create_adhoc_group(
2938 context: &Context,
2939 mime_parser: &MimeMessage,
2940 create_blocked: Blocked,
2941 from_id: ContactId,
2942 to_ids: &[ContactId],
2943 grpname: &str,
2944) -> Result<Option<(ChatId, Blocked)>> {
2945 let mut member_ids: Vec<ContactId> = to_ids.to_vec();
2946 if !member_ids.contains(&(from_id)) {
2947 member_ids.push(from_id);
2948 }
2949 if !member_ids.contains(&(ContactId::SELF)) {
2950 member_ids.push(ContactId::SELF);
2951 }
2952
2953 if mime_parser.is_mailinglist_message() {
2954 return Ok(None);
2955 }
2956 if mime_parser
2957 .get_header(HeaderDef::ChatGroupMemberRemoved)
2958 .is_some()
2959 {
2960 info!(
2961 context,
2962 "Message removes member from unknown ad-hoc group (TRASH)."
2963 );
2964 return Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)));
2965 }
2966 if member_ids.len() < 3 {
2967 return Ok(None);
2968 }
2969
2970 let new_chat_id: ChatId = ChatId::create_multiuser_record(
2971 context,
2972 Chattype::Group,
2973 "", grpname,
2975 create_blocked,
2976 ProtectionStatus::Unprotected,
2977 None,
2978 mime_parser.timestamp_sent,
2979 )
2980 .await?;
2981
2982 info!(
2983 context,
2984 "Created ad-hoc group id={new_chat_id}, name={grpname:?}."
2985 );
2986 chat::add_to_chat_contacts_table(
2987 context,
2988 mime_parser.timestamp_sent,
2989 new_chat_id,
2990 &member_ids,
2991 )
2992 .await?;
2993
2994 context.emit_event(EventType::ChatModified(new_chat_id));
2995 chatlist_events::emit_chatlist_changed(context);
2996 chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
2997
2998 Ok(Some((new_chat_id, create_blocked)))
2999}
3000
3001#[derive(Debug, PartialEq, Eq)]
3002enum VerifiedEncryption {
3003 Verified,
3004 NotVerified(String), }
3006
3007async fn update_verified_keys(
3011 context: &Context,
3012 mimeparser: &mut MimeMessage,
3013 from_id: ContactId,
3014) -> Result<Option<String>> {
3015 if from_id == ContactId::SELF {
3016 return Ok(None);
3017 }
3018
3019 if !mimeparser.was_encrypted() {
3020 return Ok(None);
3021 }
3022
3023 let Some(peerstate) = &mut mimeparser.peerstate else {
3024 return Ok(None);
3026 };
3027
3028 let signed_with_primary_verified_key = peerstate
3029 .verified_key_fingerprint
3030 .as_ref()
3031 .filter(|fp| mimeparser.signatures.contains(fp))
3032 .is_some();
3033 let signed_with_secondary_verified_key = peerstate
3034 .secondary_verified_key_fingerprint
3035 .as_ref()
3036 .filter(|fp| mimeparser.signatures.contains(fp))
3037 .is_some();
3038
3039 if signed_with_primary_verified_key {
3040 if peerstate.secondary_verified_key.is_some()
3042 || peerstate.secondary_verified_key_fingerprint.is_some()
3043 || peerstate.secondary_verifier.is_some()
3044 {
3045 peerstate.secondary_verified_key = None;
3046 peerstate.secondary_verified_key_fingerprint = None;
3047 peerstate.secondary_verifier = None;
3048 peerstate.save_to_db(&context.sql).await?;
3049 }
3050
3051 Ok(None)
3053 } else if signed_with_secondary_verified_key {
3054 peerstate.verified_key = peerstate.secondary_verified_key.take();
3055 peerstate.verified_key_fingerprint = peerstate.secondary_verified_key_fingerprint.take();
3056 peerstate.verifier = peerstate.secondary_verifier.take();
3057 peerstate.fingerprint_changed = true;
3058 peerstate.save_to_db(&context.sql).await?;
3059
3060 Ok(None)
3062 } else {
3063 Ok(None)
3064 }
3065}
3066
3067fn has_verified_encryption(
3071 mimeparser: &MimeMessage,
3072 from_id: ContactId,
3073) -> Result<VerifiedEncryption> {
3074 use VerifiedEncryption::*;
3075
3076 if !mimeparser.was_encrypted() {
3077 return Ok(NotVerified("This message is not encrypted".to_string()));
3078 };
3079
3080 if from_id != ContactId::SELF {
3085 let Some(peerstate) = &mimeparser.peerstate else {
3086 return Ok(NotVerified(
3087 "No peerstate, the contact isn't verified".to_string(),
3088 ));
3089 };
3090
3091 let signed_with_verified_key = peerstate
3092 .verified_key_fingerprint
3093 .as_ref()
3094 .filter(|fp| mimeparser.signatures.contains(fp))
3095 .is_some();
3096
3097 if !signed_with_verified_key {
3098 return Ok(NotVerified(
3099 "The message was sent with non-verified encryption".to_string(),
3100 ));
3101 }
3102 }
3103
3104 Ok(Verified)
3105}
3106
3107async fn mark_recipients_as_verified(
3108 context: &Context,
3109 from_id: ContactId,
3110 to_ids: &[ContactId],
3111 mimeparser: &MimeMessage,
3112) -> Result<()> {
3113 if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
3114 return Ok(());
3115 }
3116 let contact = Contact::get_by_id(context, from_id).await?;
3117 for &id in to_ids {
3118 if id == ContactId::SELF {
3119 continue;
3120 }
3121
3122 let Some((to_addr, is_verified)) = context
3123 .sql
3124 .query_row_optional(
3125 "SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c
3126 LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id=?",
3127 (id,),
3128 |row| {
3129 let to_addr: String = row.get(0)?;
3130 let is_verified: i32 = row.get(1).unwrap_or(0);
3131 Ok((to_addr, is_verified != 0))
3132 },
3133 )
3134 .await?
3135 else {
3136 continue;
3137 };
3138 if let Some(gossiped_key) = mimeparser.gossiped_keys.get(&to_addr.to_lowercase()) {
3140 if let Some(mut peerstate) = Peerstate::from_addr(context, &to_addr).await? {
3141 let verifier_addr = contact.get_addr().to_owned();
3151 if !is_verified {
3152 info!(context, "{verifier_addr} has verified {to_addr}.");
3153 if let Some(fp) = peerstate.gossip_key_fingerprint.clone() {
3154 peerstate.set_verified(gossiped_key.clone(), fp, verifier_addr)?;
3155 peerstate.backward_verified_key_id =
3156 Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
3157 peerstate.save_to_db(&context.sql).await?;
3158
3159 let (to_contact_id, _) = Contact::add_or_lookup(
3160 context,
3161 "",
3162 &ContactAddress::new(&to_addr)?,
3163 Origin::Hidden,
3164 )
3165 .await?;
3166 ChatId::set_protection_for_contact(
3167 context,
3168 to_contact_id,
3169 mimeparser.timestamp_sent,
3170 )
3171 .await?;
3172 }
3173 } else {
3174 peerstate.set_secondary_verified_key(gossiped_key.clone(), verifier_addr);
3177 peerstate.save_to_db(&context.sql).await?;
3178 }
3179 }
3180 }
3181 }
3182
3183 Ok(())
3184}
3185
3186async fn get_previous_message(
3190 context: &Context,
3191 mime_parser: &MimeMessage,
3192) -> Result<Option<Message>> {
3193 if let Some(field) = mime_parser.get_header(HeaderDef::References) {
3194 if let Some(rfc724mid) = parse_message_ids(field).last() {
3195 if let Some((msg_id, _)) = rfc724_mid_exists(context, rfc724mid).await? {
3196 return Message::load_from_db_optional(context, msg_id).await;
3197 }
3198 }
3199 }
3200 Ok(None)
3201}
3202
3203async fn get_parent_message(
3208 context: &Context,
3209 references: Option<&str>,
3210 in_reply_to: Option<&str>,
3211) -> Result<Option<Message>> {
3212 let mut mids = Vec::new();
3213 if let Some(field) = in_reply_to {
3214 mids = parse_message_ids(field);
3215 }
3216 if let Some(field) = references {
3217 mids.append(&mut parse_message_ids(field));
3218 }
3219 message::get_by_rfc724_mids(context, &mids).await
3220}
3221
3222pub(crate) async fn get_prefetch_parent_message(
3223 context: &Context,
3224 headers: &[mailparse::MailHeader<'_>],
3225) -> Result<Option<Message>> {
3226 get_parent_message(
3227 context,
3228 headers.get_header_value(HeaderDef::References).as_deref(),
3229 headers.get_header_value(HeaderDef::InReplyTo).as_deref(),
3230 )
3231 .await
3232}
3233
3234async fn add_or_lookup_contacts_by_address_list(
3236 context: &Context,
3237 address_list: &[SingleInfo],
3238 origin: Origin,
3239) -> Result<Vec<ContactId>> {
3240 let mut contact_ids = Vec::new();
3241 for info in address_list {
3242 let addr = &info.addr;
3243 if !may_be_valid_addr(addr) {
3244 continue;
3245 }
3246 let display_name = info.display_name.as_deref();
3247 if let Ok(addr) = ContactAddress::new(addr) {
3248 let (contact_id, _) =
3249 Contact::add_or_lookup(context, display_name.unwrap_or_default(), &addr, origin)
3250 .await?;
3251 contact_ids.push(contact_id);
3252 } else {
3253 warn!(context, "Contact with address {:?} cannot exist.", addr);
3254 }
3255 }
3256
3257 Ok(contact_ids)
3258}
3259
3260#[cfg(test)]
3261mod receive_imf_tests;