1use std::cmp::min;
4use std::collections::{BTreeMap, HashMap, HashSet};
5use std::path::Path;
6use std::str;
7use std::str::FromStr;
8
9use anyhow::{Context as _, Result, bail};
10use deltachat_contact_tools::{addr_cmp, addr_normalize, sanitize_bidi_characters};
11use deltachat_derive::{FromSql, ToSql};
12use format_flowed::unformat_flowed;
13use mailparse::{DispositionType, MailHeader, MailHeaderMap, SingleInfo, addrparse_header};
14use mime::Mime;
15
16use crate::aheader::Aheader;
17use crate::authres::handle_authres;
18use crate::blob::BlobObject;
19use crate::chat::ChatId;
20use crate::config::Config;
21use crate::constants;
22use crate::contact::ContactId;
23use crate::context::Context;
24use crate::decrypt::{try_decrypt, validate_detached_signature};
25use crate::dehtml::dehtml;
26use crate::events::EventType;
27use crate::headerdef::{HeaderDef, HeaderDefMap};
28use crate::key::{self, DcKey, Fingerprint, SignedPublicKey, load_self_secret_keyring};
29use crate::log::warn;
30use crate::message::{self, Message, MsgId, Viewtype, get_vcard_summary, set_msg_failed};
31use crate::param::{Param, Params};
32use crate::simplify::{SimplifiedText, simplify};
33use crate::sync::SyncItems;
34use crate::tools::{
35 get_filemeta, parse_receive_headers, smeared_time, time, truncate_msg_text, validate_id,
36};
37use crate::{chatlist_events, location, tools};
38
39#[derive(Debug)]
42pub struct GossipedKey {
43 pub public_key: SignedPublicKey,
45
46 pub verified: bool,
48}
49
50#[derive(Debug)]
60pub(crate) struct MimeMessage {
61 pub parts: Vec<Part>,
63
64 headers: HashMap<String, String>,
66
67 #[cfg(test)]
68 headers_removed: HashSet<String>,
70
71 pub recipients: Vec<SingleInfo>,
75
76 pub past_members: Vec<SingleInfo>,
78
79 pub from: SingleInfo,
81
82 pub incoming: bool,
84 pub list_post: Option<String>,
87 pub chat_disposition_notification_to: Option<SingleInfo>,
88 pub decrypting_failed: bool,
89
90 pub signature: Option<Fingerprint>,
96
97 pub gossiped_keys: BTreeMap<String, GossipedKey>,
100
101 pub autocrypt_fingerprint: Option<String>,
105
106 pub is_forwarded: bool,
108 pub is_system_message: SystemMessage,
109 pub location_kml: Option<location::Kml>,
110 pub message_kml: Option<location::Kml>,
111 pub(crate) sync_items: Option<SyncItems>,
112 pub(crate) webxdc_status_update: Option<String>,
113 pub(crate) user_avatar: Option<AvatarAction>,
114 pub(crate) group_avatar: Option<AvatarAction>,
115 pub(crate) mdn_reports: Vec<Report>,
116 pub(crate) delivery_report: Option<DeliveryReport>,
117
118 pub(crate) footer: Option<String>,
123
124 pub is_mime_modified: bool,
127
128 pub decoded_data: Vec<u8>,
130
131 pub(crate) hop_info: String,
133
134 pub(crate) is_bot: Option<bool>,
144
145 pub(crate) timestamp_rcvd: i64,
147 pub(crate) timestamp_sent: i64,
150}
151
152#[derive(Debug, PartialEq)]
153pub(crate) enum AvatarAction {
154 Delete,
155 Change(String),
156}
157
158#[derive(
160 Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql,
161)]
162#[repr(u32)]
163pub enum SystemMessage {
164 #[default]
166 Unknown = 0,
167
168 GroupNameChanged = 2,
170
171 GroupImageChanged = 3,
173
174 MemberAddedToGroup = 4,
176
177 MemberRemovedFromGroup = 5,
179
180 AutocryptSetupMessage = 6,
182
183 SecurejoinMessage = 7,
185
186 LocationStreamingEnabled = 8,
188
189 LocationOnly = 9,
191
192 EphemeralTimerChanged = 10,
194
195 ChatProtectionEnabled = 11,
197
198 ChatProtectionDisabled = 12,
200
201 InvalidUnencryptedMail = 13,
204
205 SecurejoinWait = 14,
208
209 SecurejoinWaitTimeout = 15,
212
213 MultiDeviceSync = 20,
216
217 WebxdcStatusUpdate = 30,
221
222 WebxdcInfoMessage = 32,
224
225 IrohNodeAddr = 40,
227
228 ChatE2ee = 50,
230
231 CallAccepted = 66,
233
234 CallEnded = 67,
236}
237
238const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
239
240impl MimeMessage {
241 pub(crate) async fn from_bytes(
245 context: &Context,
246 body: &[u8],
247 partial: Option<u32>,
248 ) -> Result<Self> {
249 let mail = mailparse::parse_mail(body)?;
250
251 let timestamp_rcvd = smeared_time(context);
252 let mut timestamp_sent =
253 Self::get_timestamp_sent(&mail.headers, timestamp_rcvd, timestamp_rcvd);
254 let mut hop_info = parse_receive_headers(&mail.get_headers());
255
256 let mut headers = Default::default();
257 let mut headers_removed = HashSet::<String>::new();
258 let mut recipients = Default::default();
259 let mut past_members = Default::default();
260 let mut from = Default::default();
261 let mut list_post = Default::default();
262 let mut chat_disposition_notification_to = None;
263
264 MimeMessage::merge_headers(
266 context,
267 &mut headers,
268 &mut headers_removed,
269 &mut recipients,
270 &mut past_members,
271 &mut from,
272 &mut list_post,
273 &mut chat_disposition_notification_to,
274 &mail,
275 );
276 headers_removed.extend(
277 headers
278 .extract_if(|k, _v| is_hidden(k))
279 .map(|(k, _v)| k.to_string()),
280 );
281
282 let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
284 let (part, mimetype) =
285 if mimetype.type_() == mime::MULTIPART && mimetype.subtype().as_str() == "signed" {
286 if let Some(part) = mail.subparts.first() {
287 timestamp_sent =
291 Self::get_timestamp_sent(&part.headers, timestamp_sent, timestamp_rcvd);
292 MimeMessage::merge_headers(
293 context,
294 &mut headers,
295 &mut headers_removed,
296 &mut recipients,
297 &mut past_members,
298 &mut from,
299 &mut list_post,
300 &mut chat_disposition_notification_to,
301 part,
302 );
303 (part, part.ctype.mimetype.parse::<Mime>()?)
304 } else {
305 (&mail, mimetype)
307 }
308 } else {
309 (&mail, mimetype)
311 };
312 if mimetype.type_() == mime::MULTIPART
313 && mimetype.subtype().as_str() == "mixed"
314 && let Some(part) = part.subparts.first()
315 {
316 for field in &part.headers {
317 let key = field.get_key().to_lowercase();
318 if !headers.contains_key(&key) && is_hidden(&key) || key == "message-id" {
319 headers.insert(key.to_string(), field.get_value());
320 }
321 }
322 }
323
324 if let Some(microsoft_message_id) = remove_header(
328 &mut headers,
329 HeaderDef::XMicrosoftOriginalMessageId.get_headername(),
330 &mut headers_removed,
331 ) {
332 headers.insert(
333 HeaderDef::MessageId.get_headername().to_string(),
334 microsoft_message_id,
335 );
336 }
337
338 Self::remove_secured_headers(&mut headers, &mut headers_removed);
341
342 let mut from = from.context("No from in message")?;
343 let private_keyring = load_self_secret_keyring(context).await?;
344
345 let dkim_results = handle_authres(context, &mail, &from.addr).await?;
346
347 let mut gossiped_keys = Default::default();
348 hop_info += "\n\n";
349 hop_info += &dkim_results.to_string();
350
351 let incoming = !context.is_self_addr(&from.addr).await?;
352
353 let mut aheader_values = mail.headers.get_all_values(HeaderDef::Autocrypt.into());
354
355 let mail_raw; let decrypted_msg; let secrets: Vec<String> = context
358 .sql
359 .query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
360 let secret: String = row.get(0)?;
361 Ok(secret)
362 })
363 .await?;
364
365 let (mail, is_encrypted) =
366 match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
367 Ok(Some(mut msg)) => {
368 mail_raw = msg.as_data_vec().unwrap_or_default();
369
370 let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
371 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
372 info!(
373 context,
374 "decrypted message mime-body:\n{}",
375 String::from_utf8_lossy(&mail_raw),
376 );
377 }
378
379 decrypted_msg = Some(msg);
380
381 timestamp_sent = Self::get_timestamp_sent(
382 &decrypted_mail.headers,
383 timestamp_sent,
384 timestamp_rcvd,
385 );
386
387 let protected_aheader_values = decrypted_mail
388 .headers
389 .get_all_values(HeaderDef::Autocrypt.into());
390 if !protected_aheader_values.is_empty() {
391 aheader_values = protected_aheader_values;
392 }
393
394 (Ok(decrypted_mail), true)
395 }
396 Ok(None) => {
397 mail_raw = Vec::new();
398 decrypted_msg = None;
399 (Ok(mail), false)
400 }
401 Err(err) => {
402 mail_raw = Vec::new();
403 decrypted_msg = None;
404 warn!(context, "decryption failed: {:#}", err);
405 (Err(err), false)
406 }
407 };
408
409 let mut autocrypt_header = None;
410 if incoming {
411 for val in aheader_values.iter().rev() {
413 autocrypt_header = match Aheader::from_str(val) {
414 Ok(header) if addr_cmp(&header.addr, &from.addr) => Some(header),
415 Ok(header) => {
416 warn!(
417 context,
418 "Autocrypt header address {:?} is not {:?}.", header.addr, from.addr
419 );
420 continue;
421 }
422 Err(err) => {
423 warn!(context, "Failed to parse Autocrypt header: {:#}.", err);
424 continue;
425 }
426 };
427 break;
428 }
429 }
430
431 let autocrypt_fingerprint = if let Some(autocrypt_header) = &autocrypt_header {
432 let fingerprint = autocrypt_header.public_key.dc_fingerprint().hex();
433 let inserted = context
434 .sql
435 .execute(
436 "INSERT INTO public_keys (fingerprint, public_key)
437 VALUES (?, ?)
438 ON CONFLICT (fingerprint)
439 DO NOTHING",
440 (&fingerprint, autocrypt_header.public_key.to_bytes()),
441 )
442 .await?;
443 if inserted > 0 {
444 info!(
445 context,
446 "Saved key with fingerprint {fingerprint} from the Autocrypt header"
447 );
448 }
449 Some(fingerprint)
450 } else {
451 None
452 };
453
454 let mut public_keyring = if incoming {
455 if let Some(autocrypt_header) = autocrypt_header {
456 vec![autocrypt_header.public_key]
457 } else {
458 vec![]
459 }
460 } else {
461 key::load_self_public_keyring(context).await?
462 };
463
464 if let Some(signature) = match &decrypted_msg {
465 Some(pgp::composed::Message::Literal { .. }) => None,
466 Some(pgp::composed::Message::Compressed { .. }) => {
467 None
470 }
471 Some(pgp::composed::Message::SignedOnePass { reader, .. }) => reader.signature(),
472 Some(pgp::composed::Message::Signed { reader, .. }) => Some(reader.signature()),
473 Some(pgp::composed::Message::Encrypted { .. }) => {
474 None
476 }
477 None => None,
478 } {
479 for issuer_fingerprint in signature.issuer_fingerprint() {
480 let issuer_fingerprint =
481 crate::key::Fingerprint::from(issuer_fingerprint.clone()).hex();
482 if let Some(public_key_bytes) = context
483 .sql
484 .query_row_optional(
485 "SELECT public_key
486 FROM public_keys
487 WHERE fingerprint=?",
488 (&issuer_fingerprint,),
489 |row| {
490 let bytes: Vec<u8> = row.get(0)?;
491 Ok(bytes)
492 },
493 )
494 .await?
495 {
496 let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
497 public_keyring.push(public_key)
498 }
499 }
500 }
501
502 let mut signatures = if let Some(ref decrypted_msg) = decrypted_msg {
503 crate::pgp::valid_signature_fingerprints(decrypted_msg, &public_keyring)
504 } else {
505 HashSet::new()
506 };
507
508 let mail = mail.as_ref().map(|mail| {
509 let (content, signatures_detached) = validate_detached_signature(mail, &public_keyring)
510 .unwrap_or((mail, Default::default()));
511 signatures.extend(signatures_detached);
512 content
513 });
514 if let (Ok(mail), true) = (mail, is_encrypted) {
515 if !signatures.is_empty() {
516 remove_header(&mut headers, "subject", &mut headers_removed);
520 remove_header(&mut headers, "list-id", &mut headers_removed);
521 }
522
523 let mut inner_from = None;
529
530 MimeMessage::merge_headers(
531 context,
532 &mut headers,
533 &mut headers_removed,
534 &mut recipients,
535 &mut past_members,
536 &mut inner_from,
537 &mut list_post,
538 &mut chat_disposition_notification_to,
539 mail,
540 );
541
542 if !signatures.is_empty() {
543 let gossip_headers = mail.headers.get_all_values("Autocrypt-Gossip");
548 gossiped_keys =
549 parse_gossip_headers(context, &from.addr, &recipients, gossip_headers).await?;
550 }
551
552 if let Some(inner_from) = inner_from {
553 if !addr_cmp(&inner_from.addr, &from.addr) {
554 warn!(
563 context,
564 "From header in encrypted part doesn't match the outer one",
565 );
566
567 bail!("From header is forged");
572 }
573 from = inner_from;
574 }
575 }
576 if signatures.is_empty() {
577 Self::remove_secured_headers(&mut headers, &mut headers_removed);
578 }
579 if !is_encrypted {
580 signatures.clear();
581 }
582
583 let mut parser = MimeMessage {
584 parts: Vec::new(),
585 headers,
586 #[cfg(test)]
587 headers_removed,
588
589 recipients,
590 past_members,
591 list_post,
592 from,
593 incoming,
594 chat_disposition_notification_to,
595 decrypting_failed: mail.is_err(),
596
597 signature: signatures.into_iter().last(),
599 autocrypt_fingerprint,
600 gossiped_keys,
601 is_forwarded: false,
602 mdn_reports: Vec::new(),
603 is_system_message: SystemMessage::Unknown,
604 location_kml: None,
605 message_kml: None,
606 sync_items: None,
607 webxdc_status_update: None,
608 user_avatar: None,
609 group_avatar: None,
610 delivery_report: None,
611 footer: None,
612 is_mime_modified: false,
613 decoded_data: Vec::new(),
614 hop_info,
615 is_bot: None,
616 timestamp_rcvd,
617 timestamp_sent,
618 };
619
620 match partial {
621 Some(org_bytes) => {
622 parser
623 .create_stub_from_partial_download(context, org_bytes)
624 .await?;
625 }
626 None => match mail {
627 Ok(mail) => {
628 parser.parse_mime_recursive(context, mail, false).await?;
629 }
630 Err(err) => {
631 let txt = "[This message cannot be decrypted.\n\n• It might already help to simply reply to this message and ask the sender to send the message again.\n\n• If you just re-installed Delta Chat then it is best if you re-setup Delta Chat now and choose \"Add as second device\" or import a backup.]";
632
633 let part = Part {
634 typ: Viewtype::Text,
635 msg_raw: Some(txt.to_string()),
636 msg: txt.to_string(),
637 error: Some(format!("Decrypting failed: {err:#}")),
640 ..Default::default()
641 };
642 parser.do_add_single_part(part);
643 }
644 },
645 };
646
647 let is_location_only = parser.location_kml.is_some() && parser.parts.is_empty();
648 if parser.mdn_reports.is_empty()
649 && !is_location_only
650 && parser.sync_items.is_none()
651 && parser.webxdc_status_update.is_none()
652 {
653 let is_bot =
654 parser.headers.get("auto-submitted") == Some(&"auto-generated".to_string());
655 parser.is_bot = Some(is_bot);
656 }
657 parser.maybe_remove_bad_parts();
658 parser.maybe_remove_inline_mailinglist_footer();
659 parser.heuristically_parse_ndn(context).await;
660 parser.parse_headers(context).await?;
661 parser.decoded_data = mail_raw;
662
663 Ok(parser)
664 }
665
666 fn get_timestamp_sent(
667 hdrs: &[mailparse::MailHeader<'_>],
668 default: i64,
669 timestamp_rcvd: i64,
670 ) -> i64 {
671 hdrs.get_header_value(HeaderDef::Date)
672 .and_then(|v| mailparse::dateparse(&v).ok())
673 .map_or(default, |value| {
674 min(value, timestamp_rcvd + constants::TIMESTAMP_SENT_TOLERANCE)
675 })
676 }
677
678 fn parse_system_message_headers(&mut self, context: &Context) {
680 if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() && !self.incoming {
681 self.parts.retain(|part| {
682 part.mimetype
683 .as_ref()
684 .is_none_or(|mimetype| mimetype.as_ref() == MIME_AC_SETUP_FILE)
685 });
686
687 if self.parts.len() == 1 {
688 self.is_system_message = SystemMessage::AutocryptSetupMessage;
689 } else {
690 warn!(context, "could not determine ASM mime-part");
691 }
692 } else if let Some(value) = self.get_header(HeaderDef::ChatContent) {
693 if value == "location-streaming-enabled" {
694 self.is_system_message = SystemMessage::LocationStreamingEnabled;
695 } else if value == "ephemeral-timer-changed" {
696 self.is_system_message = SystemMessage::EphemeralTimerChanged;
697 } else if value == "protection-enabled" {
698 self.is_system_message = SystemMessage::ChatProtectionEnabled;
699 } else if value == "protection-disabled" {
700 self.is_system_message = SystemMessage::ChatProtectionDisabled;
701 } else if value == "group-avatar-changed" {
702 self.is_system_message = SystemMessage::GroupImageChanged;
703 } else if value == "call-accepted" {
704 self.is_system_message = SystemMessage::CallAccepted;
705 } else if value == "call-ended" {
706 self.is_system_message = SystemMessage::CallEnded;
707 }
708 } else if self.get_header(HeaderDef::ChatGroupMemberRemoved).is_some() {
709 self.is_system_message = SystemMessage::MemberRemovedFromGroup;
710 } else if self.get_header(HeaderDef::ChatGroupMemberAdded).is_some() {
711 self.is_system_message = SystemMessage::MemberAddedToGroup;
712 } else if self.get_header(HeaderDef::ChatGroupNameChanged).is_some() {
713 self.is_system_message = SystemMessage::GroupNameChanged;
714 }
715 }
716
717 fn parse_avatar_headers(&mut self, context: &Context) -> Result<()> {
719 if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) {
720 self.group_avatar =
721 self.avatar_action_from_header(context, header_value.to_string())?;
722 }
723
724 if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) {
725 self.user_avatar = self.avatar_action_from_header(context, header_value.to_string())?;
726 }
727 Ok(())
728 }
729
730 fn parse_videochat_headers(&mut self) {
731 let content = self
732 .get_header(HeaderDef::ChatContent)
733 .unwrap_or_default()
734 .to_string();
735 let room = self
736 .get_header(HeaderDef::ChatWebrtcRoom)
737 .map(|s| s.to_string());
738 let accepted = self
739 .get_header(HeaderDef::ChatWebrtcAccepted)
740 .map(|s| s.to_string());
741 if let Some(part) = self.parts.first_mut() {
742 if let Some(room) = room {
743 if content == "call" {
744 part.typ = Viewtype::Call;
745 part.param.set(Param::WebrtcRoom, room);
746 }
747 } else if let Some(accepted) = accepted {
748 part.param.set(Param::WebrtcAccepted, accepted);
749 }
750 }
751 }
752
753 fn squash_attachment_parts(&mut self) {
759 if self.parts.len() == 2
760 && self.parts.first().map(|textpart| textpart.typ) == Some(Viewtype::Text)
761 && self
762 .parts
763 .get(1)
764 .is_some_and(|filepart| match filepart.typ {
765 Viewtype::Image
766 | Viewtype::Gif
767 | Viewtype::Sticker
768 | Viewtype::Audio
769 | Viewtype::Voice
770 | Viewtype::Video
771 | Viewtype::Vcard
772 | Viewtype::File
773 | Viewtype::Webxdc => true,
774 Viewtype::Unknown | Viewtype::Text | Viewtype::Call => false,
775 })
776 {
777 let mut parts = std::mem::take(&mut self.parts);
778 let Some(mut filepart) = parts.pop() else {
779 return;
781 };
782 let Some(textpart) = parts.pop() else {
783 return;
785 };
786
787 filepart.msg.clone_from(&textpart.msg);
788 if let Some(quote) = textpart.param.get(Param::Quote) {
789 filepart.param.set(Param::Quote, quote);
790 }
791
792 self.parts = vec![filepart];
793 }
794 }
795
796 fn parse_attachments(&mut self) {
798 if self.parts.len() != 1 {
801 return;
802 }
803
804 if let Some(mut part) = self.parts.pop() {
805 if part.typ == Viewtype::Audio && self.get_header(HeaderDef::ChatVoiceMessage).is_some()
806 {
807 part.typ = Viewtype::Voice;
808 }
809 if (part.typ == Viewtype::Image || part.typ == Viewtype::Gif)
810 && let Some(value) = self.get_header(HeaderDef::ChatContent)
811 && value == "sticker"
812 {
813 part.typ = Viewtype::Sticker;
814 }
815 if (part.typ == Viewtype::Audio
816 || part.typ == Viewtype::Voice
817 || part.typ == Viewtype::Video)
818 && let Some(field_0) = self.get_header(HeaderDef::ChatDuration)
819 {
820 let duration_ms = field_0.parse().unwrap_or_default();
821 if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
822 part.param.set_int(Param::Duration, duration_ms);
823 }
824 }
825
826 self.parts.push(part);
827 }
828 }
829
830 async fn parse_headers(&mut self, context: &Context) -> Result<()> {
831 self.parse_system_message_headers(context);
832 self.parse_avatar_headers(context)?;
833 self.parse_videochat_headers();
834 if self.delivery_report.is_none() {
835 self.squash_attachment_parts();
836 }
837
838 if !context.get_config_bool(Config::Bot).await?
839 && let Some(ref subject) = self.get_subject()
840 {
841 let mut prepend_subject = true;
842 if !self.decrypting_failed {
843 let colon = subject.find(':');
844 if colon == Some(2)
845 || colon == Some(3)
846 || self.has_chat_version()
847 || subject.contains("Chat:")
848 {
849 prepend_subject = false
850 }
851 }
852
853 if self.is_mailinglist_message() && !self.has_chat_version() {
856 prepend_subject = true;
857 }
858
859 if prepend_subject && !subject.is_empty() {
860 let part_with_text = self
861 .parts
862 .iter_mut()
863 .find(|part| !part.msg.is_empty() && !part.is_reaction);
864 if let Some(part) = part_with_text {
865 part.msg = format!("{} – {}", subject, part.msg);
870 }
871 }
872 }
873
874 if self.is_forwarded {
875 for part in &mut self.parts {
876 part.param.set_int(Param::Forwarded, 1);
877 }
878 }
879
880 self.parse_attachments();
881
882 if !self.decrypting_failed
884 && !self.parts.is_empty()
885 && let Some(ref dn_to) = self.chat_disposition_notification_to
886 {
887 let from = &self.from.addr;
889 if !context.is_self_addr(from).await? {
890 if from.to_lowercase() == dn_to.addr.to_lowercase() {
891 if let Some(part) = self.parts.last_mut() {
892 part.param.set_int(Param::WantsMdn, 1);
893 }
894 } else {
895 warn!(
896 context,
897 "{} requested a read receipt to {}, ignoring", from, dn_to.addr
898 );
899 }
900 }
901 }
902
903 if self.parts.is_empty() && self.mdn_reports.is_empty() {
908 let mut part = Part {
909 typ: Viewtype::Text,
910 ..Default::default()
911 };
912
913 if let Some(ref subject) = self.get_subject()
914 && !self.has_chat_version()
915 && self.webxdc_status_update.is_none()
916 {
917 part.msg = subject.to_string();
918 }
919
920 self.do_add_single_part(part);
921 }
922
923 if self.is_bot == Some(true) {
924 for part in &mut self.parts {
925 part.param.set(Param::Bot, "1");
926 }
927 }
928
929 Ok(())
930 }
931
932 fn avatar_action_from_header(
933 &mut self,
934 context: &Context,
935 header_value: String,
936 ) -> Result<Option<AvatarAction>> {
937 let res = if header_value == "0" {
938 Some(AvatarAction::Delete)
939 } else if let Some(base64) = header_value
940 .split_ascii_whitespace()
941 .collect::<String>()
942 .strip_prefix("base64:")
943 {
944 match BlobObject::store_from_base64(context, base64)? {
945 Some(path) => Some(AvatarAction::Change(path)),
946 None => {
947 warn!(context, "Could not decode avatar base64");
948 None
949 }
950 }
951 } else {
952 let mut i = 0;
955 while let Some(part) = self.parts.get_mut(i) {
956 if let Some(part_filename) = &part.org_filename
957 && part_filename == &header_value
958 {
959 if let Some(blob) = part.param.get(Param::File) {
960 let res = Some(AvatarAction::Change(blob.to_string()));
961 self.parts.remove(i);
962 return Ok(res);
963 }
964 break;
965 }
966 i += 1;
967 }
968 None
969 };
970 Ok(res)
971 }
972
973 pub fn was_encrypted(&self) -> bool {
979 self.signature.is_some()
980 }
981
982 pub(crate) fn has_chat_version(&self) -> bool {
985 self.headers.contains_key("chat-version")
986 }
987
988 pub(crate) fn get_subject(&self) -> Option<String> {
989 self.get_header(HeaderDef::Subject)
990 .map(|s| s.trim_start())
991 .filter(|s| !s.is_empty())
992 .map(|s| s.to_string())
993 }
994
995 pub fn get_header(&self, headerdef: HeaderDef) -> Option<&str> {
996 self.headers
997 .get(headerdef.get_headername())
998 .map(|s| s.as_str())
999 }
1000
1001 #[cfg(test)]
1002 pub(crate) fn header_exists(&self, headerdef: HeaderDef) -> bool {
1007 let hname = headerdef.get_headername();
1008 self.headers.contains_key(hname) || self.headers_removed.contains(hname)
1009 }
1010
1011 #[cfg(test)]
1012 pub(crate) fn decoded_data_contains(&self, s: &str) -> bool {
1014 assert!(!self.decrypting_failed);
1015 let decoded_str = str::from_utf8(&self.decoded_data).unwrap();
1016 decoded_str.contains(s)
1017 }
1018
1019 pub fn get_chat_group_id(&self) -> Option<&str> {
1021 self.get_header(HeaderDef::ChatGroupId)
1022 .filter(|s| validate_id(s))
1023 }
1024
1025 async fn parse_mime_recursive<'a>(
1026 &'a mut self,
1027 context: &'a Context,
1028 mail: &'a mailparse::ParsedMail<'a>,
1029 is_related: bool,
1030 ) -> Result<bool> {
1031 enum MimeS {
1032 Multiple,
1033 Single,
1034 Message,
1035 }
1036
1037 let mimetype = mail.ctype.mimetype.to_lowercase();
1038
1039 let m = if mimetype.starts_with("multipart") {
1040 if mail.ctype.params.contains_key("boundary") {
1041 MimeS::Multiple
1042 } else {
1043 MimeS::Single
1044 }
1045 } else if mimetype.starts_with("message") {
1046 if mimetype == "message/rfc822" && !is_attachment_disposition(mail) {
1047 MimeS::Message
1048 } else {
1049 MimeS::Single
1050 }
1051 } else {
1052 MimeS::Single
1053 };
1054
1055 let is_related = is_related || mimetype == "multipart/related";
1056 match m {
1057 MimeS::Multiple => Box::pin(self.handle_multiple(context, mail, is_related)).await,
1058 MimeS::Message => {
1059 let raw = mail.get_body_raw()?;
1060 if raw.is_empty() {
1061 return Ok(false);
1062 }
1063 let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
1064
1065 Box::pin(self.parse_mime_recursive(context, &mail, is_related)).await
1066 }
1067 MimeS::Single => {
1068 self.add_single_part_if_known(context, mail, is_related)
1069 .await
1070 }
1071 }
1072 }
1073
1074 async fn handle_multiple(
1075 &mut self,
1076 context: &Context,
1077 mail: &mailparse::ParsedMail<'_>,
1078 is_related: bool,
1079 ) -> Result<bool> {
1080 let mut any_part_added = false;
1081 let mimetype = get_mime_type(
1082 mail,
1083 &get_attachment_filename(context, mail)?,
1084 self.has_chat_version(),
1085 )?
1086 .0;
1087 match (mimetype.type_(), mimetype.subtype().as_str()) {
1088 (mime::MULTIPART, "alternative") => {
1089 for cur_data in mail.subparts.iter().rev() {
1101 let (mime_type, _viewtype) = get_mime_type(
1102 cur_data,
1103 &get_attachment_filename(context, cur_data)?,
1104 self.has_chat_version(),
1105 )?;
1106
1107 if mime_type == mime::TEXT_PLAIN || mime_type.type_() == mime::MULTIPART {
1108 any_part_added = self
1109 .parse_mime_recursive(context, cur_data, is_related)
1110 .await?;
1111 break;
1112 }
1113 }
1114
1115 for cur_data in mail.subparts.iter().rev() {
1124 let mimetype = cur_data.ctype.mimetype.parse::<Mime>()?;
1125 if mimetype.type_() == mime::TEXT && mimetype.subtype() == "calendar" {
1126 let filename = get_attachment_filename(context, cur_data)?
1127 .unwrap_or_else(|| "calendar.ics".to_string());
1128 self.do_add_single_file_part(
1129 context,
1130 Viewtype::File,
1131 mimetype,
1132 &mail.ctype.mimetype.to_lowercase(),
1133 &mail.get_body_raw()?,
1134 &filename,
1135 is_related,
1136 )
1137 .await?;
1138 }
1139 }
1140
1141 if !any_part_added {
1142 for cur_part in mail.subparts.iter().rev() {
1143 if self
1144 .parse_mime_recursive(context, cur_part, is_related)
1145 .await?
1146 {
1147 any_part_added = true;
1148 break;
1149 }
1150 }
1151 }
1152 if any_part_added && mail.subparts.len() > 1 {
1153 self.is_mime_modified = true;
1157 }
1158 }
1159 (mime::MULTIPART, "signed") => {
1160 if let Some(first) = mail.subparts.first() {
1169 any_part_added = self
1170 .parse_mime_recursive(context, first, is_related)
1171 .await?;
1172 }
1173 }
1174 (mime::MULTIPART, "report") => {
1175 if mail.subparts.len() >= 2 {
1177 match mail.ctype.params.get("report-type").map(|s| s as &str) {
1178 Some("disposition-notification") => {
1179 if let Some(report) = self.process_report(context, mail)? {
1180 self.mdn_reports.push(report);
1181 }
1182
1183 let part = Part {
1188 typ: Viewtype::Unknown,
1189 ..Default::default()
1190 };
1191 self.parts.push(part);
1192
1193 any_part_added = true;
1194 }
1195 Some("delivery-status") | None => {
1197 if let Some(report) = self.process_delivery_status(context, mail)? {
1198 self.delivery_report = Some(report);
1199 }
1200
1201 for cur_data in &mail.subparts {
1203 if self
1204 .parse_mime_recursive(context, cur_data, is_related)
1205 .await?
1206 {
1207 any_part_added = true;
1208 }
1209 }
1210 }
1211 Some("multi-device-sync") => {
1212 if let Some(second) = mail.subparts.get(1) {
1213 self.add_single_part_if_known(context, second, is_related)
1214 .await?;
1215 }
1216 }
1217 Some("status-update") => {
1218 if let Some(second) = mail.subparts.get(1) {
1219 self.add_single_part_if_known(context, second, is_related)
1220 .await?;
1221 }
1222 }
1223 Some(_) => {
1224 for cur_data in &mail.subparts {
1225 if self
1226 .parse_mime_recursive(context, cur_data, is_related)
1227 .await?
1228 {
1229 any_part_added = true;
1230 }
1231 }
1232 }
1233 }
1234 }
1235 }
1236 _ => {
1237 for cur_data in &mail.subparts {
1240 if self
1241 .parse_mime_recursive(context, cur_data, is_related)
1242 .await?
1243 {
1244 any_part_added = true;
1245 }
1246 }
1247 }
1248 }
1249
1250 Ok(any_part_added)
1251 }
1252
1253 async fn add_single_part_if_known(
1255 &mut self,
1256 context: &Context,
1257 mail: &mailparse::ParsedMail<'_>,
1258 is_related: bool,
1259 ) -> Result<bool> {
1260 let filename = get_attachment_filename(context, mail)?;
1262 let (mime_type, msg_type) = get_mime_type(mail, &filename, self.has_chat_version())?;
1263 let raw_mime = mail.ctype.mimetype.to_lowercase();
1264
1265 let old_part_count = self.parts.len();
1266
1267 match filename {
1268 Some(filename) => {
1269 self.do_add_single_file_part(
1270 context,
1271 msg_type,
1272 mime_type,
1273 &raw_mime,
1274 &mail.get_body_raw()?,
1275 &filename,
1276 is_related,
1277 )
1278 .await?;
1279 }
1280 None => {
1281 match mime_type.type_() {
1282 mime::IMAGE | mime::AUDIO | mime::VIDEO | mime::APPLICATION => {
1283 warn!(context, "Missing attachment");
1284 return Ok(false);
1285 }
1286 mime::TEXT
1287 if mail.get_content_disposition().disposition
1288 == DispositionType::Extension("reaction".to_string()) =>
1289 {
1290 let decoded_data = match mail.get_body() {
1292 Ok(decoded_data) => decoded_data,
1293 Err(err) => {
1294 warn!(context, "Invalid body parsed {:#}", err);
1295 return Ok(false);
1297 }
1298 };
1299
1300 let part = Part {
1301 typ: Viewtype::Text,
1302 mimetype: Some(mime_type),
1303 msg: decoded_data,
1304 is_reaction: true,
1305 ..Default::default()
1306 };
1307 self.do_add_single_part(part);
1308 return Ok(true);
1309 }
1310 mime::TEXT | mime::HTML => {
1311 let decoded_data = match mail.get_body() {
1312 Ok(decoded_data) => decoded_data,
1313 Err(err) => {
1314 warn!(context, "Invalid body parsed {:#}", err);
1315 return Ok(false);
1317 }
1318 };
1319
1320 let is_plaintext = mime_type == mime::TEXT_PLAIN;
1321 let mut dehtml_failed = false;
1322
1323 let SimplifiedText {
1324 text: simplified_txt,
1325 is_forwarded,
1326 is_cut,
1327 top_quote,
1328 footer,
1329 } = if decoded_data.is_empty() {
1330 Default::default()
1331 } else {
1332 let is_html = mime_type == mime::TEXT_HTML;
1333 if is_html {
1334 self.is_mime_modified = true;
1335 if let Some(text) = dehtml(&decoded_data) {
1340 text
1341 } else {
1342 dehtml_failed = true;
1343 SimplifiedText {
1344 text: decoded_data.clone(),
1345 ..Default::default()
1346 }
1347 }
1348 } else {
1349 simplify(decoded_data.clone(), self.has_chat_version())
1350 }
1351 };
1352
1353 self.is_mime_modified = self.is_mime_modified
1354 || ((is_forwarded || is_cut || top_quote.is_some())
1355 && !self.has_chat_version());
1356
1357 let is_format_flowed = if let Some(format) = mail.ctype.params.get("format")
1358 {
1359 format.as_str().eq_ignore_ascii_case("flowed")
1360 } else {
1361 false
1362 };
1363
1364 let (simplified_txt, simplified_quote) = if mime_type.type_() == mime::TEXT
1365 && mime_type.subtype() == mime::PLAIN
1366 {
1367 let simplified_txt = match mail
1370 .ctype
1371 .params
1372 .get("hp-legacy-display")
1373 .is_some_and(|v| v == "1")
1374 {
1375 false => simplified_txt,
1376 true => rm_legacy_display_elements(&simplified_txt),
1377 };
1378 if is_format_flowed {
1379 let delsp = if let Some(delsp) = mail.ctype.params.get("delsp") {
1380 delsp.as_str().eq_ignore_ascii_case("yes")
1381 } else {
1382 false
1383 };
1384 let unflowed_text = unformat_flowed(&simplified_txt, delsp);
1385 let unflowed_quote = top_quote.map(|q| unformat_flowed(&q, delsp));
1386 (unflowed_text, unflowed_quote)
1387 } else {
1388 (simplified_txt, top_quote)
1389 }
1390 } else {
1391 (simplified_txt, top_quote)
1392 };
1393
1394 let (simplified_txt, was_truncated) =
1395 truncate_msg_text(context, simplified_txt).await?;
1396 if was_truncated {
1397 self.is_mime_modified = was_truncated;
1398 }
1399
1400 if !simplified_txt.is_empty() || simplified_quote.is_some() {
1401 let mut part = Part {
1402 dehtml_failed,
1403 typ: Viewtype::Text,
1404 mimetype: Some(mime_type),
1405 msg: simplified_txt,
1406 ..Default::default()
1407 };
1408 if let Some(quote) = simplified_quote {
1409 part.param.set(Param::Quote, quote);
1410 }
1411 part.msg_raw = Some(decoded_data);
1412 self.do_add_single_part(part);
1413 }
1414
1415 if is_forwarded {
1416 self.is_forwarded = true;
1417 }
1418
1419 if self.footer.is_none() && is_plaintext {
1420 self.footer = Some(footer.unwrap_or_default());
1421 }
1422 }
1423 _ => {}
1424 }
1425 }
1426 }
1427
1428 Ok(self.parts.len() > old_part_count)
1430 }
1431
1432 #[expect(clippy::too_many_arguments)]
1433 async fn do_add_single_file_part(
1434 &mut self,
1435 context: &Context,
1436 msg_type: Viewtype,
1437 mime_type: Mime,
1438 raw_mime: &str,
1439 decoded_data: &[u8],
1440 filename: &str,
1441 is_related: bool,
1442 ) -> Result<()> {
1443 if mime_type.type_() == mime::APPLICATION
1445 && mime_type.subtype().as_str() == "pgp-keys"
1446 && Self::try_set_peer_key_from_file_part(context, decoded_data).await?
1447 {
1448 return Ok(());
1449 }
1450 let mut part = Part::default();
1451 let msg_type = if context
1452 .is_webxdc_file(filename, decoded_data)
1453 .await
1454 .unwrap_or(false)
1455 {
1456 Viewtype::Webxdc
1457 } else if filename.ends_with(".kml") {
1458 if filename.starts_with("location") || filename.starts_with("message") {
1461 let parsed = location::Kml::parse(decoded_data)
1462 .map_err(|err| {
1463 warn!(context, "failed to parse kml part: {:#}", err);
1464 })
1465 .ok();
1466 if filename.starts_with("location") {
1467 self.location_kml = parsed;
1468 } else {
1469 self.message_kml = parsed;
1470 }
1471 return Ok(());
1472 }
1473 msg_type
1474 } else if filename == "multi-device-sync.json" {
1475 if !context.get_config_bool(Config::SyncMsgs).await? {
1476 return Ok(());
1477 }
1478 let serialized = String::from_utf8_lossy(decoded_data)
1479 .parse()
1480 .unwrap_or_default();
1481 self.sync_items = context
1482 .parse_sync_items(serialized)
1483 .map_err(|err| {
1484 warn!(context, "failed to parse sync data: {:#}", err);
1485 })
1486 .ok();
1487 return Ok(());
1488 } else if filename == "status-update.json" {
1489 let serialized = String::from_utf8_lossy(decoded_data)
1490 .parse()
1491 .unwrap_or_default();
1492 self.webxdc_status_update = Some(serialized);
1493 return Ok(());
1494 } else if msg_type == Viewtype::Vcard {
1495 if let Some(summary) = get_vcard_summary(decoded_data) {
1496 part.param.set(Param::Summary1, summary);
1497 msg_type
1498 } else {
1499 Viewtype::File
1500 }
1501 } else if msg_type == Viewtype::Image
1502 || msg_type == Viewtype::Gif
1503 || msg_type == Viewtype::Sticker
1504 {
1505 match get_filemeta(decoded_data) {
1506 Ok((width, height)) if width * height <= constants::MAX_RCVD_IMAGE_PIXELS => {
1508 part.param.set_i64(Param::Width, width.into());
1509 part.param.set_i64(Param::Height, height.into());
1510 msg_type
1511 }
1512 _ => Viewtype::File,
1514 }
1515 } else {
1516 msg_type
1517 };
1518
1519 let blob =
1523 match BlobObject::create_and_deduplicate_from_bytes(context, decoded_data, filename) {
1524 Ok(blob) => blob,
1525 Err(err) => {
1526 error!(
1527 context,
1528 "Could not add blob for mime part {}, error {:#}", filename, err
1529 );
1530 return Ok(());
1531 }
1532 };
1533 info!(context, "added blobfile: {:?}", blob.as_name());
1534
1535 part.typ = msg_type;
1536 part.org_filename = Some(filename.to_string());
1537 part.mimetype = Some(mime_type);
1538 part.bytes = decoded_data.len();
1539 part.param.set(Param::File, blob.as_name());
1540 part.param.set(Param::Filename, filename);
1541 part.param.set(Param::MimeType, raw_mime);
1542 part.is_related = is_related;
1543
1544 self.do_add_single_part(part);
1545 Ok(())
1546 }
1547
1548 async fn try_set_peer_key_from_file_part(
1550 context: &Context,
1551 decoded_data: &[u8],
1552 ) -> Result<bool> {
1553 let key = match str::from_utf8(decoded_data) {
1554 Err(err) => {
1555 warn!(context, "PGP key attachment is not a UTF-8 file: {}", err);
1556 return Ok(false);
1557 }
1558 Ok(key) => key,
1559 };
1560 let key = match SignedPublicKey::from_asc(key) {
1561 Err(err) => {
1562 warn!(
1563 context,
1564 "PGP key attachment is not an ASCII-armored file: {err:#}."
1565 );
1566 return Ok(false);
1567 }
1568 Ok(key) => key,
1569 };
1570 if let Err(err) = key.verify() {
1571 warn!(context, "Attached PGP key verification failed: {err:#}.");
1572 return Ok(false);
1573 }
1574
1575 let fingerprint = key.dc_fingerprint().hex();
1576 context
1577 .sql
1578 .execute(
1579 "INSERT INTO public_keys (fingerprint, public_key)
1580 VALUES (?, ?)
1581 ON CONFLICT (fingerprint)
1582 DO NOTHING",
1583 (&fingerprint, key.to_bytes()),
1584 )
1585 .await?;
1586
1587 info!(context, "Imported PGP key {fingerprint} from attachment.");
1588 Ok(true)
1589 }
1590
1591 pub(crate) fn do_add_single_part(&mut self, mut part: Part) {
1592 if self.was_encrypted() {
1593 part.param.set_int(Param::GuaranteeE2ee, 1);
1594 }
1595 self.parts.push(part);
1596 }
1597
1598 pub(crate) fn get_mailinglist_header(&self) -> Option<&str> {
1599 if let Some(list_id) = self.get_header(HeaderDef::ListId) {
1600 return Some(list_id);
1603 } else if let Some(chat_list_id) = self.get_header(HeaderDef::ChatListId) {
1604 return Some(chat_list_id);
1605 } else if let Some(sender) = self.get_header(HeaderDef::Sender) {
1606 if let Some(precedence) = self.get_header(HeaderDef::Precedence)
1609 && (precedence == "list" || precedence == "bulk")
1610 {
1611 return Some(sender);
1615 }
1616 }
1617 None
1618 }
1619
1620 pub(crate) fn is_mailinglist_message(&self) -> bool {
1621 self.get_mailinglist_header().is_some()
1622 }
1623
1624 pub(crate) fn is_schleuder_message(&self) -> bool {
1626 if let Some(list_help) = self.get_header(HeaderDef::ListHelp) {
1627 list_help == "<https://schleuder.org/>"
1628 } else {
1629 false
1630 }
1631 }
1632
1633 pub(crate) fn is_call(&self) -> bool {
1635 self.parts
1636 .first()
1637 .is_some_and(|part| part.typ == Viewtype::Call)
1638 }
1639
1640 pub(crate) fn get_rfc724_mid(&self) -> Option<String> {
1641 self.get_header(HeaderDef::MessageId)
1642 .and_then(|msgid| parse_message_id(msgid).ok())
1643 }
1644
1645 fn remove_secured_headers(
1646 headers: &mut HashMap<String, String>,
1647 removed: &mut HashSet<String>,
1648 ) {
1649 remove_header(headers, "secure-join-fingerprint", removed);
1650 remove_header(headers, "secure-join-auth", removed);
1651 remove_header(headers, "chat-verified", removed);
1652 remove_header(headers, "autocrypt-gossip", removed);
1653
1654 if let Some(secure_join) = remove_header(headers, "secure-join", removed)
1656 && (secure_join == "vc-request" || secure_join == "vg-request")
1657 {
1658 headers.insert("secure-join".to_string(), secure_join);
1659 }
1660 }
1661
1662 #[allow(clippy::too_many_arguments)]
1668 fn merge_headers(
1669 context: &Context,
1670 headers: &mut HashMap<String, String>,
1671 headers_removed: &mut HashSet<String>,
1672 recipients: &mut Vec<SingleInfo>,
1673 past_members: &mut Vec<SingleInfo>,
1674 from: &mut Option<SingleInfo>,
1675 list_post: &mut Option<String>,
1676 chat_disposition_notification_to: &mut Option<SingleInfo>,
1677 part: &mailparse::ParsedMail,
1678 ) {
1679 let fields = &part.headers;
1680 let has_header_protection = part.ctype.params.contains_key("hp");
1682
1683 headers_removed.extend(
1684 headers
1685 .extract_if(|k, _v| has_header_protection || is_protected(k))
1686 .map(|(k, _v)| k.to_string()),
1687 );
1688 for field in fields {
1689 let key = field.get_key().to_lowercase();
1691 if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
1692 match addrparse_header(field) {
1693 Ok(addrlist) => {
1694 *chat_disposition_notification_to = addrlist.extract_single_info();
1695 }
1696 Err(e) => warn!(context, "Could not read {} address: {}", key, e),
1697 }
1698 } else {
1699 let value = field.get_value();
1700 headers.insert(key.to_string(), value);
1701 }
1702 }
1703 let recipients_new = get_recipients(fields);
1704 if !recipients_new.is_empty() {
1705 *recipients = recipients_new;
1706 }
1707 let past_members_addresses =
1708 get_all_addresses_from_header(fields, "chat-group-past-members");
1709 if !past_members_addresses.is_empty() {
1710 *past_members = past_members_addresses;
1711 }
1712 let from_new = get_from(fields);
1713 if from_new.is_some() {
1714 *from = from_new;
1715 }
1716 let list_post_new = get_list_post(fields);
1717 if list_post_new.is_some() {
1718 *list_post = list_post_new;
1719 }
1720 }
1721
1722 fn process_report(
1723 &self,
1724 context: &Context,
1725 report: &mailparse::ParsedMail<'_>,
1726 ) -> Result<Option<Report>> {
1727 let report_body = if let Some(subpart) = report.subparts.get(1) {
1729 subpart.get_body_raw()?
1730 } else {
1731 bail!("Report does not have second MIME part");
1732 };
1733 let (report_fields, _) = mailparse::parse_headers(&report_body)?;
1734
1735 if report_fields
1737 .get_header_value(HeaderDef::Disposition)
1738 .is_none()
1739 {
1740 warn!(
1741 context,
1742 "Ignoring unknown disposition-notification, Message-Id: {:?}.",
1743 report_fields.get_header_value(HeaderDef::MessageId)
1744 );
1745 return Ok(None);
1746 };
1747
1748 let original_message_id = report_fields
1749 .get_header_value(HeaderDef::OriginalMessageId)
1750 .or_else(|| report.headers.get_header_value(HeaderDef::InReplyTo))
1753 .and_then(|v| parse_message_id(&v).ok());
1754 let additional_message_ids = report_fields
1755 .get_header_value(HeaderDef::AdditionalMessageIds)
1756 .map_or_else(Vec::new, |v| {
1757 v.split(' ')
1758 .filter_map(|s| parse_message_id(s).ok())
1759 .collect()
1760 });
1761
1762 Ok(Some(Report {
1763 original_message_id,
1764 additional_message_ids,
1765 }))
1766 }
1767
1768 fn process_delivery_status(
1769 &self,
1770 context: &Context,
1771 report: &mailparse::ParsedMail<'_>,
1772 ) -> Result<Option<DeliveryReport>> {
1773 let mut failure = true;
1775
1776 if let Some(status_part) = report.subparts.get(1) {
1777 if status_part.ctype.mimetype != "message/delivery-status"
1780 && status_part.ctype.mimetype != "message/global-delivery-status"
1781 {
1782 warn!(
1783 context,
1784 "Second part of Delivery Status Notification is not message/delivery-status or message/global-delivery-status, ignoring"
1785 );
1786 return Ok(None);
1787 }
1788
1789 let status_body = status_part.get_body_raw()?;
1790
1791 let (_, sz) = mailparse::parse_headers(&status_body)?;
1793
1794 if let Some(status_body) = status_body.get(sz..) {
1796 let (status_fields, _) = mailparse::parse_headers(status_body)?;
1797 if let Some(action) = status_fields.get_first_value("action") {
1798 if action != "failed" {
1799 info!(context, "DSN with {:?} action", action);
1800 failure = false;
1801 }
1802 } else {
1803 warn!(context, "DSN without action");
1804 }
1805 } else {
1806 warn!(context, "DSN without per-recipient fields");
1807 }
1808 } else {
1809 return Ok(None);
1811 }
1812
1813 if let Some(original_msg) = report.subparts.get(2).filter(|p| {
1815 p.ctype.mimetype.contains("rfc822")
1816 || p.ctype.mimetype == "message/global"
1817 || p.ctype.mimetype == "message/global-headers"
1818 }) {
1819 let report_body = original_msg.get_body_raw()?;
1820 let (report_fields, _) = mailparse::parse_headers(&report_body)?;
1821
1822 if let Some(original_message_id) = report_fields
1823 .get_header_value(HeaderDef::MessageId)
1824 .and_then(|v| parse_message_id(&v).ok())
1825 {
1826 return Ok(Some(DeliveryReport {
1827 rfc724_mid: original_message_id,
1828 failure,
1829 }));
1830 }
1831
1832 warn!(
1833 context,
1834 "ignoring unknown ndn-notification, Message-Id: {:?}",
1835 report_fields.get_header_value(HeaderDef::MessageId)
1836 );
1837 }
1838
1839 Ok(None)
1840 }
1841
1842 fn maybe_remove_bad_parts(&mut self) {
1843 let good_parts = self.parts.iter().filter(|p| !p.dehtml_failed).count();
1844 if good_parts == 0 {
1845 self.parts.truncate(1);
1847 } else if good_parts < self.parts.len() {
1848 self.parts.retain(|p| !p.dehtml_failed);
1849 }
1850
1851 if !self.has_chat_version() && self.is_mime_modified {
1859 fn is_related_image(p: &&Part) -> bool {
1860 (p.typ == Viewtype::Image || p.typ == Viewtype::Gif) && p.is_related
1861 }
1862 let related_image_cnt = self.parts.iter().filter(is_related_image).count();
1863 if related_image_cnt > 1 {
1864 let mut is_first_image = true;
1865 self.parts.retain(|p| {
1866 let retain = is_first_image || !is_related_image(&p);
1867 if p.typ == Viewtype::Image || p.typ == Viewtype::Gif {
1868 is_first_image = false;
1869 }
1870 retain
1871 });
1872 }
1873 }
1874 }
1875
1876 fn maybe_remove_inline_mailinglist_footer(&mut self) {
1886 if self.is_mailinglist_message() && !self.is_schleuder_message() {
1887 let text_part_cnt = self
1888 .parts
1889 .iter()
1890 .filter(|p| p.typ == Viewtype::Text)
1891 .count();
1892 if text_part_cnt == 2
1893 && let Some(last_part) = self.parts.last()
1894 && last_part.typ == Viewtype::Text
1895 {
1896 self.parts.pop();
1897 }
1898 }
1899 }
1900
1901 async fn heuristically_parse_ndn(&mut self, context: &Context) {
1905 let maybe_ndn = if let Some(from) = self.get_header(HeaderDef::From_) {
1906 let from = from.to_ascii_lowercase();
1907 from.contains("mailer-daemon") || from.contains("mail-daemon")
1908 } else {
1909 false
1910 };
1911 if maybe_ndn && self.delivery_report.is_none() {
1912 for original_message_id in self
1913 .parts
1914 .iter()
1915 .filter_map(|part| part.msg_raw.as_ref())
1916 .flat_map(|part| part.lines())
1917 .filter_map(|line| line.split_once("Message-ID:"))
1918 .filter_map(|(_, message_id)| parse_message_id(message_id).ok())
1919 {
1920 if let Ok(Some(_)) = message::rfc724_mid_exists(context, &original_message_id).await
1921 {
1922 self.delivery_report = Some(DeliveryReport {
1923 rfc724_mid: original_message_id,
1924 failure: true,
1925 })
1926 }
1927 }
1928 }
1929 }
1930
1931 pub async fn handle_reports(&self, context: &Context, from_id: ContactId, parts: &[Part]) {
1935 for report in &self.mdn_reports {
1936 for original_message_id in report
1937 .original_message_id
1938 .iter()
1939 .chain(&report.additional_message_ids)
1940 {
1941 if let Err(err) =
1942 handle_mdn(context, from_id, original_message_id, self.timestamp_sent).await
1943 {
1944 warn!(context, "Could not handle MDN: {err:#}.");
1945 }
1946 }
1947 }
1948
1949 if let Some(delivery_report) = &self.delivery_report
1950 && delivery_report.failure
1951 {
1952 let error = parts
1953 .iter()
1954 .find(|p| p.typ == Viewtype::Text)
1955 .map(|p| p.msg.clone());
1956 if let Err(err) = handle_ndn(context, delivery_report, error).await {
1957 warn!(context, "Could not handle NDN: {err:#}.");
1958 }
1959 }
1960 }
1961
1962 pub async fn get_parent_timestamp(&self, context: &Context) -> Result<Option<i64>> {
1967 let parent_timestamp = if let Some(field) = self
1968 .get_header(HeaderDef::InReplyTo)
1969 .and_then(|msgid| parse_message_id(msgid).ok())
1970 {
1971 context
1972 .sql
1973 .query_get_value("SELECT timestamp FROM msgs WHERE rfc724_mid=?", (field,))
1974 .await?
1975 } else {
1976 None
1977 };
1978 Ok(parent_timestamp)
1979 }
1980
1981 pub fn chat_group_member_timestamps(&self) -> Option<Vec<i64>> {
1985 let now = time() + constants::TIMESTAMP_SENT_TOLERANCE;
1986 self.get_header(HeaderDef::ChatGroupMemberTimestamps)
1987 .map(|h| {
1988 h.split_ascii_whitespace()
1989 .filter_map(|ts| ts.parse::<i64>().ok())
1990 .map(|ts| std::cmp::min(now, ts))
1991 .collect()
1992 })
1993 }
1994
1995 pub fn chat_group_member_fingerprints(&self) -> Vec<Fingerprint> {
1998 if let Some(header) = self.get_header(HeaderDef::ChatGroupMemberFpr) {
1999 header
2000 .split_ascii_whitespace()
2001 .filter_map(|fpr| Fingerprint::from_str(fpr).ok())
2002 .collect()
2003 } else {
2004 Vec::new()
2005 }
2006 }
2007}
2008
2009fn rm_legacy_display_elements(text: &str) -> String {
2010 let mut res = None;
2011 for l in text.lines() {
2012 res = res.map(|r: String| match r.is_empty() {
2013 true => l.to_string(),
2014 false => r + "\r\n" + l,
2015 });
2016 if l.is_empty() {
2017 res = Some(String::new());
2018 }
2019 }
2020 res.unwrap_or_default()
2021}
2022
2023fn remove_header(
2024 headers: &mut HashMap<String, String>,
2025 key: &str,
2026 removed: &mut HashSet<String>,
2027) -> Option<String> {
2028 if let Some((k, v)) = headers.remove_entry(key) {
2029 removed.insert(k);
2030 Some(v)
2031 } else {
2032 None
2033 }
2034}
2035
2036async fn parse_gossip_headers(
2042 context: &Context,
2043 from: &str,
2044 recipients: &[SingleInfo],
2045 gossip_headers: Vec<String>,
2046) -> Result<BTreeMap<String, GossipedKey>> {
2047 let mut gossiped_keys: BTreeMap<String, GossipedKey> = Default::default();
2049
2050 for value in &gossip_headers {
2051 let header = match value.parse::<Aheader>() {
2052 Ok(header) => header,
2053 Err(err) => {
2054 warn!(context, "Failed parsing Autocrypt-Gossip header: {}", err);
2055 continue;
2056 }
2057 };
2058
2059 if !recipients
2060 .iter()
2061 .any(|info| addr_cmp(&info.addr, &header.addr))
2062 {
2063 warn!(
2064 context,
2065 "Ignoring gossiped \"{}\" as the address is not in To/Cc list.", &header.addr,
2066 );
2067 continue;
2068 }
2069 if addr_cmp(from, &header.addr) {
2070 warn!(
2072 context,
2073 "Ignoring gossiped \"{}\" as it equals the From address", &header.addr,
2074 );
2075 continue;
2076 }
2077
2078 let fingerprint = header.public_key.dc_fingerprint().hex();
2079 context
2080 .sql
2081 .execute(
2082 "INSERT INTO public_keys (fingerprint, public_key)
2083 VALUES (?, ?)
2084 ON CONFLICT (fingerprint)
2085 DO NOTHING",
2086 (&fingerprint, header.public_key.to_bytes()),
2087 )
2088 .await?;
2089
2090 let gossiped_key = GossipedKey {
2091 public_key: header.public_key,
2092
2093 verified: header.verified,
2094 };
2095 gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key);
2096 }
2097
2098 Ok(gossiped_keys)
2099}
2100
2101#[derive(Debug)]
2103pub(crate) struct Report {
2104 original_message_id: Option<String>,
2109 additional_message_ids: Vec<String>,
2111}
2112
2113#[derive(Debug)]
2115pub(crate) struct DeliveryReport {
2116 pub rfc724_mid: String,
2117 pub failure: bool,
2118}
2119
2120pub(crate) fn parse_message_ids(ids: &str) -> Vec<String> {
2121 let mut msgids = Vec::new();
2123 for id in ids.split_whitespace() {
2124 let mut id = id.to_string();
2125 if let Some(id_without_prefix) = id.strip_prefix('<') {
2126 id = id_without_prefix.to_string();
2127 };
2128 if let Some(id_without_suffix) = id.strip_suffix('>') {
2129 id = id_without_suffix.to_string();
2130 };
2131 if !id.is_empty() {
2132 msgids.push(id);
2133 }
2134 }
2135 msgids
2136}
2137
2138pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
2139 if let Some(id) = parse_message_ids(ids).first() {
2140 Ok(id.to_string())
2141 } else {
2142 bail!("could not parse message_id: {ids}");
2143 }
2144}
2145
2146fn is_protected(key: &str) -> bool {
2153 key.starts_with("chat-")
2154 || matches!(
2155 key,
2156 "return-path"
2157 | "auto-submitted"
2158 | "autocrypt-setup-message"
2159 | "date"
2160 | "from"
2161 | "sender"
2162 | "reply-to"
2163 | "to"
2164 | "cc"
2165 | "bcc"
2166 | "message-id"
2167 | "in-reply-to"
2168 | "references"
2169 | "secure-join"
2170 )
2171}
2172
2173pub(crate) fn is_hidden(key: &str) -> bool {
2175 matches!(
2176 key,
2177 "chat-user-avatar" | "chat-group-avatar" | "chat-delete" | "chat-edit"
2178 )
2179}
2180
2181#[derive(Debug, Default, Clone)]
2183pub struct Part {
2184 pub typ: Viewtype,
2186
2187 pub mimetype: Option<Mime>,
2189
2190 pub msg: String,
2192
2193 pub msg_raw: Option<String>,
2195
2196 pub bytes: usize,
2198
2199 pub param: Params,
2201
2202 pub(crate) org_filename: Option<String>,
2204
2205 pub error: Option<String>,
2207
2208 pub(crate) dehtml_failed: bool,
2210
2211 pub(crate) is_related: bool,
2218
2219 pub(crate) is_reaction: bool,
2221}
2222
2223fn get_mime_type(
2228 mail: &mailparse::ParsedMail<'_>,
2229 filename: &Option<String>,
2230 is_chat_msg: bool,
2231) -> Result<(Mime, Viewtype)> {
2232 let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
2233
2234 let viewtype = match mimetype.type_() {
2235 mime::TEXT => match mimetype.subtype() {
2236 mime::VCARD => Viewtype::Vcard,
2237 mime::PLAIN | mime::HTML if !is_attachment_disposition(mail) => Viewtype::Text,
2238 _ => Viewtype::File,
2239 },
2240 mime::IMAGE => match mimetype.subtype() {
2241 mime::GIF => Viewtype::Gif,
2242 mime::SVG => Viewtype::File,
2243 _ => Viewtype::Image,
2244 },
2245 mime::AUDIO => Viewtype::Audio,
2246 mime::VIDEO => Viewtype::Video,
2247 mime::MULTIPART => Viewtype::Unknown,
2248 mime::MESSAGE => {
2249 if is_attachment_disposition(mail) {
2250 Viewtype::File
2251 } else {
2252 Viewtype::Unknown
2260 }
2261 }
2262 mime::APPLICATION => match mimetype.subtype() {
2263 mime::OCTET_STREAM => match filename {
2264 Some(filename) if !is_chat_msg => {
2265 match message::guess_msgtype_from_path_suffix(Path::new(&filename)) {
2266 Some((viewtype, _)) => viewtype,
2267 None => Viewtype::File,
2268 }
2269 }
2270 _ => Viewtype::File,
2271 },
2272 _ => Viewtype::File,
2273 },
2274 _ => Viewtype::Unknown,
2275 };
2276
2277 Ok((mimetype, viewtype))
2278}
2279
2280fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
2281 let ct = mail.get_content_disposition();
2282 ct.disposition == DispositionType::Attachment
2283 && ct
2284 .params
2285 .iter()
2286 .any(|(key, _value)| key.starts_with("filename"))
2287}
2288
2289fn get_attachment_filename(
2296 context: &Context,
2297 mail: &mailparse::ParsedMail,
2298) -> Result<Option<String>> {
2299 let ct = mail.get_content_disposition();
2300
2301 let mut desired_filename = ct.params.get("filename").map(|s| s.to_string());
2304
2305 if desired_filename.is_none()
2306 && let Some(name) = ct.params.get("filename*").map(|s| s.to_string())
2307 {
2308 warn!(context, "apostrophed encoding invalid: {}", name);
2312 desired_filename = Some(name);
2313 }
2314
2315 if desired_filename.is_none() {
2317 desired_filename = ct.params.get("name").map(|s| s.to_string());
2318 }
2319
2320 if desired_filename.is_none() {
2323 desired_filename = mail.ctype.params.get("name").map(|s| s.to_string());
2324 }
2325
2326 if desired_filename.is_none() && ct.disposition == DispositionType::Attachment {
2328 if let Some(subtype) = mail.ctype.mimetype.split('/').nth(1) {
2329 desired_filename = Some(format!("file.{subtype}",));
2330 } else {
2331 bail!(
2332 "could not determine attachment filename: {:?}",
2333 ct.disposition
2334 );
2335 };
2336 }
2337
2338 let desired_filename = desired_filename.map(|filename| sanitize_bidi_characters(&filename));
2339
2340 Ok(desired_filename)
2341}
2342
2343pub(crate) fn get_recipients(headers: &[MailHeader]) -> Vec<SingleInfo> {
2345 let to_addresses = get_all_addresses_from_header(headers, "to");
2346 let cc_addresses = get_all_addresses_from_header(headers, "cc");
2347
2348 let mut res = to_addresses;
2349 res.extend(cc_addresses);
2350 res
2351}
2352
2353pub(crate) fn get_from(headers: &[MailHeader]) -> Option<SingleInfo> {
2355 let all = get_all_addresses_from_header(headers, "from");
2356 tools::single_value(all)
2357}
2358
2359pub(crate) fn get_list_post(headers: &[MailHeader]) -> Option<String> {
2361 get_all_addresses_from_header(headers, "list-post")
2362 .into_iter()
2363 .next()
2364 .map(|s| s.addr)
2365}
2366
2367fn get_all_addresses_from_header(headers: &[MailHeader], header: &str) -> Vec<SingleInfo> {
2379 let mut result: Vec<SingleInfo> = Default::default();
2380
2381 if let Some(header) = headers
2382 .iter()
2383 .rev()
2384 .find(|h| h.get_key().to_lowercase() == header)
2385 && let Ok(addrs) = mailparse::addrparse_header(header)
2386 {
2387 for addr in addrs.iter() {
2388 match addr {
2389 mailparse::MailAddr::Single(info) => {
2390 result.push(SingleInfo {
2391 addr: addr_normalize(&info.addr).to_lowercase(),
2392 display_name: info.display_name.clone(),
2393 });
2394 }
2395 mailparse::MailAddr::Group(infos) => {
2396 for info in &infos.addrs {
2397 result.push(SingleInfo {
2398 addr: addr_normalize(&info.addr).to_lowercase(),
2399 display_name: info.display_name.clone(),
2400 });
2401 }
2402 }
2403 }
2404 }
2405 }
2406
2407 result
2408}
2409
2410async fn handle_mdn(
2411 context: &Context,
2412 from_id: ContactId,
2413 rfc724_mid: &str,
2414 timestamp_sent: i64,
2415) -> Result<()> {
2416 if from_id == ContactId::SELF {
2417 warn!(
2418 context,
2419 "Ignoring MDN sent to self, this is a bug on the sender device."
2420 );
2421
2422 return Ok(());
2425 }
2426
2427 let Some((msg_id, chat_id, has_mdns, is_dup)) = context
2428 .sql
2429 .query_row_optional(
2430 concat!(
2431 "SELECT",
2432 " m.id AS msg_id,",
2433 " c.id AS chat_id,",
2434 " mdns.contact_id AS mdn_contact",
2435 " FROM msgs m ",
2436 " LEFT JOIN chats c ON m.chat_id=c.id",
2437 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
2438 " WHERE rfc724_mid=? AND from_id=1",
2439 " ORDER BY msg_id DESC, mdn_contact=? DESC",
2440 " LIMIT 1",
2441 ),
2442 (&rfc724_mid, from_id),
2443 |row| {
2444 let msg_id: MsgId = row.get("msg_id")?;
2445 let chat_id: ChatId = row.get("chat_id")?;
2446 let mdn_contact: Option<ContactId> = row.get("mdn_contact")?;
2447 Ok((
2448 msg_id,
2449 chat_id,
2450 mdn_contact.is_some(),
2451 mdn_contact == Some(from_id),
2452 ))
2453 },
2454 )
2455 .await?
2456 else {
2457 info!(
2458 context,
2459 "Ignoring MDN, found no message with Message-ID {rfc724_mid:?} sent by us in the database.",
2460 );
2461 return Ok(());
2462 };
2463
2464 if is_dup {
2465 return Ok(());
2466 }
2467 context
2468 .sql
2469 .execute(
2470 "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?)",
2471 (msg_id, from_id, timestamp_sent),
2472 )
2473 .await?;
2474 if !has_mdns {
2475 context.emit_event(EventType::MsgRead { chat_id, msg_id });
2476 chatlist_events::emit_chatlist_item_changed(context, chat_id);
2478 }
2479 Ok(())
2480}
2481
2482async fn handle_ndn(
2485 context: &Context,
2486 failed: &DeliveryReport,
2487 error: Option<String>,
2488) -> Result<()> {
2489 if failed.rfc724_mid.is_empty() {
2490 return Ok(());
2491 }
2492
2493 let msg_ids = context
2496 .sql
2497 .query_map_vec(
2498 "SELECT id FROM msgs
2499 WHERE rfc724_mid=? AND from_id=1",
2500 (&failed.rfc724_mid,),
2501 |row| {
2502 let msg_id: MsgId = row.get(0)?;
2503 Ok(msg_id)
2504 },
2505 )
2506 .await?;
2507
2508 let error = if let Some(error) = error {
2509 error
2510 } else {
2511 "Delivery to at least one recipient failed.".to_string()
2512 };
2513 let err_msg = &error;
2514
2515 for msg_id in msg_ids {
2516 let mut message = Message::load_from_db(context, msg_id).await?;
2517 let aggregated_error = message
2518 .error
2519 .as_ref()
2520 .map(|err| format!("{err}\n\n{err_msg}"));
2521 set_msg_failed(
2522 context,
2523 &mut message,
2524 aggregated_error.as_ref().unwrap_or(err_msg),
2525 )
2526 .await?;
2527 }
2528
2529 Ok(())
2530}
2531
2532#[cfg(test)]
2533mod mimeparser_tests;