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::{error, info, 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, stock_str, 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 signatures: HashSet<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(
246 context: &Context,
247 body: &[u8],
248 partial: Option<(u32, Option<String>)>,
249 ) -> Result<Self> {
250 let mail = mailparse::parse_mail(body)?;
251
252 let timestamp_rcvd = smeared_time(context);
253 let mut timestamp_sent =
254 Self::get_timestamp_sent(&mail.headers, timestamp_rcvd, timestamp_rcvd);
255 let mut hop_info = parse_receive_headers(&mail.get_headers());
256
257 let mut headers = Default::default();
258 let mut headers_removed = HashSet::<String>::new();
259 let mut recipients = Default::default();
260 let mut past_members = Default::default();
261 let mut from = Default::default();
262 let mut list_post = Default::default();
263 let mut chat_disposition_notification_to = None;
264
265 MimeMessage::merge_headers(
267 context,
268 &mut headers,
269 &mut headers_removed,
270 &mut recipients,
271 &mut past_members,
272 &mut from,
273 &mut list_post,
274 &mut chat_disposition_notification_to,
275 &mail.headers,
276 );
277 headers.retain(|k, _| {
278 !is_hidden(k) || {
279 headers_removed.insert(k.to_string());
280 false
281 }
282 });
283
284 let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
286 let (part, mimetype) =
287 if mimetype.type_() == mime::MULTIPART && mimetype.subtype().as_str() == "signed" {
288 if let Some(part) = mail.subparts.first() {
289 timestamp_sent =
293 Self::get_timestamp_sent(&part.headers, timestamp_sent, timestamp_rcvd);
294 MimeMessage::merge_headers(
295 context,
296 &mut headers,
297 &mut headers_removed,
298 &mut recipients,
299 &mut past_members,
300 &mut from,
301 &mut list_post,
302 &mut chat_disposition_notification_to,
303 &part.headers,
304 );
305 (part, part.ctype.mimetype.parse::<Mime>()?)
306 } else {
307 (&mail, mimetype)
309 }
310 } else {
311 (&mail, mimetype)
313 };
314 if mimetype.type_() == mime::MULTIPART && mimetype.subtype().as_str() == "mixed" {
315 if let Some(part) = part.subparts.first() {
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
325 if let Some(microsoft_message_id) = remove_header(
329 &mut headers,
330 HeaderDef::XMicrosoftOriginalMessageId.get_headername(),
331 &mut headers_removed,
332 ) {
333 headers.insert(
334 HeaderDef::MessageId.get_headername().to_string(),
335 microsoft_message_id,
336 );
337 }
338
339 Self::remove_secured_headers(&mut headers, &mut headers_removed);
342
343 let mut from = from.context("No from in message")?;
344 let private_keyring = load_self_secret_keyring(context).await?;
345
346 let dkim_results = handle_authres(context, &mail, &from.addr).await?;
347
348 let mut gossiped_keys = Default::default();
349 hop_info += "\n\n";
350 hop_info += &dkim_results.to_string();
351
352 let incoming = !context.is_self_addr(&from.addr).await?;
353
354 let mut aheader_values = mail.headers.get_all_values(HeaderDef::Autocrypt.into());
355
356 let mail_raw; let decrypted_msg; let (mail, is_encrypted) =
360 match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring)) {
361 Ok(Some(mut msg)) => {
362 mail_raw = msg.as_data_vec().unwrap_or_default();
363
364 let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
365 if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
366 info!(
367 context,
368 "decrypted message mime-body:\n{}",
369 String::from_utf8_lossy(&mail_raw),
370 );
371 }
372
373 decrypted_msg = Some(msg);
374
375 timestamp_sent = Self::get_timestamp_sent(
376 &decrypted_mail.headers,
377 timestamp_sent,
378 timestamp_rcvd,
379 );
380
381 let protected_aheader_values = decrypted_mail
382 .headers
383 .get_all_values(HeaderDef::Autocrypt.into());
384 if !protected_aheader_values.is_empty() {
385 aheader_values = protected_aheader_values;
386 }
387
388 (Ok(decrypted_mail), true)
389 }
390 Ok(None) => {
391 mail_raw = Vec::new();
392 decrypted_msg = None;
393 (Ok(mail), false)
394 }
395 Err(err) => {
396 mail_raw = Vec::new();
397 decrypted_msg = None;
398 warn!(context, "decryption failed: {:#}", err);
399 (Err(err), false)
400 }
401 };
402
403 let mut autocrypt_header = None;
404 if incoming {
405 for val in aheader_values.iter().rev() {
407 autocrypt_header = match Aheader::from_str(val) {
408 Ok(header) if addr_cmp(&header.addr, &from.addr) => Some(header),
409 Ok(header) => {
410 warn!(
411 context,
412 "Autocrypt header address {:?} is not {:?}.", header.addr, from.addr
413 );
414 continue;
415 }
416 Err(err) => {
417 warn!(context, "Failed to parse Autocrypt header: {:#}.", err);
418 continue;
419 }
420 };
421 break;
422 }
423 }
424
425 let autocrypt_fingerprint = if let Some(autocrypt_header) = &autocrypt_header {
426 let fingerprint = autocrypt_header.public_key.dc_fingerprint().hex();
427 let inserted = context
428 .sql
429 .execute(
430 "INSERT INTO public_keys (fingerprint, public_key)
431 VALUES (?, ?)
432 ON CONFLICT (fingerprint)
433 DO NOTHING",
434 (&fingerprint, autocrypt_header.public_key.to_bytes()),
435 )
436 .await?;
437 if inserted > 0 {
438 info!(
439 context,
440 "Saved key with fingerprint {fingerprint} from the Autocrypt header"
441 );
442 }
443 Some(fingerprint)
444 } else {
445 None
446 };
447
448 let mut public_keyring = if incoming {
449 if let Some(autocrypt_header) = autocrypt_header {
450 vec![autocrypt_header.public_key]
451 } else {
452 vec![]
453 }
454 } else {
455 key::load_self_public_keyring(context).await?
456 };
457
458 if let Some(signature) = match &decrypted_msg {
459 Some(pgp::composed::Message::Literal { .. }) => None,
460 Some(pgp::composed::Message::Compressed { .. }) => {
461 None
464 }
465 Some(pgp::composed::Message::SignedOnePass { reader, .. }) => reader.signature(),
466 Some(pgp::composed::Message::Signed { reader, .. }) => Some(reader.signature()),
467 Some(pgp::composed::Message::Encrypted { .. }) => {
468 None
470 }
471 None => None,
472 } {
473 for issuer_fingerprint in signature.issuer_fingerprint() {
474 let issuer_fingerprint =
475 crate::key::Fingerprint::from(issuer_fingerprint.clone()).hex();
476 if let Some(public_key_bytes) = context
477 .sql
478 .query_row_optional(
479 "SELECT public_key
480 FROM public_keys
481 WHERE fingerprint=?",
482 (&issuer_fingerprint,),
483 |row| {
484 let bytes: Vec<u8> = row.get(0)?;
485 Ok(bytes)
486 },
487 )
488 .await?
489 {
490 let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
491 public_keyring.push(public_key)
492 }
493 }
494 }
495
496 let mut signatures = if let Some(ref decrypted_msg) = decrypted_msg {
497 crate::pgp::valid_signature_fingerprints(decrypted_msg, &public_keyring)
498 } else {
499 HashSet::new()
500 };
501
502 let mail = mail.as_ref().map(|mail| {
503 let (content, signatures_detached) = validate_detached_signature(mail, &public_keyring)
504 .unwrap_or((mail, Default::default()));
505 signatures.extend(signatures_detached);
506 content
507 });
508 if let (Ok(mail), true) = (mail, is_encrypted) {
509 if !signatures.is_empty() {
510 remove_header(&mut headers, "subject", &mut headers_removed);
514 remove_header(&mut headers, "list-id", &mut headers_removed);
515 }
516
517 let mut inner_from = None;
523
524 MimeMessage::merge_headers(
525 context,
526 &mut headers,
527 &mut headers_removed,
528 &mut recipients,
529 &mut past_members,
530 &mut inner_from,
531 &mut list_post,
532 &mut chat_disposition_notification_to,
533 &mail.headers,
534 );
535
536 if !signatures.is_empty() {
537 let gossip_headers = mail.headers.get_all_values("Autocrypt-Gossip");
542 gossiped_keys =
543 parse_gossip_headers(context, &from.addr, &recipients, gossip_headers).await?;
544 }
545
546 if let Some(inner_from) = inner_from {
547 if !addr_cmp(&inner_from.addr, &from.addr) {
548 warn!(
557 context,
558 "From header in encrypted part doesn't match the outer one",
559 );
560
561 bail!("From header is forged");
566 }
567 from = inner_from;
568 }
569 }
570 if signatures.is_empty() {
571 Self::remove_secured_headers(&mut headers, &mut headers_removed);
572 }
573 if !is_encrypted {
574 signatures.clear();
575 }
576
577 let mut parser = MimeMessage {
578 parts: Vec::new(),
579 headers,
580 #[cfg(test)]
581 headers_removed,
582
583 recipients,
584 past_members,
585 list_post,
586 from,
587 incoming,
588 chat_disposition_notification_to,
589 decrypting_failed: mail.is_err(),
590
591 signatures,
593 autocrypt_fingerprint,
594 gossiped_keys,
595 is_forwarded: false,
596 mdn_reports: Vec::new(),
597 is_system_message: SystemMessage::Unknown,
598 location_kml: None,
599 message_kml: None,
600 sync_items: None,
601 webxdc_status_update: None,
602 user_avatar: None,
603 group_avatar: None,
604 delivery_report: None,
605 footer: None,
606 is_mime_modified: false,
607 decoded_data: Vec::new(),
608 hop_info,
609 is_bot: None,
610 timestamp_rcvd,
611 timestamp_sent,
612 };
613
614 match partial {
615 Some((org_bytes, err)) => {
616 parser
617 .create_stub_from_partial_download(context, org_bytes, err)
618 .await?;
619 }
620 None => match mail {
621 Ok(mail) => {
622 parser.parse_mime_recursive(context, mail, false).await?;
623 }
624 Err(err) => {
625 let msg_body = stock_str::cant_decrypt_msg_body(context).await;
626 let txt = format!("[{msg_body}]");
627
628 let part = Part {
629 typ: Viewtype::Text,
630 msg_raw: Some(txt.clone()),
631 msg: txt,
632 error: Some(format!("Decrypting failed: {err:#}")),
635 ..Default::default()
636 };
637 parser.do_add_single_part(part);
638 }
639 },
640 };
641
642 let is_location_only = parser.location_kml.is_some() && parser.parts.is_empty();
643 if parser.mdn_reports.is_empty()
644 && !is_location_only
645 && parser.sync_items.is_none()
646 && parser.webxdc_status_update.is_none()
647 {
648 let is_bot =
649 parser.headers.get("auto-submitted") == Some(&"auto-generated".to_string());
650 parser.is_bot = Some(is_bot);
651 }
652 parser.maybe_remove_bad_parts();
653 parser.maybe_remove_inline_mailinglist_footer();
654 parser.heuristically_parse_ndn(context).await;
655 parser.parse_headers(context).await?;
656 parser.decoded_data = mail_raw;
657
658 Ok(parser)
659 }
660
661 fn get_timestamp_sent(
662 hdrs: &[mailparse::MailHeader<'_>],
663 default: i64,
664 timestamp_rcvd: i64,
665 ) -> i64 {
666 hdrs.get_header_value(HeaderDef::Date)
667 .and_then(|v| mailparse::dateparse(&v).ok())
668 .map_or(default, |value| {
669 min(value, timestamp_rcvd + constants::TIMESTAMP_SENT_TOLERANCE)
670 })
671 }
672
673 fn parse_system_message_headers(&mut self, context: &Context) {
675 if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() && !self.incoming {
676 self.parts.retain(|part| {
677 part.mimetype.is_none()
678 || part.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
679 });
680
681 if self.parts.len() == 1 {
682 self.is_system_message = SystemMessage::AutocryptSetupMessage;
683 } else {
684 warn!(context, "could not determine ASM mime-part");
685 }
686 } else if let Some(value) = self.get_header(HeaderDef::ChatContent) {
687 if value == "location-streaming-enabled" {
688 self.is_system_message = SystemMessage::LocationStreamingEnabled;
689 } else if value == "ephemeral-timer-changed" {
690 self.is_system_message = SystemMessage::EphemeralTimerChanged;
691 } else if value == "protection-enabled" {
692 self.is_system_message = SystemMessage::ChatProtectionEnabled;
693 } else if value == "protection-disabled" {
694 self.is_system_message = SystemMessage::ChatProtectionDisabled;
695 } else if value == "group-avatar-changed" {
696 self.is_system_message = SystemMessage::GroupImageChanged;
697 } else if value == "call-accepted" {
698 self.is_system_message = SystemMessage::CallAccepted;
699 } else if value == "call-ended" {
700 self.is_system_message = SystemMessage::CallEnded;
701 }
702 } else if self.get_header(HeaderDef::ChatGroupMemberRemoved).is_some() {
703 self.is_system_message = SystemMessage::MemberRemovedFromGroup;
704 } else if self.get_header(HeaderDef::ChatGroupMemberAdded).is_some() {
705 self.is_system_message = SystemMessage::MemberAddedToGroup;
706 } else if self.get_header(HeaderDef::ChatGroupNameChanged).is_some() {
707 self.is_system_message = SystemMessage::GroupNameChanged;
708 }
709 }
710
711 fn parse_avatar_headers(&mut self, context: &Context) {
713 if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) {
714 self.group_avatar = self.avatar_action_from_header(context, header_value.to_string());
715 }
716
717 if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) {
718 self.user_avatar = self.avatar_action_from_header(context, header_value.to_string());
719 }
720 }
721
722 fn parse_videochat_headers(&mut self) {
723 let content = self
724 .get_header(HeaderDef::ChatContent)
725 .unwrap_or_default()
726 .to_string();
727 let room = self
728 .get_header(HeaderDef::ChatWebrtcRoom)
729 .map(|s| s.to_string());
730 let accepted = self
731 .get_header(HeaderDef::ChatWebrtcAccepted)
732 .map(|s| s.to_string());
733 if let Some(part) = self.parts.first_mut() {
734 if let Some(room) = room {
735 if content == "call" {
736 part.typ = Viewtype::Call;
737 part.param.set(Param::WebrtcRoom, room);
738 }
739 } else if let Some(accepted) = accepted {
740 part.param.set(Param::WebrtcAccepted, accepted);
741 }
742 }
743 }
744
745 fn squash_attachment_parts(&mut self) {
751 if self.parts.len() == 2
752 && self.parts.first().map(|textpart| textpart.typ) == Some(Viewtype::Text)
753 && self
754 .parts
755 .get(1)
756 .is_some_and(|filepart| match filepart.typ {
757 Viewtype::Image
758 | Viewtype::Gif
759 | Viewtype::Sticker
760 | Viewtype::Audio
761 | Viewtype::Voice
762 | Viewtype::Video
763 | Viewtype::Vcard
764 | Viewtype::File
765 | Viewtype::Webxdc => true,
766 Viewtype::Unknown | Viewtype::Text | Viewtype::Call => false,
767 })
768 {
769 let mut parts = std::mem::take(&mut self.parts);
770 let Some(mut filepart) = parts.pop() else {
771 return;
773 };
774 let Some(textpart) = parts.pop() else {
775 return;
777 };
778
779 filepart.msg.clone_from(&textpart.msg);
780 if let Some(quote) = textpart.param.get(Param::Quote) {
781 filepart.param.set(Param::Quote, quote);
782 }
783
784 self.parts = vec![filepart];
785 }
786 }
787
788 fn parse_attachments(&mut self) {
790 if self.parts.len() != 1 {
793 return;
794 }
795
796 if let Some(mut part) = self.parts.pop() {
797 if part.typ == Viewtype::Audio && self.get_header(HeaderDef::ChatVoiceMessage).is_some()
798 {
799 part.typ = Viewtype::Voice;
800 }
801 if part.typ == Viewtype::Image || part.typ == Viewtype::Gif {
802 if let Some(value) = self.get_header(HeaderDef::ChatContent) {
803 if value == "sticker" {
804 part.typ = Viewtype::Sticker;
805 }
806 }
807 }
808 if part.typ == Viewtype::Audio
809 || part.typ == Viewtype::Voice
810 || part.typ == Viewtype::Video
811 {
812 if let Some(field_0) = self.get_header(HeaderDef::ChatDuration) {
813 let duration_ms = field_0.parse().unwrap_or_default();
814 if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
815 part.param.set_int(Param::Duration, duration_ms);
816 }
817 }
818 }
819
820 self.parts.push(part);
821 }
822 }
823
824 async fn parse_headers(&mut self, context: &Context) -> Result<()> {
825 self.parse_system_message_headers(context);
826 self.parse_avatar_headers(context);
827 self.parse_videochat_headers();
828 if self.delivery_report.is_none() {
829 self.squash_attachment_parts();
830 }
831
832 if !context.get_config_bool(Config::Bot).await? {
833 if let Some(ref subject) = self.get_subject() {
834 let mut prepend_subject = true;
835 if !self.decrypting_failed {
836 let colon = subject.find(':');
837 if colon == Some(2)
838 || colon == Some(3)
839 || self.has_chat_version()
840 || subject.contains("Chat:")
841 {
842 prepend_subject = false
843 }
844 }
845
846 if self.is_mailinglist_message() && !self.has_chat_version() {
849 prepend_subject = true;
850 }
851
852 if prepend_subject && !subject.is_empty() {
853 let part_with_text = self
854 .parts
855 .iter_mut()
856 .find(|part| !part.msg.is_empty() && !part.is_reaction);
857 if let Some(part) = part_with_text {
858 part.msg = format!("{} – {}", subject, part.msg);
859 }
860 }
861 }
862 }
863
864 if self.is_forwarded {
865 for part in &mut self.parts {
866 part.param.set_int(Param::Forwarded, 1);
867 }
868 }
869
870 self.parse_attachments();
871
872 if !self.decrypting_failed && !self.parts.is_empty() {
874 if let Some(ref dn_to) = self.chat_disposition_notification_to {
875 let from = &self.from.addr;
877 if !context.is_self_addr(from).await? {
878 if from.to_lowercase() == dn_to.addr.to_lowercase() {
879 if let Some(part) = self.parts.last_mut() {
880 part.param.set_int(Param::WantsMdn, 1);
881 }
882 } else {
883 warn!(
884 context,
885 "{} requested a read receipt to {}, ignoring", from, dn_to.addr
886 );
887 }
888 }
889 }
890 }
891
892 if self.parts.is_empty() && self.mdn_reports.is_empty() {
897 let mut part = Part {
898 typ: Viewtype::Text,
899 ..Default::default()
900 };
901
902 if let Some(ref subject) = self.get_subject() {
903 if !self.has_chat_version() && self.webxdc_status_update.is_none() {
904 part.msg = subject.to_string();
905 }
906 }
907
908 self.do_add_single_part(part);
909 }
910
911 if self.is_bot == Some(true) {
912 for part in &mut self.parts {
913 part.param.set(Param::Bot, "1");
914 }
915 }
916
917 Ok(())
918 }
919
920 fn avatar_action_from_header(
921 &mut self,
922 context: &Context,
923 header_value: String,
924 ) -> Option<AvatarAction> {
925 if header_value == "0" {
926 Some(AvatarAction::Delete)
927 } else if let Some(base64) = header_value
928 .split_ascii_whitespace()
929 .collect::<String>()
930 .strip_prefix("base64:")
931 {
932 match BlobObject::store_from_base64(context, base64) {
933 Ok(path) => Some(AvatarAction::Change(path)),
934 Err(err) => {
935 warn!(
936 context,
937 "Could not decode and save avatar to blob file: {:#}", err,
938 );
939 None
940 }
941 }
942 } else {
943 let mut i = 0;
946 while let Some(part) = self.parts.get_mut(i) {
947 if let Some(part_filename) = &part.org_filename {
948 if part_filename == &header_value {
949 if let Some(blob) = part.param.get(Param::File) {
950 let res = Some(AvatarAction::Change(blob.to_string()));
951 self.parts.remove(i);
952 return res;
953 }
954 break;
955 }
956 }
957 i += 1;
958 }
959 None
960 }
961 }
962
963 pub fn was_encrypted(&self) -> bool {
969 !self.signatures.is_empty()
970 }
971
972 pub(crate) fn has_chat_version(&self) -> bool {
975 self.headers.contains_key("chat-version")
976 }
977
978 pub(crate) fn get_subject(&self) -> Option<String> {
979 self.get_header(HeaderDef::Subject)
980 .map(|s| s.trim_start())
981 .filter(|s| !s.is_empty())
982 .map(|s| s.to_string())
983 }
984
985 pub fn get_header(&self, headerdef: HeaderDef) -> Option<&str> {
986 self.headers
987 .get(headerdef.get_headername())
988 .map(|s| s.as_str())
989 }
990
991 #[cfg(test)]
992 pub(crate) fn header_exists(&self, headerdef: HeaderDef) -> bool {
997 let hname = headerdef.get_headername();
998 self.headers.contains_key(hname) || self.headers_removed.contains(hname)
999 }
1000
1001 pub fn get_chat_group_id(&self) -> Option<&str> {
1003 self.get_header(HeaderDef::ChatGroupId)
1004 .filter(|s| validate_id(s))
1005 }
1006
1007 async fn parse_mime_recursive<'a>(
1008 &'a mut self,
1009 context: &'a Context,
1010 mail: &'a mailparse::ParsedMail<'a>,
1011 is_related: bool,
1012 ) -> Result<bool> {
1013 enum MimeS {
1014 Multiple,
1015 Single,
1016 Message,
1017 }
1018
1019 let mimetype = mail.ctype.mimetype.to_lowercase();
1020
1021 let m = if mimetype.starts_with("multipart") {
1022 if mail.ctype.params.contains_key("boundary") {
1023 MimeS::Multiple
1024 } else {
1025 MimeS::Single
1026 }
1027 } else if mimetype.starts_with("message") {
1028 if mimetype == "message/rfc822" && !is_attachment_disposition(mail) {
1029 MimeS::Message
1030 } else {
1031 MimeS::Single
1032 }
1033 } else {
1034 MimeS::Single
1035 };
1036
1037 let is_related = is_related || mimetype == "multipart/related";
1038 match m {
1039 MimeS::Multiple => Box::pin(self.handle_multiple(context, mail, is_related)).await,
1040 MimeS::Message => {
1041 let raw = mail.get_body_raw()?;
1042 if raw.is_empty() {
1043 return Ok(false);
1044 }
1045 let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
1046
1047 Box::pin(self.parse_mime_recursive(context, &mail, is_related)).await
1048 }
1049 MimeS::Single => {
1050 self.add_single_part_if_known(context, mail, is_related)
1051 .await
1052 }
1053 }
1054 }
1055
1056 async fn handle_multiple(
1057 &mut self,
1058 context: &Context,
1059 mail: &mailparse::ParsedMail<'_>,
1060 is_related: bool,
1061 ) -> Result<bool> {
1062 let mut any_part_added = false;
1063 let mimetype = get_mime_type(
1064 mail,
1065 &get_attachment_filename(context, mail)?,
1066 self.has_chat_version(),
1067 )?
1068 .0;
1069 match (mimetype.type_(), mimetype.subtype().as_str()) {
1070 (mime::MULTIPART, "alternative") => {
1071 for cur_data in mail.subparts.iter().rev() {
1083 let (mime_type, _viewtype) = get_mime_type(
1084 cur_data,
1085 &get_attachment_filename(context, cur_data)?,
1086 self.has_chat_version(),
1087 )?;
1088
1089 if mime_type == mime::TEXT_PLAIN || mime_type.type_() == mime::MULTIPART {
1090 any_part_added = self
1091 .parse_mime_recursive(context, cur_data, is_related)
1092 .await?;
1093 break;
1094 }
1095 }
1096
1097 for cur_data in mail.subparts.iter().rev() {
1106 let mimetype = cur_data.ctype.mimetype.parse::<Mime>()?;
1107 if mimetype.type_() == mime::TEXT && mimetype.subtype() == "calendar" {
1108 let filename = get_attachment_filename(context, cur_data)?
1109 .unwrap_or_else(|| "calendar.ics".to_string());
1110 self.do_add_single_file_part(
1111 context,
1112 Viewtype::File,
1113 mimetype,
1114 &mail.ctype.mimetype.to_lowercase(),
1115 &mail.get_body_raw()?,
1116 &filename,
1117 is_related,
1118 )
1119 .await?;
1120 }
1121 }
1122
1123 if !any_part_added {
1124 for cur_part in mail.subparts.iter().rev() {
1125 if self
1126 .parse_mime_recursive(context, cur_part, is_related)
1127 .await?
1128 {
1129 any_part_added = true;
1130 break;
1131 }
1132 }
1133 }
1134 if any_part_added && mail.subparts.len() > 1 {
1135 self.is_mime_modified = true;
1139 }
1140 }
1141 (mime::MULTIPART, "signed") => {
1142 if let Some(first) = mail.subparts.first() {
1151 any_part_added = self
1152 .parse_mime_recursive(context, first, is_related)
1153 .await?;
1154 }
1155 }
1156 (mime::MULTIPART, "report") => {
1157 if mail.subparts.len() >= 2 {
1159 match mail.ctype.params.get("report-type").map(|s| s as &str) {
1160 Some("disposition-notification") => {
1161 if let Some(report) = self.process_report(context, mail)? {
1162 self.mdn_reports.push(report);
1163 }
1164
1165 let part = Part {
1170 typ: Viewtype::Unknown,
1171 ..Default::default()
1172 };
1173 self.parts.push(part);
1174
1175 any_part_added = true;
1176 }
1177 Some("delivery-status") | None => {
1179 if let Some(report) = self.process_delivery_status(context, mail)? {
1180 self.delivery_report = Some(report);
1181 }
1182
1183 for cur_data in &mail.subparts {
1185 if self
1186 .parse_mime_recursive(context, cur_data, is_related)
1187 .await?
1188 {
1189 any_part_added = true;
1190 }
1191 }
1192 }
1193 Some("multi-device-sync") => {
1194 if let Some(second) = mail.subparts.get(1) {
1195 self.add_single_part_if_known(context, second, is_related)
1196 .await?;
1197 }
1198 }
1199 Some("status-update") => {
1200 if let Some(second) = mail.subparts.get(1) {
1201 self.add_single_part_if_known(context, second, is_related)
1202 .await?;
1203 }
1204 }
1205 Some(_) => {
1206 for cur_data in &mail.subparts {
1207 if self
1208 .parse_mime_recursive(context, cur_data, is_related)
1209 .await?
1210 {
1211 any_part_added = true;
1212 }
1213 }
1214 }
1215 }
1216 }
1217 }
1218 _ => {
1219 for cur_data in &mail.subparts {
1222 if self
1223 .parse_mime_recursive(context, cur_data, is_related)
1224 .await?
1225 {
1226 any_part_added = true;
1227 }
1228 }
1229 }
1230 }
1231
1232 Ok(any_part_added)
1233 }
1234
1235 async fn add_single_part_if_known(
1237 &mut self,
1238 context: &Context,
1239 mail: &mailparse::ParsedMail<'_>,
1240 is_related: bool,
1241 ) -> Result<bool> {
1242 let filename = get_attachment_filename(context, mail)?;
1244 let (mime_type, msg_type) = get_mime_type(mail, &filename, self.has_chat_version())?;
1245 let raw_mime = mail.ctype.mimetype.to_lowercase();
1246
1247 let old_part_count = self.parts.len();
1248
1249 match filename {
1250 Some(filename) => {
1251 self.do_add_single_file_part(
1252 context,
1253 msg_type,
1254 mime_type,
1255 &raw_mime,
1256 &mail.get_body_raw()?,
1257 &filename,
1258 is_related,
1259 )
1260 .await?;
1261 }
1262 None => {
1263 match mime_type.type_() {
1264 mime::IMAGE | mime::AUDIO | mime::VIDEO | mime::APPLICATION => {
1265 warn!(context, "Missing attachment");
1266 return Ok(false);
1267 }
1268 mime::TEXT
1269 if mail.get_content_disposition().disposition
1270 == DispositionType::Extension("reaction".to_string()) =>
1271 {
1272 let decoded_data = match mail.get_body() {
1274 Ok(decoded_data) => decoded_data,
1275 Err(err) => {
1276 warn!(context, "Invalid body parsed {:#}", err);
1277 return Ok(false);
1279 }
1280 };
1281
1282 let part = Part {
1283 typ: Viewtype::Text,
1284 mimetype: Some(mime_type),
1285 msg: decoded_data,
1286 is_reaction: true,
1287 ..Default::default()
1288 };
1289 self.do_add_single_part(part);
1290 return Ok(true);
1291 }
1292 mime::TEXT | mime::HTML => {
1293 let decoded_data = match mail.get_body() {
1294 Ok(decoded_data) => decoded_data,
1295 Err(err) => {
1296 warn!(context, "Invalid body parsed {:#}", err);
1297 return Ok(false);
1299 }
1300 };
1301
1302 let is_plaintext = mime_type == mime::TEXT_PLAIN;
1303 let mut dehtml_failed = false;
1304
1305 let SimplifiedText {
1306 text: simplified_txt,
1307 is_forwarded,
1308 is_cut,
1309 top_quote,
1310 footer,
1311 } = if decoded_data.is_empty() {
1312 Default::default()
1313 } else {
1314 let is_html = mime_type == mime::TEXT_HTML;
1315 if is_html {
1316 self.is_mime_modified = true;
1317 if let Some(text) = dehtml(&decoded_data) {
1318 text
1319 } else {
1320 dehtml_failed = true;
1321 SimplifiedText {
1322 text: decoded_data.clone(),
1323 ..Default::default()
1324 }
1325 }
1326 } else {
1327 simplify(decoded_data.clone(), self.has_chat_version())
1328 }
1329 };
1330
1331 self.is_mime_modified = self.is_mime_modified
1332 || ((is_forwarded || is_cut || top_quote.is_some())
1333 && !self.has_chat_version());
1334
1335 let is_format_flowed = if let Some(format) = mail.ctype.params.get("format")
1336 {
1337 format.as_str().eq_ignore_ascii_case("flowed")
1338 } else {
1339 false
1340 };
1341
1342 let (simplified_txt, simplified_quote) = if mime_type.type_() == mime::TEXT
1343 && mime_type.subtype() == mime::PLAIN
1344 && is_format_flowed
1345 {
1346 let delsp = if let Some(delsp) = mail.ctype.params.get("delsp") {
1347 delsp.as_str().eq_ignore_ascii_case("yes")
1348 } else {
1349 false
1350 };
1351 let unflowed_text = unformat_flowed(&simplified_txt, delsp);
1352 let unflowed_quote = top_quote.map(|q| unformat_flowed(&q, delsp));
1353 (unflowed_text, unflowed_quote)
1354 } else {
1355 (simplified_txt, top_quote)
1356 };
1357
1358 let (simplified_txt, was_truncated) =
1359 truncate_msg_text(context, simplified_txt).await?;
1360 if was_truncated {
1361 self.is_mime_modified = was_truncated;
1362 }
1363
1364 if !simplified_txt.is_empty() || simplified_quote.is_some() {
1365 let mut part = Part {
1366 dehtml_failed,
1367 typ: Viewtype::Text,
1368 mimetype: Some(mime_type),
1369 msg: simplified_txt,
1370 ..Default::default()
1371 };
1372 if let Some(quote) = simplified_quote {
1373 part.param.set(Param::Quote, quote);
1374 }
1375 part.msg_raw = Some(decoded_data);
1376 self.do_add_single_part(part);
1377 }
1378
1379 if is_forwarded {
1380 self.is_forwarded = true;
1381 }
1382
1383 if self.footer.is_none() && is_plaintext {
1384 self.footer = Some(footer.unwrap_or_default());
1385 }
1386 }
1387 _ => {}
1388 }
1389 }
1390 }
1391
1392 Ok(self.parts.len() > old_part_count)
1394 }
1395
1396 #[expect(clippy::too_many_arguments)]
1397 async fn do_add_single_file_part(
1398 &mut self,
1399 context: &Context,
1400 msg_type: Viewtype,
1401 mime_type: Mime,
1402 raw_mime: &str,
1403 decoded_data: &[u8],
1404 filename: &str,
1405 is_related: bool,
1406 ) -> Result<()> {
1407 if mime_type.type_() == mime::APPLICATION
1409 && mime_type.subtype().as_str() == "pgp-keys"
1410 && Self::try_set_peer_key_from_file_part(context, decoded_data).await?
1411 {
1412 return Ok(());
1413 }
1414 let mut part = Part::default();
1415 let msg_type = if context
1416 .is_webxdc_file(filename, decoded_data)
1417 .await
1418 .unwrap_or(false)
1419 {
1420 Viewtype::Webxdc
1421 } else if filename.ends_with(".kml") {
1422 if filename.starts_with("location") || filename.starts_with("message") {
1425 let parsed = location::Kml::parse(decoded_data)
1426 .map_err(|err| {
1427 warn!(context, "failed to parse kml part: {:#}", err);
1428 })
1429 .ok();
1430 if filename.starts_with("location") {
1431 self.location_kml = parsed;
1432 } else {
1433 self.message_kml = parsed;
1434 }
1435 return Ok(());
1436 }
1437 msg_type
1438 } else if filename == "multi-device-sync.json" {
1439 if !context.get_config_bool(Config::SyncMsgs).await? {
1440 return Ok(());
1441 }
1442 let serialized = String::from_utf8_lossy(decoded_data)
1443 .parse()
1444 .unwrap_or_default();
1445 self.sync_items = context
1446 .parse_sync_items(serialized)
1447 .map_err(|err| {
1448 warn!(context, "failed to parse sync data: {:#}", err);
1449 })
1450 .ok();
1451 return Ok(());
1452 } else if filename == "status-update.json" {
1453 let serialized = String::from_utf8_lossy(decoded_data)
1454 .parse()
1455 .unwrap_or_default();
1456 self.webxdc_status_update = Some(serialized);
1457 return Ok(());
1458 } else if msg_type == Viewtype::Vcard {
1459 if let Some(summary) = get_vcard_summary(decoded_data) {
1460 part.param.set(Param::Summary1, summary);
1461 msg_type
1462 } else {
1463 Viewtype::File
1464 }
1465 } else if msg_type == Viewtype::Image
1466 || msg_type == Viewtype::Gif
1467 || msg_type == Viewtype::Sticker
1468 {
1469 match get_filemeta(decoded_data) {
1470 Ok((width, height)) if width * height <= constants::MAX_RCVD_IMAGE_PIXELS => {
1472 part.param.set_i64(Param::Width, width.into());
1473 part.param.set_i64(Param::Height, height.into());
1474 msg_type
1475 }
1476 _ => Viewtype::File,
1478 }
1479 } else {
1480 msg_type
1481 };
1482
1483 let blob =
1487 match BlobObject::create_and_deduplicate_from_bytes(context, decoded_data, filename) {
1488 Ok(blob) => blob,
1489 Err(err) => {
1490 error!(
1491 context,
1492 "Could not add blob for mime part {}, error {:#}", filename, err
1493 );
1494 return Ok(());
1495 }
1496 };
1497 info!(context, "added blobfile: {:?}", blob.as_name());
1498
1499 part.typ = msg_type;
1500 part.org_filename = Some(filename.to_string());
1501 part.mimetype = Some(mime_type);
1502 part.bytes = decoded_data.len();
1503 part.param.set(Param::File, blob.as_name());
1504 part.param.set(Param::Filename, filename);
1505 part.param.set(Param::MimeType, raw_mime);
1506 part.is_related = is_related;
1507
1508 self.do_add_single_part(part);
1509 Ok(())
1510 }
1511
1512 async fn try_set_peer_key_from_file_part(
1514 context: &Context,
1515 decoded_data: &[u8],
1516 ) -> Result<bool> {
1517 let key = match str::from_utf8(decoded_data) {
1518 Err(err) => {
1519 warn!(context, "PGP key attachment is not a UTF-8 file: {}", err);
1520 return Ok(false);
1521 }
1522 Ok(key) => key,
1523 };
1524 let key = match SignedPublicKey::from_asc(key) {
1525 Err(err) => {
1526 warn!(
1527 context,
1528 "PGP key attachment is not an ASCII-armored file: {err:#}."
1529 );
1530 return Ok(false);
1531 }
1532 Ok(key) => key,
1533 };
1534 if let Err(err) = key.verify() {
1535 warn!(context, "Attached PGP key verification failed: {err:#}.");
1536 return Ok(false);
1537 }
1538
1539 let fingerprint = key.dc_fingerprint().hex();
1540 context
1541 .sql
1542 .execute(
1543 "INSERT INTO public_keys (fingerprint, public_key)
1544 VALUES (?, ?)
1545 ON CONFLICT (fingerprint)
1546 DO NOTHING",
1547 (&fingerprint, key.to_bytes()),
1548 )
1549 .await?;
1550
1551 info!(context, "Imported PGP key {fingerprint} from attachment.");
1552 Ok(true)
1553 }
1554
1555 pub(crate) fn do_add_single_part(&mut self, mut part: Part) {
1556 if self.was_encrypted() {
1557 part.param.set_int(Param::GuaranteeE2ee, 1);
1558 }
1559 self.parts.push(part);
1560 }
1561
1562 pub(crate) fn get_mailinglist_header(&self) -> Option<&str> {
1563 if let Some(list_id) = self.get_header(HeaderDef::ListId) {
1564 return Some(list_id);
1567 } else if let Some(sender) = self.get_header(HeaderDef::Sender) {
1568 if let Some(precedence) = self.get_header(HeaderDef::Precedence) {
1571 if precedence == "list" || precedence == "bulk" {
1572 return Some(sender);
1576 }
1577 }
1578 }
1579 None
1580 }
1581
1582 pub(crate) fn is_mailinglist_message(&self) -> bool {
1583 self.get_mailinglist_header().is_some()
1584 }
1585
1586 pub(crate) fn is_schleuder_message(&self) -> bool {
1588 if let Some(list_help) = self.get_header(HeaderDef::ListHelp) {
1589 list_help == "<https://schleuder.org/>"
1590 } else {
1591 false
1592 }
1593 }
1594
1595 pub(crate) fn is_call(&self) -> bool {
1597 self.parts
1598 .first()
1599 .is_some_and(|part| part.typ == Viewtype::Call)
1600 }
1601
1602 pub(crate) fn get_rfc724_mid(&self) -> Option<String> {
1603 self.get_header(HeaderDef::MessageId)
1604 .and_then(|msgid| parse_message_id(msgid).ok())
1605 }
1606
1607 fn remove_secured_headers(
1608 headers: &mut HashMap<String, String>,
1609 removed: &mut HashSet<String>,
1610 ) {
1611 remove_header(headers, "secure-join-fingerprint", removed);
1612 remove_header(headers, "secure-join-auth", removed);
1613 remove_header(headers, "chat-verified", removed);
1614 remove_header(headers, "autocrypt-gossip", removed);
1615
1616 if let Some(secure_join) = remove_header(headers, "secure-join", removed) {
1618 if secure_join == "vc-request" || secure_join == "vg-request" {
1619 headers.insert("secure-join".to_string(), secure_join);
1620 }
1621 }
1622 }
1623
1624 #[allow(clippy::too_many_arguments)]
1625 fn merge_headers(
1626 context: &Context,
1627 headers: &mut HashMap<String, String>,
1628 headers_removed: &mut HashSet<String>,
1629 recipients: &mut Vec<SingleInfo>,
1630 past_members: &mut Vec<SingleInfo>,
1631 from: &mut Option<SingleInfo>,
1632 list_post: &mut Option<String>,
1633 chat_disposition_notification_to: &mut Option<SingleInfo>,
1634 fields: &[mailparse::MailHeader<'_>],
1635 ) {
1636 headers.retain(|k, _| {
1637 !is_protected(k) || {
1638 headers_removed.insert(k.to_string());
1639 false
1640 }
1641 });
1642 for field in fields {
1643 let key = field.get_key().to_lowercase();
1645 if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
1646 match addrparse_header(field) {
1647 Ok(addrlist) => {
1648 *chat_disposition_notification_to = addrlist.extract_single_info();
1649 }
1650 Err(e) => warn!(context, "Could not read {} address: {}", key, e),
1651 }
1652 } else {
1653 let value = field.get_value();
1654 headers.insert(key.to_string(), value);
1655 }
1656 }
1657 let recipients_new = get_recipients(fields);
1658 if !recipients_new.is_empty() {
1659 *recipients = recipients_new;
1660 }
1661 let past_members_addresses =
1662 get_all_addresses_from_header(fields, "chat-group-past-members");
1663 if !past_members_addresses.is_empty() {
1664 *past_members = past_members_addresses;
1665 }
1666 let from_new = get_from(fields);
1667 if from_new.is_some() {
1668 *from = from_new;
1669 }
1670 let list_post_new = get_list_post(fields);
1671 if list_post_new.is_some() {
1672 *list_post = list_post_new;
1673 }
1674 }
1675
1676 fn process_report(
1677 &self,
1678 context: &Context,
1679 report: &mailparse::ParsedMail<'_>,
1680 ) -> Result<Option<Report>> {
1681 let report_body = if let Some(subpart) = report.subparts.get(1) {
1683 subpart.get_body_raw()?
1684 } else {
1685 bail!("Report does not have second MIME part");
1686 };
1687 let (report_fields, _) = mailparse::parse_headers(&report_body)?;
1688
1689 if report_fields
1691 .get_header_value(HeaderDef::Disposition)
1692 .is_none()
1693 {
1694 warn!(
1695 context,
1696 "Ignoring unknown disposition-notification, Message-Id: {:?}.",
1697 report_fields.get_header_value(HeaderDef::MessageId)
1698 );
1699 return Ok(None);
1700 };
1701
1702 let original_message_id = report_fields
1703 .get_header_value(HeaderDef::OriginalMessageId)
1704 .or_else(|| report.headers.get_header_value(HeaderDef::InReplyTo))
1707 .and_then(|v| parse_message_id(&v).ok());
1708 let additional_message_ids = report_fields
1709 .get_header_value(HeaderDef::AdditionalMessageIds)
1710 .map_or_else(Vec::new, |v| {
1711 v.split(' ')
1712 .filter_map(|s| parse_message_id(s).ok())
1713 .collect()
1714 });
1715
1716 Ok(Some(Report {
1717 original_message_id,
1718 additional_message_ids,
1719 }))
1720 }
1721
1722 fn process_delivery_status(
1723 &self,
1724 context: &Context,
1725 report: &mailparse::ParsedMail<'_>,
1726 ) -> Result<Option<DeliveryReport>> {
1727 let mut failure = true;
1729
1730 if let Some(status_part) = report.subparts.get(1) {
1731 if status_part.ctype.mimetype != "message/delivery-status"
1734 && status_part.ctype.mimetype != "message/global-delivery-status"
1735 {
1736 warn!(
1737 context,
1738 "Second part of Delivery Status Notification is not message/delivery-status or message/global-delivery-status, ignoring"
1739 );
1740 return Ok(None);
1741 }
1742
1743 let status_body = status_part.get_body_raw()?;
1744
1745 let (_, sz) = mailparse::parse_headers(&status_body)?;
1747
1748 if let Some(status_body) = status_body.get(sz..) {
1750 let (status_fields, _) = mailparse::parse_headers(status_body)?;
1751 if let Some(action) = status_fields.get_first_value("action") {
1752 if action != "failed" {
1753 info!(context, "DSN with {:?} action", action);
1754 failure = false;
1755 }
1756 } else {
1757 warn!(context, "DSN without action");
1758 }
1759 } else {
1760 warn!(context, "DSN without per-recipient fields");
1761 }
1762 } else {
1763 return Ok(None);
1765 }
1766
1767 if let Some(original_msg) = report.subparts.get(2).filter(|p| {
1769 p.ctype.mimetype.contains("rfc822")
1770 || p.ctype.mimetype == "message/global"
1771 || p.ctype.mimetype == "message/global-headers"
1772 }) {
1773 let report_body = original_msg.get_body_raw()?;
1774 let (report_fields, _) = mailparse::parse_headers(&report_body)?;
1775
1776 if let Some(original_message_id) = report_fields
1777 .get_header_value(HeaderDef::MessageId)
1778 .and_then(|v| parse_message_id(&v).ok())
1779 {
1780 return Ok(Some(DeliveryReport {
1781 rfc724_mid: original_message_id,
1782 failure,
1783 }));
1784 }
1785
1786 warn!(
1787 context,
1788 "ignoring unknown ndn-notification, Message-Id: {:?}",
1789 report_fields.get_header_value(HeaderDef::MessageId)
1790 );
1791 }
1792
1793 Ok(None)
1794 }
1795
1796 fn maybe_remove_bad_parts(&mut self) {
1797 let good_parts = self.parts.iter().filter(|p| !p.dehtml_failed).count();
1798 if good_parts == 0 {
1799 self.parts.truncate(1);
1801 } else if good_parts < self.parts.len() {
1802 self.parts.retain(|p| !p.dehtml_failed);
1803 }
1804
1805 if !self.has_chat_version() && self.is_mime_modified {
1813 fn is_related_image(p: &&Part) -> bool {
1814 (p.typ == Viewtype::Image || p.typ == Viewtype::Gif) && p.is_related
1815 }
1816 let related_image_cnt = self.parts.iter().filter(is_related_image).count();
1817 if related_image_cnt > 1 {
1818 let mut is_first_image = true;
1819 self.parts.retain(|p| {
1820 let retain = is_first_image || !is_related_image(&p);
1821 if p.typ == Viewtype::Image || p.typ == Viewtype::Gif {
1822 is_first_image = false;
1823 }
1824 retain
1825 });
1826 }
1827 }
1828 }
1829
1830 fn maybe_remove_inline_mailinglist_footer(&mut self) {
1840 if self.is_mailinglist_message() && !self.is_schleuder_message() {
1841 let text_part_cnt = self
1842 .parts
1843 .iter()
1844 .filter(|p| p.typ == Viewtype::Text)
1845 .count();
1846 if text_part_cnt == 2 {
1847 if let Some(last_part) = self.parts.last() {
1848 if last_part.typ == Viewtype::Text {
1849 self.parts.pop();
1850 }
1851 }
1852 }
1853 }
1854 }
1855
1856 async fn heuristically_parse_ndn(&mut self, context: &Context) {
1860 let maybe_ndn = if let Some(from) = self.get_header(HeaderDef::From_) {
1861 let from = from.to_ascii_lowercase();
1862 from.contains("mailer-daemon") || from.contains("mail-daemon")
1863 } else {
1864 false
1865 };
1866 if maybe_ndn && self.delivery_report.is_none() {
1867 for original_message_id in self
1868 .parts
1869 .iter()
1870 .filter_map(|part| part.msg_raw.as_ref())
1871 .flat_map(|part| part.lines())
1872 .filter_map(|line| line.split_once("Message-ID:"))
1873 .filter_map(|(_, message_id)| parse_message_id(message_id).ok())
1874 {
1875 if let Ok(Some(_)) = message::rfc724_mid_exists(context, &original_message_id).await
1876 {
1877 self.delivery_report = Some(DeliveryReport {
1878 rfc724_mid: original_message_id,
1879 failure: true,
1880 })
1881 }
1882 }
1883 }
1884 }
1885
1886 pub async fn handle_reports(&self, context: &Context, from_id: ContactId, parts: &[Part]) {
1890 for report in &self.mdn_reports {
1891 for original_message_id in report
1892 .original_message_id
1893 .iter()
1894 .chain(&report.additional_message_ids)
1895 {
1896 if let Err(err) =
1897 handle_mdn(context, from_id, original_message_id, self.timestamp_sent).await
1898 {
1899 warn!(context, "Could not handle MDN: {err:#}.");
1900 }
1901 }
1902 }
1903
1904 if let Some(delivery_report) = &self.delivery_report {
1905 if delivery_report.failure {
1906 let error = parts
1907 .iter()
1908 .find(|p| p.typ == Viewtype::Text)
1909 .map(|p| p.msg.clone());
1910 if let Err(err) = handle_ndn(context, delivery_report, error).await {
1911 warn!(context, "Could not handle NDN: {err:#}.");
1912 }
1913 }
1914 }
1915 }
1916
1917 pub async fn get_parent_timestamp(&self, context: &Context) -> Result<Option<i64>> {
1922 let parent_timestamp = if let Some(field) = self
1923 .get_header(HeaderDef::InReplyTo)
1924 .and_then(|msgid| parse_message_id(msgid).ok())
1925 {
1926 context
1927 .sql
1928 .query_get_value("SELECT timestamp FROM msgs WHERE rfc724_mid=?", (field,))
1929 .await?
1930 } else {
1931 None
1932 };
1933 Ok(parent_timestamp)
1934 }
1935
1936 pub fn chat_group_member_timestamps(&self) -> Option<Vec<i64>> {
1940 let now = time() + constants::TIMESTAMP_SENT_TOLERANCE;
1941 self.get_header(HeaderDef::ChatGroupMemberTimestamps)
1942 .map(|h| {
1943 h.split_ascii_whitespace()
1944 .filter_map(|ts| ts.parse::<i64>().ok())
1945 .map(|ts| std::cmp::min(now, ts))
1946 .collect()
1947 })
1948 }
1949
1950 pub fn chat_group_member_fingerprints(&self) -> Vec<Fingerprint> {
1953 if let Some(header) = self.get_header(HeaderDef::ChatGroupMemberFpr) {
1954 header
1955 .split_ascii_whitespace()
1956 .filter_map(|fpr| Fingerprint::from_str(fpr).ok())
1957 .collect()
1958 } else {
1959 Vec::new()
1960 }
1961 }
1962}
1963
1964fn remove_header(
1965 headers: &mut HashMap<String, String>,
1966 key: &str,
1967 removed: &mut HashSet<String>,
1968) -> Option<String> {
1969 if let Some((k, v)) = headers.remove_entry(key) {
1970 removed.insert(k);
1971 Some(v)
1972 } else {
1973 None
1974 }
1975}
1976
1977async fn parse_gossip_headers(
1983 context: &Context,
1984 from: &str,
1985 recipients: &[SingleInfo],
1986 gossip_headers: Vec<String>,
1987) -> Result<BTreeMap<String, GossipedKey>> {
1988 let mut gossiped_keys: BTreeMap<String, GossipedKey> = Default::default();
1990
1991 for value in &gossip_headers {
1992 let header = match value.parse::<Aheader>() {
1993 Ok(header) => header,
1994 Err(err) => {
1995 warn!(context, "Failed parsing Autocrypt-Gossip header: {}", err);
1996 continue;
1997 }
1998 };
1999
2000 if !recipients
2001 .iter()
2002 .any(|info| addr_cmp(&info.addr, &header.addr))
2003 {
2004 warn!(
2005 context,
2006 "Ignoring gossiped \"{}\" as the address is not in To/Cc list.", &header.addr,
2007 );
2008 continue;
2009 }
2010 if addr_cmp(from, &header.addr) {
2011 warn!(
2013 context,
2014 "Ignoring gossiped \"{}\" as it equals the From address", &header.addr,
2015 );
2016 continue;
2017 }
2018
2019 let fingerprint = header.public_key.dc_fingerprint().hex();
2020 context
2021 .sql
2022 .execute(
2023 "INSERT INTO public_keys (fingerprint, public_key)
2024 VALUES (?, ?)
2025 ON CONFLICT (fingerprint)
2026 DO NOTHING",
2027 (&fingerprint, header.public_key.to_bytes()),
2028 )
2029 .await?;
2030
2031 let gossiped_key = GossipedKey {
2032 public_key: header.public_key,
2033
2034 verified: header.verified,
2035 };
2036 gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key);
2037 }
2038
2039 Ok(gossiped_keys)
2040}
2041
2042#[derive(Debug)]
2044pub(crate) struct Report {
2045 original_message_id: Option<String>,
2050 additional_message_ids: Vec<String>,
2052}
2053
2054#[derive(Debug)]
2056pub(crate) struct DeliveryReport {
2057 pub rfc724_mid: String,
2058 pub failure: bool,
2059}
2060
2061pub(crate) fn parse_message_ids(ids: &str) -> Vec<String> {
2062 let mut msgids = Vec::new();
2064 for id in ids.split_whitespace() {
2065 let mut id = id.to_string();
2066 if let Some(id_without_prefix) = id.strip_prefix('<') {
2067 id = id_without_prefix.to_string();
2068 };
2069 if let Some(id_without_suffix) = id.strip_suffix('>') {
2070 id = id_without_suffix.to_string();
2071 };
2072 if !id.is_empty() {
2073 msgids.push(id);
2074 }
2075 }
2076 msgids
2077}
2078
2079pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
2080 if let Some(id) = parse_message_ids(ids).first() {
2081 Ok(id.to_string())
2082 } else {
2083 bail!("could not parse message_id: {ids}");
2084 }
2085}
2086
2087fn is_protected(key: &str) -> bool {
2093 key.starts_with("chat-")
2094 || matches!(
2095 key,
2096 "return-path"
2097 | "auto-submitted"
2098 | "autocrypt-setup-message"
2099 | "date"
2100 | "from"
2101 | "sender"
2102 | "reply-to"
2103 | "to"
2104 | "cc"
2105 | "bcc"
2106 | "message-id"
2107 | "in-reply-to"
2108 | "references"
2109 | "secure-join"
2110 )
2111}
2112
2113pub(crate) fn is_hidden(key: &str) -> bool {
2115 matches!(
2116 key,
2117 "chat-user-avatar" | "chat-group-avatar" | "chat-delete" | "chat-edit"
2118 )
2119}
2120
2121#[derive(Debug, Default, Clone)]
2123pub struct Part {
2124 pub typ: Viewtype,
2126
2127 pub mimetype: Option<Mime>,
2129
2130 pub msg: String,
2132
2133 pub msg_raw: Option<String>,
2135
2136 pub bytes: usize,
2138
2139 pub param: Params,
2141
2142 pub(crate) org_filename: Option<String>,
2144
2145 pub error: Option<String>,
2147
2148 pub(crate) dehtml_failed: bool,
2150
2151 pub(crate) is_related: bool,
2158
2159 pub(crate) is_reaction: bool,
2161}
2162
2163fn get_mime_type(
2168 mail: &mailparse::ParsedMail<'_>,
2169 filename: &Option<String>,
2170 is_chat_msg: bool,
2171) -> Result<(Mime, Viewtype)> {
2172 let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
2173
2174 let viewtype = match mimetype.type_() {
2175 mime::TEXT => match mimetype.subtype() {
2176 mime::VCARD => Viewtype::Vcard,
2177 mime::PLAIN | mime::HTML if !is_attachment_disposition(mail) => Viewtype::Text,
2178 _ => Viewtype::File,
2179 },
2180 mime::IMAGE => match mimetype.subtype() {
2181 mime::GIF => Viewtype::Gif,
2182 mime::SVG => Viewtype::File,
2183 _ => Viewtype::Image,
2184 },
2185 mime::AUDIO => Viewtype::Audio,
2186 mime::VIDEO => Viewtype::Video,
2187 mime::MULTIPART => Viewtype::Unknown,
2188 mime::MESSAGE => {
2189 if is_attachment_disposition(mail) {
2190 Viewtype::File
2191 } else {
2192 Viewtype::Unknown
2200 }
2201 }
2202 mime::APPLICATION => match mimetype.subtype() {
2203 mime::OCTET_STREAM => match filename {
2204 Some(filename) if !is_chat_msg => {
2205 match message::guess_msgtype_from_path_suffix(Path::new(&filename)) {
2206 Some((viewtype, _)) => viewtype,
2207 None => Viewtype::File,
2208 }
2209 }
2210 _ => Viewtype::File,
2211 },
2212 _ => Viewtype::File,
2213 },
2214 _ => Viewtype::Unknown,
2215 };
2216
2217 Ok((mimetype, viewtype))
2218}
2219
2220fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
2221 let ct = mail.get_content_disposition();
2222 ct.disposition == DispositionType::Attachment
2223 && ct
2224 .params
2225 .iter()
2226 .any(|(key, _value)| key.starts_with("filename"))
2227}
2228
2229fn get_attachment_filename(
2236 context: &Context,
2237 mail: &mailparse::ParsedMail,
2238) -> Result<Option<String>> {
2239 let ct = mail.get_content_disposition();
2240
2241 let mut desired_filename = ct.params.get("filename").map(|s| s.to_string());
2244
2245 if desired_filename.is_none() {
2246 if let Some(name) = ct.params.get("filename*").map(|s| s.to_string()) {
2247 warn!(context, "apostrophed encoding invalid: {}", name);
2251 desired_filename = Some(name);
2252 }
2253 }
2254
2255 if desired_filename.is_none() {
2257 desired_filename = ct.params.get("name").map(|s| s.to_string());
2258 }
2259
2260 if desired_filename.is_none() {
2263 desired_filename = mail.ctype.params.get("name").map(|s| s.to_string());
2264 }
2265
2266 if desired_filename.is_none() && ct.disposition == DispositionType::Attachment {
2268 if let Some(subtype) = mail.ctype.mimetype.split('/').nth(1) {
2269 desired_filename = Some(format!("file.{subtype}",));
2270 } else {
2271 bail!(
2272 "could not determine attachment filename: {:?}",
2273 ct.disposition
2274 );
2275 };
2276 }
2277
2278 let desired_filename = desired_filename.map(|filename| sanitize_bidi_characters(&filename));
2279
2280 Ok(desired_filename)
2281}
2282
2283pub(crate) fn get_recipients(headers: &[MailHeader]) -> Vec<SingleInfo> {
2285 let to_addresses = get_all_addresses_from_header(headers, "to");
2286 let cc_addresses = get_all_addresses_from_header(headers, "cc");
2287
2288 let mut res = to_addresses;
2289 res.extend(cc_addresses);
2290 res
2291}
2292
2293pub(crate) fn get_from(headers: &[MailHeader]) -> Option<SingleInfo> {
2295 let all = get_all_addresses_from_header(headers, "from");
2296 tools::single_value(all)
2297}
2298
2299pub(crate) fn get_list_post(headers: &[MailHeader]) -> Option<String> {
2301 get_all_addresses_from_header(headers, "list-post")
2302 .into_iter()
2303 .next()
2304 .map(|s| s.addr)
2305}
2306
2307fn get_all_addresses_from_header(headers: &[MailHeader], header: &str) -> Vec<SingleInfo> {
2319 let mut result: Vec<SingleInfo> = Default::default();
2320
2321 if let Some(header) = headers
2322 .iter()
2323 .rev()
2324 .find(|h| h.get_key().to_lowercase() == header)
2325 {
2326 if let Ok(addrs) = mailparse::addrparse_header(header) {
2327 for addr in addrs.iter() {
2328 match addr {
2329 mailparse::MailAddr::Single(info) => {
2330 result.push(SingleInfo {
2331 addr: addr_normalize(&info.addr).to_lowercase(),
2332 display_name: info.display_name.clone(),
2333 });
2334 }
2335 mailparse::MailAddr::Group(infos) => {
2336 for info in &infos.addrs {
2337 result.push(SingleInfo {
2338 addr: addr_normalize(&info.addr).to_lowercase(),
2339 display_name: info.display_name.clone(),
2340 });
2341 }
2342 }
2343 }
2344 }
2345 }
2346 }
2347
2348 result
2349}
2350
2351async fn handle_mdn(
2352 context: &Context,
2353 from_id: ContactId,
2354 rfc724_mid: &str,
2355 timestamp_sent: i64,
2356) -> Result<()> {
2357 if from_id == ContactId::SELF {
2358 warn!(
2359 context,
2360 "Ignoring MDN sent to self, this is a bug on the sender device."
2361 );
2362
2363 return Ok(());
2366 }
2367
2368 let Some((msg_id, chat_id, has_mdns, is_dup)) = context
2369 .sql
2370 .query_row_optional(
2371 concat!(
2372 "SELECT",
2373 " m.id AS msg_id,",
2374 " c.id AS chat_id,",
2375 " mdns.contact_id AS mdn_contact",
2376 " FROM msgs m ",
2377 " LEFT JOIN chats c ON m.chat_id=c.id",
2378 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
2379 " WHERE rfc724_mid=? AND from_id=1",
2380 " ORDER BY msg_id DESC, mdn_contact=? DESC",
2381 " LIMIT 1",
2382 ),
2383 (&rfc724_mid, from_id),
2384 |row| {
2385 let msg_id: MsgId = row.get("msg_id")?;
2386 let chat_id: ChatId = row.get("chat_id")?;
2387 let mdn_contact: Option<ContactId> = row.get("mdn_contact")?;
2388 Ok((
2389 msg_id,
2390 chat_id,
2391 mdn_contact.is_some(),
2392 mdn_contact == Some(from_id),
2393 ))
2394 },
2395 )
2396 .await?
2397 else {
2398 info!(
2399 context,
2400 "Ignoring MDN, found no message with Message-ID {rfc724_mid:?} sent by us in the database.",
2401 );
2402 return Ok(());
2403 };
2404
2405 if is_dup {
2406 return Ok(());
2407 }
2408 context
2409 .sql
2410 .execute(
2411 "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?)",
2412 (msg_id, from_id, timestamp_sent),
2413 )
2414 .await?;
2415 if !has_mdns {
2416 context.emit_event(EventType::MsgRead { chat_id, msg_id });
2417 chatlist_events::emit_chatlist_item_changed(context, chat_id);
2419 }
2420 Ok(())
2421}
2422
2423async fn handle_ndn(
2426 context: &Context,
2427 failed: &DeliveryReport,
2428 error: Option<String>,
2429) -> Result<()> {
2430 if failed.rfc724_mid.is_empty() {
2431 return Ok(());
2432 }
2433
2434 let msgs: Vec<_> = context
2437 .sql
2438 .query_map(
2439 "SELECT id FROM msgs
2440 WHERE rfc724_mid=? AND from_id=1",
2441 (&failed.rfc724_mid,),
2442 |row| {
2443 let msg_id: MsgId = row.get(0)?;
2444 Ok(msg_id)
2445 },
2446 |rows| Ok(rows.collect::<Vec<_>>()),
2447 )
2448 .await?;
2449
2450 let error = if let Some(error) = error {
2451 error
2452 } else {
2453 "Delivery to at least one recipient failed.".to_string()
2454 };
2455 let err_msg = &error;
2456
2457 for msg in msgs {
2458 let msg_id = msg?;
2459 let mut message = Message::load_from_db(context, msg_id).await?;
2460 let aggregated_error = message
2461 .error
2462 .as_ref()
2463 .map(|err| format!("{err}\n\n{err_msg}"));
2464 set_msg_failed(
2465 context,
2466 &mut message,
2467 aggregated_error.as_ref().unwrap_or(err_msg),
2468 )
2469 .await?;
2470 }
2471
2472 Ok(())
2473}
2474
2475#[cfg(test)]
2476mod mimeparser_tests;