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, 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(
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 secrets: Vec<String> = context
359            .sql
360            .query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
361                let secret: String = row.get(0)?;
362                Ok(secret)
363            })
364            .await?;
365
366        let (mail, is_encrypted) =
367            match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
368                Ok(Some(mut msg)) => {
369                    mail_raw = msg.as_data_vec().unwrap_or_default();
370
371                    let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
372                    if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
373                        info!(
374                            context,
375                            "decrypted message mime-body:\n{}",
376                            String::from_utf8_lossy(&mail_raw),
377                        );
378                    }
379
380                    decrypted_msg = Some(msg);
381
382                    timestamp_sent = Self::get_timestamp_sent(
383                        &decrypted_mail.headers,
384                        timestamp_sent,
385                        timestamp_rcvd,
386                    );
387
388                    let protected_aheader_values = decrypted_mail
389                        .headers
390                        .get_all_values(HeaderDef::Autocrypt.into());
391                    if !protected_aheader_values.is_empty() {
392                        aheader_values = protected_aheader_values;
393                    }
394
395                    (Ok(decrypted_mail), true)
396                }
397                Ok(None) => {
398                    mail_raw = Vec::new();
399                    decrypted_msg = None;
400                    (Ok(mail), false)
401                }
402                Err(err) => {
403                    mail_raw = Vec::new();
404                    decrypted_msg = None;
405                    warn!(context, "decryption failed: {:#}", err);
406                    (Err(err), false)
407                }
408            };
409
410        let mut autocrypt_header = None;
411        if incoming {
412            for val in aheader_values.iter().rev() {
414                autocrypt_header = match Aheader::from_str(val) {
415                    Ok(header) if addr_cmp(&header.addr, &from.addr) => Some(header),
416                    Ok(header) => {
417                        warn!(
418                            context,
419                            "Autocrypt header address {:?} is not {:?}.", header.addr, from.addr
420                        );
421                        continue;
422                    }
423                    Err(err) => {
424                        warn!(context, "Failed to parse Autocrypt header: {:#}.", err);
425                        continue;
426                    }
427                };
428                break;
429            }
430        }
431
432        let autocrypt_fingerprint = if let Some(autocrypt_header) = &autocrypt_header {
433            let fingerprint = autocrypt_header.public_key.dc_fingerprint().hex();
434            let inserted = context
435                .sql
436                .execute(
437                    "INSERT INTO public_keys (fingerprint, public_key)
438                                 VALUES (?, ?)
439                                 ON CONFLICT (fingerprint)
440                                 DO NOTHING",
441                    (&fingerprint, autocrypt_header.public_key.to_bytes()),
442                )
443                .await?;
444            if inserted > 0 {
445                info!(
446                    context,
447                    "Saved key with fingerprint {fingerprint} from the Autocrypt header"
448                );
449            }
450            Some(fingerprint)
451        } else {
452            None
453        };
454
455        let mut public_keyring = if incoming {
456            if let Some(autocrypt_header) = autocrypt_header {
457                vec![autocrypt_header.public_key]
458            } else {
459                vec![]
460            }
461        } else {
462            key::load_self_public_keyring(context).await?
463        };
464
465        if let Some(signature) = match &decrypted_msg {
466            Some(pgp::composed::Message::Literal { .. }) => None,
467            Some(pgp::composed::Message::Compressed { .. }) => {
468                None
471            }
472            Some(pgp::composed::Message::SignedOnePass { reader, .. }) => reader.signature(),
473            Some(pgp::composed::Message::Signed { reader, .. }) => Some(reader.signature()),
474            Some(pgp::composed::Message::Encrypted { .. }) => {
475                None
477            }
478            None => None,
479        } {
480            for issuer_fingerprint in signature.issuer_fingerprint() {
481                let issuer_fingerprint =
482                    crate::key::Fingerprint::from(issuer_fingerprint.clone()).hex();
483                if let Some(public_key_bytes) = context
484                    .sql
485                    .query_row_optional(
486                        "SELECT public_key
487                         FROM public_keys
488                         WHERE fingerprint=?",
489                        (&issuer_fingerprint,),
490                        |row| {
491                            let bytes: Vec<u8> = row.get(0)?;
492                            Ok(bytes)
493                        },
494                    )
495                    .await?
496                {
497                    let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
498                    public_keyring.push(public_key)
499                }
500            }
501        }
502
503        let mut signatures = if let Some(ref decrypted_msg) = decrypted_msg {
504            crate::pgp::valid_signature_fingerprints(decrypted_msg, &public_keyring)
505        } else {
506            HashSet::new()
507        };
508
509        let mail = mail.as_ref().map(|mail| {
510            let (content, signatures_detached) = validate_detached_signature(mail, &public_keyring)
511                .unwrap_or((mail, Default::default()));
512            signatures.extend(signatures_detached);
513            content
514        });
515        if let (Ok(mail), true) = (mail, is_encrypted) {
516            if !signatures.is_empty() {
517                remove_header(&mut headers, "subject", &mut headers_removed);
521                remove_header(&mut headers, "list-id", &mut headers_removed);
522            }
523
524            let mut inner_from = None;
530
531            MimeMessage::merge_headers(
532                context,
533                &mut headers,
534                &mut headers_removed,
535                &mut recipients,
536                &mut past_members,
537                &mut inner_from,
538                &mut list_post,
539                &mut chat_disposition_notification_to,
540                &mail.headers,
541            );
542
543            if !signatures.is_empty() {
544                let gossip_headers = mail.headers.get_all_values("Autocrypt-Gossip");
549                gossiped_keys =
550                    parse_gossip_headers(context, &from.addr, &recipients, gossip_headers).await?;
551            }
552
553            if let Some(inner_from) = inner_from {
554                if !addr_cmp(&inner_from.addr, &from.addr) {
555                    warn!(
564                        context,
565                        "From header in encrypted part doesn't match the outer one",
566                    );
567
568                    bail!("From header is forged");
573                }
574                from = inner_from;
575            }
576        }
577        if signatures.is_empty() {
578            Self::remove_secured_headers(&mut headers, &mut headers_removed);
579        }
580        if !is_encrypted {
581            signatures.clear();
582        }
583
584        let mut parser = MimeMessage {
585            parts: Vec::new(),
586            headers,
587            #[cfg(test)]
588            headers_removed,
589
590            recipients,
591            past_members,
592            list_post,
593            from,
594            incoming,
595            chat_disposition_notification_to,
596            decrypting_failed: mail.is_err(),
597
598            signature: signatures.into_iter().last(),
600            autocrypt_fingerprint,
601            gossiped_keys,
602            is_forwarded: false,
603            mdn_reports: Vec::new(),
604            is_system_message: SystemMessage::Unknown,
605            location_kml: None,
606            message_kml: None,
607            sync_items: None,
608            webxdc_status_update: None,
609            user_avatar: None,
610            group_avatar: None,
611            delivery_report: None,
612            footer: None,
613            is_mime_modified: false,
614            decoded_data: Vec::new(),
615            hop_info,
616            is_bot: None,
617            timestamp_rcvd,
618            timestamp_sent,
619        };
620
621        match partial {
622            Some((org_bytes, err)) => {
623                parser
624                    .create_stub_from_partial_download(context, org_bytes, err)
625                    .await?;
626            }
627            None => match mail {
628                Ok(mail) => {
629                    parser.parse_mime_recursive(context, mail, false).await?;
630                }
631                Err(err) => {
632                    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.]";
633
634                    let part = Part {
635                        typ: Viewtype::Text,
636                        msg_raw: Some(txt.to_string()),
637                        msg: txt.to_string(),
638                        error: Some(format!("Decrypting failed: {err:#}")),
641                        ..Default::default()
642                    };
643                    parser.do_add_single_part(part);
644                }
645            },
646        };
647
648        let is_location_only = parser.location_kml.is_some() && parser.parts.is_empty();
649        if parser.mdn_reports.is_empty()
650            && !is_location_only
651            && parser.sync_items.is_none()
652            && parser.webxdc_status_update.is_none()
653        {
654            let is_bot =
655                parser.headers.get("auto-submitted") == Some(&"auto-generated".to_string());
656            parser.is_bot = Some(is_bot);
657        }
658        parser.maybe_remove_bad_parts();
659        parser.maybe_remove_inline_mailinglist_footer();
660        parser.heuristically_parse_ndn(context).await;
661        parser.parse_headers(context).await?;
662        parser.decoded_data = mail_raw;
663
664        Ok(parser)
665    }
666
667    fn get_timestamp_sent(
668        hdrs: &[mailparse::MailHeader<'_>],
669        default: i64,
670        timestamp_rcvd: i64,
671    ) -> i64 {
672        hdrs.get_header_value(HeaderDef::Date)
673            .and_then(|v| mailparse::dateparse(&v).ok())
674            .map_or(default, |value| {
675                min(value, timestamp_rcvd + constants::TIMESTAMP_SENT_TOLERANCE)
676            })
677    }
678
679    fn parse_system_message_headers(&mut self, context: &Context) {
681        if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() && !self.incoming {
682            self.parts.retain(|part| {
683                part.mimetype.is_none()
684                    || part.mimetype.as_ref().unwrap().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) {
719        if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) {
720            self.group_avatar = self.avatar_action_from_header(context, header_value.to_string());
721        }
722
723        if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) {
724            self.user_avatar = self.avatar_action_from_header(context, header_value.to_string());
725        }
726    }
727
728    fn parse_videochat_headers(&mut self) {
729        let content = self
730            .get_header(HeaderDef::ChatContent)
731            .unwrap_or_default()
732            .to_string();
733        let room = self
734            .get_header(HeaderDef::ChatWebrtcRoom)
735            .map(|s| s.to_string());
736        let accepted = self
737            .get_header(HeaderDef::ChatWebrtcAccepted)
738            .map(|s| s.to_string());
739        if let Some(part) = self.parts.first_mut() {
740            if let Some(room) = room {
741                if content == "call" {
742                    part.typ = Viewtype::Call;
743                    part.param.set(Param::WebrtcRoom, room);
744                }
745            } else if let Some(accepted) = accepted {
746                part.param.set(Param::WebrtcAccepted, accepted);
747            }
748        }
749    }
750
751    fn squash_attachment_parts(&mut self) {
757        if self.parts.len() == 2
758            && self.parts.first().map(|textpart| textpart.typ) == Some(Viewtype::Text)
759            && self
760                .parts
761                .get(1)
762                .is_some_and(|filepart| match filepart.typ {
763                    Viewtype::Image
764                    | Viewtype::Gif
765                    | Viewtype::Sticker
766                    | Viewtype::Audio
767                    | Viewtype::Voice
768                    | Viewtype::Video
769                    | Viewtype::Vcard
770                    | Viewtype::File
771                    | Viewtype::Webxdc => true,
772                    Viewtype::Unknown | Viewtype::Text | Viewtype::Call => false,
773                })
774        {
775            let mut parts = std::mem::take(&mut self.parts);
776            let Some(mut filepart) = parts.pop() else {
777                return;
779            };
780            let Some(textpart) = parts.pop() else {
781                return;
783            };
784
785            filepart.msg.clone_from(&textpart.msg);
786            if let Some(quote) = textpart.param.get(Param::Quote) {
787                filepart.param.set(Param::Quote, quote);
788            }
789
790            self.parts = vec![filepart];
791        }
792    }
793
794    fn parse_attachments(&mut self) {
796        if self.parts.len() != 1 {
799            return;
800        }
801
802        if let Some(mut part) = self.parts.pop() {
803            if part.typ == Viewtype::Audio && self.get_header(HeaderDef::ChatVoiceMessage).is_some()
804            {
805                part.typ = Viewtype::Voice;
806            }
807            if part.typ == Viewtype::Image || part.typ == Viewtype::Gif {
808                if let Some(value) = self.get_header(HeaderDef::ChatContent) {
809                    if value == "sticker" {
810                        part.typ = Viewtype::Sticker;
811                    }
812                }
813            }
814            if part.typ == Viewtype::Audio
815                || part.typ == Viewtype::Voice
816                || part.typ == Viewtype::Video
817            {
818                if let Some(field_0) = self.get_header(HeaderDef::ChatDuration) {
819                    let duration_ms = field_0.parse().unwrap_or_default();
820                    if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
821                        part.param.set_int(Param::Duration, duration_ms);
822                    }
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            if let Some(ref subject) = self.get_subject() {
840                let mut prepend_subject = true;
841                if !self.decrypting_failed {
842                    let colon = subject.find(':');
843                    if colon == Some(2)
844                        || colon == Some(3)
845                        || self.has_chat_version()
846                        || subject.contains("Chat:")
847                    {
848                        prepend_subject = false
849                    }
850                }
851
852                if self.is_mailinglist_message() && !self.has_chat_version() {
855                    prepend_subject = true;
856                }
857
858                if prepend_subject && !subject.is_empty() {
859                    let part_with_text = self
860                        .parts
861                        .iter_mut()
862                        .find(|part| !part.msg.is_empty() && !part.is_reaction);
863                    if let Some(part) = part_with_text {
864                        part.msg = format!("{} – {}", subject, part.msg);
869                    }
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 && !self.parts.is_empty() {
884            if let Some(ref dn_to) = self.chat_disposition_notification_to {
885                let from = &self.from.addr;
887                if !context.is_self_addr(from).await? {
888                    if from.to_lowercase() == dn_to.addr.to_lowercase() {
889                        if let Some(part) = self.parts.last_mut() {
890                            part.param.set_int(Param::WantsMdn, 1);
891                        }
892                    } else {
893                        warn!(
894                            context,
895                            "{} requested a read receipt to {}, ignoring", from, dn_to.addr
896                        );
897                    }
898                }
899            }
900        }
901
902        if self.parts.is_empty() && self.mdn_reports.is_empty() {
907            let mut part = Part {
908                typ: Viewtype::Text,
909                ..Default::default()
910            };
911
912            if let Some(ref subject) = self.get_subject() {
913                if !self.has_chat_version() && self.webxdc_status_update.is_none() {
914                    part.msg = subject.to_string();
915                }
916            }
917
918            self.do_add_single_part(part);
919        }
920
921        if self.is_bot == Some(true) {
922            for part in &mut self.parts {
923                part.param.set(Param::Bot, "1");
924            }
925        }
926
927        Ok(())
928    }
929
930    fn avatar_action_from_header(
931        &mut self,
932        context: &Context,
933        header_value: String,
934    ) -> Option<AvatarAction> {
935        if header_value == "0" {
936            Some(AvatarAction::Delete)
937        } else if let Some(base64) = header_value
938            .split_ascii_whitespace()
939            .collect::<String>()
940            .strip_prefix("base64:")
941        {
942            match BlobObject::store_from_base64(context, base64) {
943                Ok(path) => Some(AvatarAction::Change(path)),
944                Err(err) => {
945                    warn!(
946                        context,
947                        "Could not decode and save avatar to blob file: {:#}", err,
948                    );
949                    None
950                }
951            }
952        } else {
953            let mut i = 0;
956            while let Some(part) = self.parts.get_mut(i) {
957                if let Some(part_filename) = &part.org_filename {
958                    if part_filename == &header_value {
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 res;
963                        }
964                        break;
965                    }
966                }
967                i += 1;
968            }
969            None
970        }
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    pub fn get_chat_group_id(&self) -> Option<&str> {
1013        self.get_header(HeaderDef::ChatGroupId)
1014            .filter(|s| validate_id(s))
1015    }
1016
1017    async fn parse_mime_recursive<'a>(
1018        &'a mut self,
1019        context: &'a Context,
1020        mail: &'a mailparse::ParsedMail<'a>,
1021        is_related: bool,
1022    ) -> Result<bool> {
1023        enum MimeS {
1024            Multiple,
1025            Single,
1026            Message,
1027        }
1028
1029        let mimetype = mail.ctype.mimetype.to_lowercase();
1030
1031        let m = if mimetype.starts_with("multipart") {
1032            if mail.ctype.params.contains_key("boundary") {
1033                MimeS::Multiple
1034            } else {
1035                MimeS::Single
1036            }
1037        } else if mimetype.starts_with("message") {
1038            if mimetype == "message/rfc822" && !is_attachment_disposition(mail) {
1039                MimeS::Message
1040            } else {
1041                MimeS::Single
1042            }
1043        } else {
1044            MimeS::Single
1045        };
1046
1047        let is_related = is_related || mimetype == "multipart/related";
1048        match m {
1049            MimeS::Multiple => Box::pin(self.handle_multiple(context, mail, is_related)).await,
1050            MimeS::Message => {
1051                let raw = mail.get_body_raw()?;
1052                if raw.is_empty() {
1053                    return Ok(false);
1054                }
1055                let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
1056
1057                Box::pin(self.parse_mime_recursive(context, &mail, is_related)).await
1058            }
1059            MimeS::Single => {
1060                self.add_single_part_if_known(context, mail, is_related)
1061                    .await
1062            }
1063        }
1064    }
1065
1066    async fn handle_multiple(
1067        &mut self,
1068        context: &Context,
1069        mail: &mailparse::ParsedMail<'_>,
1070        is_related: bool,
1071    ) -> Result<bool> {
1072        let mut any_part_added = false;
1073        let mimetype = get_mime_type(
1074            mail,
1075            &get_attachment_filename(context, mail)?,
1076            self.has_chat_version(),
1077        )?
1078        .0;
1079        match (mimetype.type_(), mimetype.subtype().as_str()) {
1080            (mime::MULTIPART, "alternative") => {
1081                for cur_data in mail.subparts.iter().rev() {
1093                    let (mime_type, _viewtype) = get_mime_type(
1094                        cur_data,
1095                        &get_attachment_filename(context, cur_data)?,
1096                        self.has_chat_version(),
1097                    )?;
1098
1099                    if mime_type == mime::TEXT_PLAIN || mime_type.type_() == mime::MULTIPART {
1100                        any_part_added = self
1101                            .parse_mime_recursive(context, cur_data, is_related)
1102                            .await?;
1103                        break;
1104                    }
1105                }
1106
1107                for cur_data in mail.subparts.iter().rev() {
1116                    let mimetype = cur_data.ctype.mimetype.parse::<Mime>()?;
1117                    if mimetype.type_() == mime::TEXT && mimetype.subtype() == "calendar" {
1118                        let filename = get_attachment_filename(context, cur_data)?
1119                            .unwrap_or_else(|| "calendar.ics".to_string());
1120                        self.do_add_single_file_part(
1121                            context,
1122                            Viewtype::File,
1123                            mimetype,
1124                            &mail.ctype.mimetype.to_lowercase(),
1125                            &mail.get_body_raw()?,
1126                            &filename,
1127                            is_related,
1128                        )
1129                        .await?;
1130                    }
1131                }
1132
1133                if !any_part_added {
1134                    for cur_part in mail.subparts.iter().rev() {
1135                        if self
1136                            .parse_mime_recursive(context, cur_part, is_related)
1137                            .await?
1138                        {
1139                            any_part_added = true;
1140                            break;
1141                        }
1142                    }
1143                }
1144                if any_part_added && mail.subparts.len() > 1 {
1145                    self.is_mime_modified = true;
1149                }
1150            }
1151            (mime::MULTIPART, "signed") => {
1152                if let Some(first) = mail.subparts.first() {
1161                    any_part_added = self
1162                        .parse_mime_recursive(context, first, is_related)
1163                        .await?;
1164                }
1165            }
1166            (mime::MULTIPART, "report") => {
1167                if mail.subparts.len() >= 2 {
1169                    match mail.ctype.params.get("report-type").map(|s| s as &str) {
1170                        Some("disposition-notification") => {
1171                            if let Some(report) = self.process_report(context, mail)? {
1172                                self.mdn_reports.push(report);
1173                            }
1174
1175                            let part = Part {
1180                                typ: Viewtype::Unknown,
1181                                ..Default::default()
1182                            };
1183                            self.parts.push(part);
1184
1185                            any_part_added = true;
1186                        }
1187                        Some("delivery-status") | None => {
1189                            if let Some(report) = self.process_delivery_status(context, mail)? {
1190                                self.delivery_report = Some(report);
1191                            }
1192
1193                            for cur_data in &mail.subparts {
1195                                if self
1196                                    .parse_mime_recursive(context, cur_data, is_related)
1197                                    .await?
1198                                {
1199                                    any_part_added = true;
1200                                }
1201                            }
1202                        }
1203                        Some("multi-device-sync") => {
1204                            if let Some(second) = mail.subparts.get(1) {
1205                                self.add_single_part_if_known(context, second, is_related)
1206                                    .await?;
1207                            }
1208                        }
1209                        Some("status-update") => {
1210                            if let Some(second) = mail.subparts.get(1) {
1211                                self.add_single_part_if_known(context, second, is_related)
1212                                    .await?;
1213                            }
1214                        }
1215                        Some(_) => {
1216                            for cur_data in &mail.subparts {
1217                                if self
1218                                    .parse_mime_recursive(context, cur_data, is_related)
1219                                    .await?
1220                                {
1221                                    any_part_added = true;
1222                                }
1223                            }
1224                        }
1225                    }
1226                }
1227            }
1228            _ => {
1229                for cur_data in &mail.subparts {
1232                    if self
1233                        .parse_mime_recursive(context, cur_data, is_related)
1234                        .await?
1235                    {
1236                        any_part_added = true;
1237                    }
1238                }
1239            }
1240        }
1241
1242        Ok(any_part_added)
1243    }
1244
1245    async fn add_single_part_if_known(
1247        &mut self,
1248        context: &Context,
1249        mail: &mailparse::ParsedMail<'_>,
1250        is_related: bool,
1251    ) -> Result<bool> {
1252        let filename = get_attachment_filename(context, mail)?;
1254        let (mime_type, msg_type) = get_mime_type(mail, &filename, self.has_chat_version())?;
1255        let raw_mime = mail.ctype.mimetype.to_lowercase();
1256
1257        let old_part_count = self.parts.len();
1258
1259        match filename {
1260            Some(filename) => {
1261                self.do_add_single_file_part(
1262                    context,
1263                    msg_type,
1264                    mime_type,
1265                    &raw_mime,
1266                    &mail.get_body_raw()?,
1267                    &filename,
1268                    is_related,
1269                )
1270                .await?;
1271            }
1272            None => {
1273                match mime_type.type_() {
1274                    mime::IMAGE | mime::AUDIO | mime::VIDEO | mime::APPLICATION => {
1275                        warn!(context, "Missing attachment");
1276                        return Ok(false);
1277                    }
1278                    mime::TEXT
1279                        if mail.get_content_disposition().disposition
1280                            == DispositionType::Extension("reaction".to_string()) =>
1281                    {
1282                        let decoded_data = match mail.get_body() {
1284                            Ok(decoded_data) => decoded_data,
1285                            Err(err) => {
1286                                warn!(context, "Invalid body parsed {:#}", err);
1287                                return Ok(false);
1289                            }
1290                        };
1291
1292                        let part = Part {
1293                            typ: Viewtype::Text,
1294                            mimetype: Some(mime_type),
1295                            msg: decoded_data,
1296                            is_reaction: true,
1297                            ..Default::default()
1298                        };
1299                        self.do_add_single_part(part);
1300                        return Ok(true);
1301                    }
1302                    mime::TEXT | mime::HTML => {
1303                        let decoded_data = match mail.get_body() {
1304                            Ok(decoded_data) => decoded_data,
1305                            Err(err) => {
1306                                warn!(context, "Invalid body parsed {:#}", err);
1307                                return Ok(false);
1309                            }
1310                        };
1311
1312                        let is_plaintext = mime_type == mime::TEXT_PLAIN;
1313                        let mut dehtml_failed = false;
1314
1315                        let SimplifiedText {
1316                            text: simplified_txt,
1317                            is_forwarded,
1318                            is_cut,
1319                            top_quote,
1320                            footer,
1321                        } = if decoded_data.is_empty() {
1322                            Default::default()
1323                        } else {
1324                            let is_html = mime_type == mime::TEXT_HTML;
1325                            if is_html {
1326                                self.is_mime_modified = true;
1327                                if let Some(text) = dehtml(&decoded_data) {
1328                                    text
1329                                } else {
1330                                    dehtml_failed = true;
1331                                    SimplifiedText {
1332                                        text: decoded_data.clone(),
1333                                        ..Default::default()
1334                                    }
1335                                }
1336                            } else {
1337                                simplify(decoded_data.clone(), self.has_chat_version())
1338                            }
1339                        };
1340
1341                        self.is_mime_modified = self.is_mime_modified
1342                            || ((is_forwarded || is_cut || top_quote.is_some())
1343                                && !self.has_chat_version());
1344
1345                        let is_format_flowed = if let Some(format) = mail.ctype.params.get("format")
1346                        {
1347                            format.as_str().eq_ignore_ascii_case("flowed")
1348                        } else {
1349                            false
1350                        };
1351
1352                        let (simplified_txt, simplified_quote) = if mime_type.type_() == mime::TEXT
1353                            && mime_type.subtype() == mime::PLAIN
1354                            && is_format_flowed
1355                        {
1356                            let delsp = if let Some(delsp) = mail.ctype.params.get("delsp") {
1357                                delsp.as_str().eq_ignore_ascii_case("yes")
1358                            } else {
1359                                false
1360                            };
1361                            let unflowed_text = unformat_flowed(&simplified_txt, delsp);
1362                            let unflowed_quote = top_quote.map(|q| unformat_flowed(&q, delsp));
1363                            (unflowed_text, unflowed_quote)
1364                        } else {
1365                            (simplified_txt, top_quote)
1366                        };
1367
1368                        let (simplified_txt, was_truncated) =
1369                            truncate_msg_text(context, simplified_txt).await?;
1370                        if was_truncated {
1371                            self.is_mime_modified = was_truncated;
1372                        }
1373
1374                        if !simplified_txt.is_empty() || simplified_quote.is_some() {
1375                            let mut part = Part {
1376                                dehtml_failed,
1377                                typ: Viewtype::Text,
1378                                mimetype: Some(mime_type),
1379                                msg: simplified_txt,
1380                                ..Default::default()
1381                            };
1382                            if let Some(quote) = simplified_quote {
1383                                part.param.set(Param::Quote, quote);
1384                            }
1385                            part.msg_raw = Some(decoded_data);
1386                            self.do_add_single_part(part);
1387                        }
1388
1389                        if is_forwarded {
1390                            self.is_forwarded = true;
1391                        }
1392
1393                        if self.footer.is_none() && is_plaintext {
1394                            self.footer = Some(footer.unwrap_or_default());
1395                        }
1396                    }
1397                    _ => {}
1398                }
1399            }
1400        }
1401
1402        Ok(self.parts.len() > old_part_count)
1404    }
1405
1406    #[expect(clippy::too_many_arguments)]
1407    async fn do_add_single_file_part(
1408        &mut self,
1409        context: &Context,
1410        msg_type: Viewtype,
1411        mime_type: Mime,
1412        raw_mime: &str,
1413        decoded_data: &[u8],
1414        filename: &str,
1415        is_related: bool,
1416    ) -> Result<()> {
1417        if mime_type.type_() == mime::APPLICATION
1419            && mime_type.subtype().as_str() == "pgp-keys"
1420            && Self::try_set_peer_key_from_file_part(context, decoded_data).await?
1421        {
1422            return Ok(());
1423        }
1424        let mut part = Part::default();
1425        let msg_type = if context
1426            .is_webxdc_file(filename, decoded_data)
1427            .await
1428            .unwrap_or(false)
1429        {
1430            Viewtype::Webxdc
1431        } else if filename.ends_with(".kml") {
1432            if filename.starts_with("location") || filename.starts_with("message") {
1435                let parsed = location::Kml::parse(decoded_data)
1436                    .map_err(|err| {
1437                        warn!(context, "failed to parse kml part: {:#}", err);
1438                    })
1439                    .ok();
1440                if filename.starts_with("location") {
1441                    self.location_kml = parsed;
1442                } else {
1443                    self.message_kml = parsed;
1444                }
1445                return Ok(());
1446            }
1447            msg_type
1448        } else if filename == "multi-device-sync.json" {
1449            if !context.get_config_bool(Config::SyncMsgs).await? {
1450                return Ok(());
1451            }
1452            let serialized = String::from_utf8_lossy(decoded_data)
1453                .parse()
1454                .unwrap_or_default();
1455            self.sync_items = context
1456                .parse_sync_items(serialized)
1457                .map_err(|err| {
1458                    warn!(context, "failed to parse sync data: {:#}", err);
1459                })
1460                .ok();
1461            return Ok(());
1462        } else if filename == "status-update.json" {
1463            let serialized = String::from_utf8_lossy(decoded_data)
1464                .parse()
1465                .unwrap_or_default();
1466            self.webxdc_status_update = Some(serialized);
1467            return Ok(());
1468        } else if msg_type == Viewtype::Vcard {
1469            if let Some(summary) = get_vcard_summary(decoded_data) {
1470                part.param.set(Param::Summary1, summary);
1471                msg_type
1472            } else {
1473                Viewtype::File
1474            }
1475        } else if msg_type == Viewtype::Image
1476            || msg_type == Viewtype::Gif
1477            || msg_type == Viewtype::Sticker
1478        {
1479            match get_filemeta(decoded_data) {
1480                Ok((width, height)) if width * height <= constants::MAX_RCVD_IMAGE_PIXELS => {
1482                    part.param.set_i64(Param::Width, width.into());
1483                    part.param.set_i64(Param::Height, height.into());
1484                    msg_type
1485                }
1486                _ => Viewtype::File,
1488            }
1489        } else {
1490            msg_type
1491        };
1492
1493        let blob =
1497            match BlobObject::create_and_deduplicate_from_bytes(context, decoded_data, filename) {
1498                Ok(blob) => blob,
1499                Err(err) => {
1500                    error!(
1501                        context,
1502                        "Could not add blob for mime part {}, error {:#}", filename, err
1503                    );
1504                    return Ok(());
1505                }
1506            };
1507        info!(context, "added blobfile: {:?}", blob.as_name());
1508
1509        part.typ = msg_type;
1510        part.org_filename = Some(filename.to_string());
1511        part.mimetype = Some(mime_type);
1512        part.bytes = decoded_data.len();
1513        part.param.set(Param::File, blob.as_name());
1514        part.param.set(Param::Filename, filename);
1515        part.param.set(Param::MimeType, raw_mime);
1516        part.is_related = is_related;
1517
1518        self.do_add_single_part(part);
1519        Ok(())
1520    }
1521
1522    async fn try_set_peer_key_from_file_part(
1524        context: &Context,
1525        decoded_data: &[u8],
1526    ) -> Result<bool> {
1527        let key = match str::from_utf8(decoded_data) {
1528            Err(err) => {
1529                warn!(context, "PGP key attachment is not a UTF-8 file: {}", err);
1530                return Ok(false);
1531            }
1532            Ok(key) => key,
1533        };
1534        let key = match SignedPublicKey::from_asc(key) {
1535            Err(err) => {
1536                warn!(
1537                    context,
1538                    "PGP key attachment is not an ASCII-armored file: {err:#}."
1539                );
1540                return Ok(false);
1541            }
1542            Ok(key) => key,
1543        };
1544        if let Err(err) = key.verify() {
1545            warn!(context, "Attached PGP key verification failed: {err:#}.");
1546            return Ok(false);
1547        }
1548
1549        let fingerprint = key.dc_fingerprint().hex();
1550        context
1551            .sql
1552            .execute(
1553                "INSERT INTO public_keys (fingerprint, public_key)
1554                 VALUES (?, ?)
1555                 ON CONFLICT (fingerprint)
1556                 DO NOTHING",
1557                (&fingerprint, key.to_bytes()),
1558            )
1559            .await?;
1560
1561        info!(context, "Imported PGP key {fingerprint} from attachment.");
1562        Ok(true)
1563    }
1564
1565    pub(crate) fn do_add_single_part(&mut self, mut part: Part) {
1566        if self.was_encrypted() {
1567            part.param.set_int(Param::GuaranteeE2ee, 1);
1568        }
1569        self.parts.push(part);
1570    }
1571
1572    pub(crate) fn get_mailinglist_header(&self) -> Option<&str> {
1573        if let Some(list_id) = self.get_header(HeaderDef::ListId) {
1574            return Some(list_id);
1577        } else if let Some(chat_list_id) = self.get_header(HeaderDef::ChatListId) {
1578            return Some(chat_list_id);
1579        } else if let Some(sender) = self.get_header(HeaderDef::Sender) {
1580            if let Some(precedence) = self.get_header(HeaderDef::Precedence) {
1583                if precedence == "list" || precedence == "bulk" {
1584                    return Some(sender);
1588                }
1589            }
1590        }
1591        None
1592    }
1593
1594    pub(crate) fn is_mailinglist_message(&self) -> bool {
1595        self.get_mailinglist_header().is_some()
1596    }
1597
1598    pub(crate) fn is_schleuder_message(&self) -> bool {
1600        if let Some(list_help) = self.get_header(HeaderDef::ListHelp) {
1601            list_help == "<https://schleuder.org/>"
1602        } else {
1603            false
1604        }
1605    }
1606
1607    pub(crate) fn is_call(&self) -> bool {
1609        self.parts
1610            .first()
1611            .is_some_and(|part| part.typ == Viewtype::Call)
1612    }
1613
1614    pub(crate) fn get_rfc724_mid(&self) -> Option<String> {
1615        self.get_header(HeaderDef::MessageId)
1616            .and_then(|msgid| parse_message_id(msgid).ok())
1617    }
1618
1619    fn remove_secured_headers(
1620        headers: &mut HashMap<String, String>,
1621        removed: &mut HashSet<String>,
1622    ) {
1623        remove_header(headers, "secure-join-fingerprint", removed);
1624        remove_header(headers, "secure-join-auth", removed);
1625        remove_header(headers, "chat-verified", removed);
1626        remove_header(headers, "autocrypt-gossip", removed);
1627
1628        if let Some(secure_join) = remove_header(headers, "secure-join", removed) {
1630            if secure_join == "vc-request" || secure_join == "vg-request" {
1631                headers.insert("secure-join".to_string(), secure_join);
1632            }
1633        }
1634    }
1635
1636    #[allow(clippy::too_many_arguments)]
1637    fn merge_headers(
1638        context: &Context,
1639        headers: &mut HashMap<String, String>,
1640        headers_removed: &mut HashSet<String>,
1641        recipients: &mut Vec<SingleInfo>,
1642        past_members: &mut Vec<SingleInfo>,
1643        from: &mut Option<SingleInfo>,
1644        list_post: &mut Option<String>,
1645        chat_disposition_notification_to: &mut Option<SingleInfo>,
1646        fields: &[mailparse::MailHeader<'_>],
1647    ) {
1648        headers.retain(|k, _| {
1649            !is_protected(k) || {
1650                headers_removed.insert(k.to_string());
1651                false
1652            }
1653        });
1654        for field in fields {
1655            let key = field.get_key().to_lowercase();
1657            if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
1658                match addrparse_header(field) {
1659                    Ok(addrlist) => {
1660                        *chat_disposition_notification_to = addrlist.extract_single_info();
1661                    }
1662                    Err(e) => warn!(context, "Could not read {} address: {}", key, e),
1663                }
1664            } else {
1665                let value = field.get_value();
1666                headers.insert(key.to_string(), value);
1667            }
1668        }
1669        let recipients_new = get_recipients(fields);
1670        if !recipients_new.is_empty() {
1671            *recipients = recipients_new;
1672        }
1673        let past_members_addresses =
1674            get_all_addresses_from_header(fields, "chat-group-past-members");
1675        if !past_members_addresses.is_empty() {
1676            *past_members = past_members_addresses;
1677        }
1678        let from_new = get_from(fields);
1679        if from_new.is_some() {
1680            *from = from_new;
1681        }
1682        let list_post_new = get_list_post(fields);
1683        if list_post_new.is_some() {
1684            *list_post = list_post_new;
1685        }
1686    }
1687
1688    fn process_report(
1689        &self,
1690        context: &Context,
1691        report: &mailparse::ParsedMail<'_>,
1692    ) -> Result<Option<Report>> {
1693        let report_body = if let Some(subpart) = report.subparts.get(1) {
1695            subpart.get_body_raw()?
1696        } else {
1697            bail!("Report does not have second MIME part");
1698        };
1699        let (report_fields, _) = mailparse::parse_headers(&report_body)?;
1700
1701        if report_fields
1703            .get_header_value(HeaderDef::Disposition)
1704            .is_none()
1705        {
1706            warn!(
1707                context,
1708                "Ignoring unknown disposition-notification, Message-Id: {:?}.",
1709                report_fields.get_header_value(HeaderDef::MessageId)
1710            );
1711            return Ok(None);
1712        };
1713
1714        let original_message_id = report_fields
1715            .get_header_value(HeaderDef::OriginalMessageId)
1716            .or_else(|| report.headers.get_header_value(HeaderDef::InReplyTo))
1719            .and_then(|v| parse_message_id(&v).ok());
1720        let additional_message_ids = report_fields
1721            .get_header_value(HeaderDef::AdditionalMessageIds)
1722            .map_or_else(Vec::new, |v| {
1723                v.split(' ')
1724                    .filter_map(|s| parse_message_id(s).ok())
1725                    .collect()
1726            });
1727
1728        Ok(Some(Report {
1729            original_message_id,
1730            additional_message_ids,
1731        }))
1732    }
1733
1734    fn process_delivery_status(
1735        &self,
1736        context: &Context,
1737        report: &mailparse::ParsedMail<'_>,
1738    ) -> Result<Option<DeliveryReport>> {
1739        let mut failure = true;
1741
1742        if let Some(status_part) = report.subparts.get(1) {
1743            if status_part.ctype.mimetype != "message/delivery-status"
1746                && status_part.ctype.mimetype != "message/global-delivery-status"
1747            {
1748                warn!(
1749                    context,
1750                    "Second part of Delivery Status Notification is not message/delivery-status or message/global-delivery-status, ignoring"
1751                );
1752                return Ok(None);
1753            }
1754
1755            let status_body = status_part.get_body_raw()?;
1756
1757            let (_, sz) = mailparse::parse_headers(&status_body)?;
1759
1760            if let Some(status_body) = status_body.get(sz..) {
1762                let (status_fields, _) = mailparse::parse_headers(status_body)?;
1763                if let Some(action) = status_fields.get_first_value("action") {
1764                    if action != "failed" {
1765                        info!(context, "DSN with {:?} action", action);
1766                        failure = false;
1767                    }
1768                } else {
1769                    warn!(context, "DSN without action");
1770                }
1771            } else {
1772                warn!(context, "DSN without per-recipient fields");
1773            }
1774        } else {
1775            return Ok(None);
1777        }
1778
1779        if let Some(original_msg) = report.subparts.get(2).filter(|p| {
1781            p.ctype.mimetype.contains("rfc822")
1782                || p.ctype.mimetype == "message/global"
1783                || p.ctype.mimetype == "message/global-headers"
1784        }) {
1785            let report_body = original_msg.get_body_raw()?;
1786            let (report_fields, _) = mailparse::parse_headers(&report_body)?;
1787
1788            if let Some(original_message_id) = report_fields
1789                .get_header_value(HeaderDef::MessageId)
1790                .and_then(|v| parse_message_id(&v).ok())
1791            {
1792                return Ok(Some(DeliveryReport {
1793                    rfc724_mid: original_message_id,
1794                    failure,
1795                }));
1796            }
1797
1798            warn!(
1799                context,
1800                "ignoring unknown ndn-notification, Message-Id: {:?}",
1801                report_fields.get_header_value(HeaderDef::MessageId)
1802            );
1803        }
1804
1805        Ok(None)
1806    }
1807
1808    fn maybe_remove_bad_parts(&mut self) {
1809        let good_parts = self.parts.iter().filter(|p| !p.dehtml_failed).count();
1810        if good_parts == 0 {
1811            self.parts.truncate(1);
1813        } else if good_parts < self.parts.len() {
1814            self.parts.retain(|p| !p.dehtml_failed);
1815        }
1816
1817        if !self.has_chat_version() && self.is_mime_modified {
1825            fn is_related_image(p: &&Part) -> bool {
1826                (p.typ == Viewtype::Image || p.typ == Viewtype::Gif) && p.is_related
1827            }
1828            let related_image_cnt = self.parts.iter().filter(is_related_image).count();
1829            if related_image_cnt > 1 {
1830                let mut is_first_image = true;
1831                self.parts.retain(|p| {
1832                    let retain = is_first_image || !is_related_image(&p);
1833                    if p.typ == Viewtype::Image || p.typ == Viewtype::Gif {
1834                        is_first_image = false;
1835                    }
1836                    retain
1837                });
1838            }
1839        }
1840    }
1841
1842    fn maybe_remove_inline_mailinglist_footer(&mut self) {
1852        if self.is_mailinglist_message() && !self.is_schleuder_message() {
1853            let text_part_cnt = self
1854                .parts
1855                .iter()
1856                .filter(|p| p.typ == Viewtype::Text)
1857                .count();
1858            if text_part_cnt == 2 {
1859                if let Some(last_part) = self.parts.last() {
1860                    if last_part.typ == Viewtype::Text {
1861                        self.parts.pop();
1862                    }
1863                }
1864            }
1865        }
1866    }
1867
1868    async fn heuristically_parse_ndn(&mut self, context: &Context) {
1872        let maybe_ndn = if let Some(from) = self.get_header(HeaderDef::From_) {
1873            let from = from.to_ascii_lowercase();
1874            from.contains("mailer-daemon") || from.contains("mail-daemon")
1875        } else {
1876            false
1877        };
1878        if maybe_ndn && self.delivery_report.is_none() {
1879            for original_message_id in self
1880                .parts
1881                .iter()
1882                .filter_map(|part| part.msg_raw.as_ref())
1883                .flat_map(|part| part.lines())
1884                .filter_map(|line| line.split_once("Message-ID:"))
1885                .filter_map(|(_, message_id)| parse_message_id(message_id).ok())
1886            {
1887                if let Ok(Some(_)) = message::rfc724_mid_exists(context, &original_message_id).await
1888                {
1889                    self.delivery_report = Some(DeliveryReport {
1890                        rfc724_mid: original_message_id,
1891                        failure: true,
1892                    })
1893                }
1894            }
1895        }
1896    }
1897
1898    pub async fn handle_reports(&self, context: &Context, from_id: ContactId, parts: &[Part]) {
1902        for report in &self.mdn_reports {
1903            for original_message_id in report
1904                .original_message_id
1905                .iter()
1906                .chain(&report.additional_message_ids)
1907            {
1908                if let Err(err) =
1909                    handle_mdn(context, from_id, original_message_id, self.timestamp_sent).await
1910                {
1911                    warn!(context, "Could not handle MDN: {err:#}.");
1912                }
1913            }
1914        }
1915
1916        if let Some(delivery_report) = &self.delivery_report {
1917            if delivery_report.failure {
1918                let error = parts
1919                    .iter()
1920                    .find(|p| p.typ == Viewtype::Text)
1921                    .map(|p| p.msg.clone());
1922                if let Err(err) = handle_ndn(context, delivery_report, error).await {
1923                    warn!(context, "Could not handle NDN: {err:#}.");
1924                }
1925            }
1926        }
1927    }
1928
1929    pub async fn get_parent_timestamp(&self, context: &Context) -> Result<Option<i64>> {
1934        let parent_timestamp = if let Some(field) = self
1935            .get_header(HeaderDef::InReplyTo)
1936            .and_then(|msgid| parse_message_id(msgid).ok())
1937        {
1938            context
1939                .sql
1940                .query_get_value("SELECT timestamp FROM msgs WHERE rfc724_mid=?", (field,))
1941                .await?
1942        } else {
1943            None
1944        };
1945        Ok(parent_timestamp)
1946    }
1947
1948    pub fn chat_group_member_timestamps(&self) -> Option<Vec<i64>> {
1952        let now = time() + constants::TIMESTAMP_SENT_TOLERANCE;
1953        self.get_header(HeaderDef::ChatGroupMemberTimestamps)
1954            .map(|h| {
1955                h.split_ascii_whitespace()
1956                    .filter_map(|ts| ts.parse::<i64>().ok())
1957                    .map(|ts| std::cmp::min(now, ts))
1958                    .collect()
1959            })
1960    }
1961
1962    pub fn chat_group_member_fingerprints(&self) -> Vec<Fingerprint> {
1965        if let Some(header) = self.get_header(HeaderDef::ChatGroupMemberFpr) {
1966            header
1967                .split_ascii_whitespace()
1968                .filter_map(|fpr| Fingerprint::from_str(fpr).ok())
1969                .collect()
1970        } else {
1971            Vec::new()
1972        }
1973    }
1974}
1975
1976fn remove_header(
1977    headers: &mut HashMap<String, String>,
1978    key: &str,
1979    removed: &mut HashSet<String>,
1980) -> Option<String> {
1981    if let Some((k, v)) = headers.remove_entry(key) {
1982        removed.insert(k);
1983        Some(v)
1984    } else {
1985        None
1986    }
1987}
1988
1989async fn parse_gossip_headers(
1995    context: &Context,
1996    from: &str,
1997    recipients: &[SingleInfo],
1998    gossip_headers: Vec<String>,
1999) -> Result<BTreeMap<String, GossipedKey>> {
2000    let mut gossiped_keys: BTreeMap<String, GossipedKey> = Default::default();
2002
2003    for value in &gossip_headers {
2004        let header = match value.parse::<Aheader>() {
2005            Ok(header) => header,
2006            Err(err) => {
2007                warn!(context, "Failed parsing Autocrypt-Gossip header: {}", err);
2008                continue;
2009            }
2010        };
2011
2012        if !recipients
2013            .iter()
2014            .any(|info| addr_cmp(&info.addr, &header.addr))
2015        {
2016            warn!(
2017                context,
2018                "Ignoring gossiped \"{}\" as the address is not in To/Cc list.", &header.addr,
2019            );
2020            continue;
2021        }
2022        if addr_cmp(from, &header.addr) {
2023            warn!(
2025                context,
2026                "Ignoring gossiped \"{}\" as it equals the From address", &header.addr,
2027            );
2028            continue;
2029        }
2030
2031        let fingerprint = header.public_key.dc_fingerprint().hex();
2032        context
2033            .sql
2034            .execute(
2035                "INSERT INTO public_keys (fingerprint, public_key)
2036                             VALUES (?, ?)
2037                             ON CONFLICT (fingerprint)
2038                             DO NOTHING",
2039                (&fingerprint, header.public_key.to_bytes()),
2040            )
2041            .await?;
2042
2043        let gossiped_key = GossipedKey {
2044            public_key: header.public_key,
2045
2046            verified: header.verified,
2047        };
2048        gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key);
2049    }
2050
2051    Ok(gossiped_keys)
2052}
2053
2054#[derive(Debug)]
2056pub(crate) struct Report {
2057    original_message_id: Option<String>,
2062    additional_message_ids: Vec<String>,
2064}
2065
2066#[derive(Debug)]
2068pub(crate) struct DeliveryReport {
2069    pub rfc724_mid: String,
2070    pub failure: bool,
2071}
2072
2073pub(crate) fn parse_message_ids(ids: &str) -> Vec<String> {
2074    let mut msgids = Vec::new();
2076    for id in ids.split_whitespace() {
2077        let mut id = id.to_string();
2078        if let Some(id_without_prefix) = id.strip_prefix('<') {
2079            id = id_without_prefix.to_string();
2080        };
2081        if let Some(id_without_suffix) = id.strip_suffix('>') {
2082            id = id_without_suffix.to_string();
2083        };
2084        if !id.is_empty() {
2085            msgids.push(id);
2086        }
2087    }
2088    msgids
2089}
2090
2091pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
2092    if let Some(id) = parse_message_ids(ids).first() {
2093        Ok(id.to_string())
2094    } else {
2095        bail!("could not parse message_id: {ids}");
2096    }
2097}
2098
2099fn is_protected(key: &str) -> bool {
2105    key.starts_with("chat-")
2106        || matches!(
2107            key,
2108            "return-path"
2109                | "auto-submitted"
2110                | "autocrypt-setup-message"
2111                | "date"
2112                | "from"
2113                | "sender"
2114                | "reply-to"
2115                | "to"
2116                | "cc"
2117                | "bcc"
2118                | "message-id"
2119                | "in-reply-to"
2120                | "references"
2121                | "secure-join"
2122        )
2123}
2124
2125pub(crate) fn is_hidden(key: &str) -> bool {
2127    matches!(
2128        key,
2129        "chat-user-avatar" | "chat-group-avatar" | "chat-delete" | "chat-edit"
2130    )
2131}
2132
2133#[derive(Debug, Default, Clone)]
2135pub struct Part {
2136    pub typ: Viewtype,
2138
2139    pub mimetype: Option<Mime>,
2141
2142    pub msg: String,
2144
2145    pub msg_raw: Option<String>,
2147
2148    pub bytes: usize,
2150
2151    pub param: Params,
2153
2154    pub(crate) org_filename: Option<String>,
2156
2157    pub error: Option<String>,
2159
2160    pub(crate) dehtml_failed: bool,
2162
2163    pub(crate) is_related: bool,
2170
2171    pub(crate) is_reaction: bool,
2173}
2174
2175fn get_mime_type(
2180    mail: &mailparse::ParsedMail<'_>,
2181    filename: &Option<String>,
2182    is_chat_msg: bool,
2183) -> Result<(Mime, Viewtype)> {
2184    let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
2185
2186    let viewtype = match mimetype.type_() {
2187        mime::TEXT => match mimetype.subtype() {
2188            mime::VCARD => Viewtype::Vcard,
2189            mime::PLAIN | mime::HTML if !is_attachment_disposition(mail) => Viewtype::Text,
2190            _ => Viewtype::File,
2191        },
2192        mime::IMAGE => match mimetype.subtype() {
2193            mime::GIF => Viewtype::Gif,
2194            mime::SVG => Viewtype::File,
2195            _ => Viewtype::Image,
2196        },
2197        mime::AUDIO => Viewtype::Audio,
2198        mime::VIDEO => Viewtype::Video,
2199        mime::MULTIPART => Viewtype::Unknown,
2200        mime::MESSAGE => {
2201            if is_attachment_disposition(mail) {
2202                Viewtype::File
2203            } else {
2204                Viewtype::Unknown
2212            }
2213        }
2214        mime::APPLICATION => match mimetype.subtype() {
2215            mime::OCTET_STREAM => match filename {
2216                Some(filename) if !is_chat_msg => {
2217                    match message::guess_msgtype_from_path_suffix(Path::new(&filename)) {
2218                        Some((viewtype, _)) => viewtype,
2219                        None => Viewtype::File,
2220                    }
2221                }
2222                _ => Viewtype::File,
2223            },
2224            _ => Viewtype::File,
2225        },
2226        _ => Viewtype::Unknown,
2227    };
2228
2229    Ok((mimetype, viewtype))
2230}
2231
2232fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
2233    let ct = mail.get_content_disposition();
2234    ct.disposition == DispositionType::Attachment
2235        && ct
2236            .params
2237            .iter()
2238            .any(|(key, _value)| key.starts_with("filename"))
2239}
2240
2241fn get_attachment_filename(
2248    context: &Context,
2249    mail: &mailparse::ParsedMail,
2250) -> Result<Option<String>> {
2251    let ct = mail.get_content_disposition();
2252
2253    let mut desired_filename = ct.params.get("filename").map(|s| s.to_string());
2256
2257    if desired_filename.is_none() {
2258        if let Some(name) = ct.params.get("filename*").map(|s| s.to_string()) {
2259            warn!(context, "apostrophed encoding invalid: {}", name);
2263            desired_filename = Some(name);
2264        }
2265    }
2266
2267    if desired_filename.is_none() {
2269        desired_filename = ct.params.get("name").map(|s| s.to_string());
2270    }
2271
2272    if desired_filename.is_none() {
2275        desired_filename = mail.ctype.params.get("name").map(|s| s.to_string());
2276    }
2277
2278    if desired_filename.is_none() && ct.disposition == DispositionType::Attachment {
2280        if let Some(subtype) = mail.ctype.mimetype.split('/').nth(1) {
2281            desired_filename = Some(format!("file.{subtype}",));
2282        } else {
2283            bail!(
2284                "could not determine attachment filename: {:?}",
2285                ct.disposition
2286            );
2287        };
2288    }
2289
2290    let desired_filename = desired_filename.map(|filename| sanitize_bidi_characters(&filename));
2291
2292    Ok(desired_filename)
2293}
2294
2295pub(crate) fn get_recipients(headers: &[MailHeader]) -> Vec<SingleInfo> {
2297    let to_addresses = get_all_addresses_from_header(headers, "to");
2298    let cc_addresses = get_all_addresses_from_header(headers, "cc");
2299
2300    let mut res = to_addresses;
2301    res.extend(cc_addresses);
2302    res
2303}
2304
2305pub(crate) fn get_from(headers: &[MailHeader]) -> Option<SingleInfo> {
2307    let all = get_all_addresses_from_header(headers, "from");
2308    tools::single_value(all)
2309}
2310
2311pub(crate) fn get_list_post(headers: &[MailHeader]) -> Option<String> {
2313    get_all_addresses_from_header(headers, "list-post")
2314        .into_iter()
2315        .next()
2316        .map(|s| s.addr)
2317}
2318
2319fn get_all_addresses_from_header(headers: &[MailHeader], header: &str) -> Vec<SingleInfo> {
2331    let mut result: Vec<SingleInfo> = Default::default();
2332
2333    if let Some(header) = headers
2334        .iter()
2335        .rev()
2336        .find(|h| h.get_key().to_lowercase() == header)
2337    {
2338        if let Ok(addrs) = mailparse::addrparse_header(header) {
2339            for addr in addrs.iter() {
2340                match addr {
2341                    mailparse::MailAddr::Single(info) => {
2342                        result.push(SingleInfo {
2343                            addr: addr_normalize(&info.addr).to_lowercase(),
2344                            display_name: info.display_name.clone(),
2345                        });
2346                    }
2347                    mailparse::MailAddr::Group(infos) => {
2348                        for info in &infos.addrs {
2349                            result.push(SingleInfo {
2350                                addr: addr_normalize(&info.addr).to_lowercase(),
2351                                display_name: info.display_name.clone(),
2352                            });
2353                        }
2354                    }
2355                }
2356            }
2357        }
2358    }
2359
2360    result
2361}
2362
2363async fn handle_mdn(
2364    context: &Context,
2365    from_id: ContactId,
2366    rfc724_mid: &str,
2367    timestamp_sent: i64,
2368) -> Result<()> {
2369    if from_id == ContactId::SELF {
2370        warn!(
2371            context,
2372            "Ignoring MDN sent to self, this is a bug on the sender device."
2373        );
2374
2375        return Ok(());
2378    }
2379
2380    let Some((msg_id, chat_id, has_mdns, is_dup)) = context
2381        .sql
2382        .query_row_optional(
2383            concat!(
2384                "SELECT",
2385                "    m.id AS msg_id,",
2386                "    c.id AS chat_id,",
2387                "    mdns.contact_id AS mdn_contact",
2388                " FROM msgs m ",
2389                " LEFT JOIN chats c ON m.chat_id=c.id",
2390                " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
2391                " WHERE rfc724_mid=? AND from_id=1",
2392                " ORDER BY msg_id DESC, mdn_contact=? DESC",
2393                " LIMIT 1",
2394            ),
2395            (&rfc724_mid, from_id),
2396            |row| {
2397                let msg_id: MsgId = row.get("msg_id")?;
2398                let chat_id: ChatId = row.get("chat_id")?;
2399                let mdn_contact: Option<ContactId> = row.get("mdn_contact")?;
2400                Ok((
2401                    msg_id,
2402                    chat_id,
2403                    mdn_contact.is_some(),
2404                    mdn_contact == Some(from_id),
2405                ))
2406            },
2407        )
2408        .await?
2409    else {
2410        info!(
2411            context,
2412            "Ignoring MDN, found no message with Message-ID {rfc724_mid:?} sent by us in the database.",
2413        );
2414        return Ok(());
2415    };
2416
2417    if is_dup {
2418        return Ok(());
2419    }
2420    context
2421        .sql
2422        .execute(
2423            "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?)",
2424            (msg_id, from_id, timestamp_sent),
2425        )
2426        .await?;
2427    if !has_mdns {
2428        context.emit_event(EventType::MsgRead { chat_id, msg_id });
2429        chatlist_events::emit_chatlist_item_changed(context, chat_id);
2431    }
2432    Ok(())
2433}
2434
2435async fn handle_ndn(
2438    context: &Context,
2439    failed: &DeliveryReport,
2440    error: Option<String>,
2441) -> Result<()> {
2442    if failed.rfc724_mid.is_empty() {
2443        return Ok(());
2444    }
2445
2446    let msg_ids = context
2449        .sql
2450        .query_map_vec(
2451            "SELECT id FROM msgs
2452                WHERE rfc724_mid=? AND from_id=1",
2453            (&failed.rfc724_mid,),
2454            |row| {
2455                let msg_id: MsgId = row.get(0)?;
2456                Ok(msg_id)
2457            },
2458        )
2459        .await?;
2460
2461    let error = if let Some(error) = error {
2462        error
2463    } else {
2464        "Delivery to at least one recipient failed.".to_string()
2465    };
2466    let err_msg = &error;
2467
2468    for msg_id in msg_ids {
2469        let mut message = Message::load_from_db(context, msg_id).await?;
2470        let aggregated_error = message
2471            .error
2472            .as_ref()
2473            .map(|err| format!("{err}\n\n{err_msg}"));
2474        set_msg_failed(
2475            context,
2476            &mut message,
2477            aggregated_error.as_ref().unwrap_or(err_msg),
2478        )
2479        .await?;
2480    }
2481
2482    Ok(())
2483}
2484
2485#[cfg(test)]
2486mod mimeparser_tests;