From dae80cbe352143b4f5d93cc0ee33dba3a8bc88be Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 11 Dec 2021 21:19:10 +0000 Subject: [PATCH] Recognize MS Exchange read receipts as read receipts They contain X-MSExch-Correlation-Key header, but no Original-Message-ID, so they cannot be used to find the original message, but we want to recognize them as MDN nevertheless to assign them to the trash chat. --- src/mimeparser.rs | 55 ++++++--- ...change_report_disposition_notification.eml | 108 ++++++++++++++++++ 2 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 test-data/message/ms_exchange_report_disposition_notification.eml diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 6537dc98b..794be2d6d 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -1159,23 +1159,21 @@ impl MimeMessage { // must be present if let Some(_disposition) = report_fields.get_header_value(HeaderDef::Disposition) { - if let Some(original_message_id) = report_fields + let original_message_id = report_fields .get_header_value(HeaderDef::OriginalMessageId) - .and_then(|v| parse_message_id(&v).ok()) - { - let additional_message_ids = report_fields - .get_header_value(HeaderDef::AdditionalMessageIds) - .map_or_else(Vec::new, |v| { - v.split(' ') - .filter_map(|s| parse_message_id(s).ok()) - .collect() - }); + .and_then(|v| parse_message_id(&v).ok()); + let additional_message_ids = report_fields + .get_header_value(HeaderDef::AdditionalMessageIds) + .map_or_else(Vec::new, |v| { + v.split(' ') + .filter_map(|s| parse_message_id(s).ok()) + .collect() + }); - return Ok(Some(Report { - original_message_id, - additional_message_ids, - })); - } + return Ok(Some(Report { + original_message_id, + additional_message_ids, + })); } warn!( context, @@ -1331,8 +1329,10 @@ impl MimeMessage { parts: &[Part], ) { for report in &self.mdn_reports { - for original_message_id in - std::iter::once(&report.original_message_id).chain(&report.additional_message_ids) + for original_message_id in report + .original_message_id + .iter() + .chain(&report.additional_message_ids) { match message::handle_mdn(context, from_id, original_message_id, sent_timestamp) .await @@ -1437,7 +1437,10 @@ async fn update_gossip_peerstates( #[derive(Debug)] pub(crate) struct Report { /// Original-Message-ID header - original_message_id: String, + /// + /// It MUST be present if the original message has a Message-ID according to RFC 8098, but MS + /// Exchange does not add it nevertheless, in which case it is `None`. + original_message_id: Option, /// Additional-Message-IDs additional_message_ids: Vec, } @@ -2346,7 +2349,7 @@ Additional-Message-IDs: \n\ assert_eq!(message.mdn_reports.len(), 1); assert_eq!( message.mdn_reports[0].original_message_id, - "foo@example.org" + Some("foo@example.org".to_string()) ); assert_eq!( &message.mdn_reports[0].additional_message_ids, @@ -3125,4 +3128,18 @@ Message. Ok(()) } + + /// Test parsing of MDN sent by MS Exchange. + /// + /// It does not have required Original-Message-ID field, so it is useless, but we want to + /// recognize it as MDN nevertheless to avoid displaying it in the chat as normal message. + #[async_std::test] + async fn test_ms_exchange_mdn() -> Result<()> { + let t = TestContext::new_alice().await; + let raw = + include_bytes!("../test-data/message/ms_exchange_report_disposition_notification.eml"); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await?; + assert!(!mimeparser.mdn_reports.is_empty()); + Ok(()) + } } diff --git a/test-data/message/ms_exchange_report_disposition_notification.eml b/test-data/message/ms_exchange_report_disposition_notification.eml new file mode 100644 index 000000000..cb4f4e4e0 --- /dev/null +++ b/test-data/message/ms_exchange_report_disposition_notification.eml @@ -0,0 +1,108 @@ +Return-Path: +Delivered-To: anonymous@posteo.de +Received: from proxy02.posteo.name ([127.0.0.1]) + by dovecot16.posteo.name (Dovecot) with LMTP id Cp2uFxP1sWHbCQEAchYRkQ + for ; Thu, 09 Dec 2021 13:25:38 +0100 +Received: from proxy02.posteo.de ([127.0.0.1]) + by proxy02.posteo.name (Dovecot) with LMTP id MWsaCwrvsWG0wgEAGFAyLg + ; Thu, 09 Dec 2021 13:25:38 +0100 +Received: from mailin06.posteo.de (unknown [10.0.1.6]) + by proxy02.posteo.de (Postfix) with ESMTPS id 4J8tXy0KkMz120l + for ; Thu, 9 Dec 2021 13:25:38 +0100 (CET) +Received: from mx04.posteo.de (mailin06.posteo.de [127.0.0.1]) + by mailin06.posteo.de (Postfix) with ESMTPS id F24DE215B8 + for ; Thu, 9 Dec 2021 13:25:37 +0100 (CET) +X-Virus-Scanned: amavisd-new at posteo.de +X-Spam-Flag: NO +X-Spam-Score: 0.011 +X-Spam-Level: +X-Spam-Status: No, score=0.011 tagged_above=-1000 required=7 + tests=[HTML_MESSAGE=0.001, T_POSTEO_TLSINY=0.01] autolearn=disabled +X-Posteo-Antispam-Signature: v=1; e=base64; a=aes-256-gcm; d=27yedFdXeAzOobR4x685XJ/5e6WQmX8PP5pSnOlGU2a9Ismhk38wb5AS44xh1yeL5PUxla78UEsHwGkPR0IyPRlHWaLMFLd5CJZN3GzFfrj/2CuB+cd1hOLpp9hRmCebc3rchuDr +Authentication-Results: posteo.de; dmarc=none (p=none dis=none) header.from=example.org +X-Posteo-TLS-Received-Status: TLSv1.2 +Received: from mail.example.org (mail.example.org [0.0.0.0]) + by mx04.posteo.de (Postfix) with ESMTPS id 4J8tXx38vRz10yw + for ; Thu, 9 Dec 2021 13:25:37 +0100 (CET) +Received: from [192.168.1.11] (port=22105 helo=mail.example.org) + by mail.example.org with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 + (Exim 4.94.2) + (envelope-from ) + id 1mvIUG-0007VC-2U + for anonymous@posteo.at; Thu, 09 Dec 2021 13:25:24 +0100 +From: Anonymous +To: Anonymous +Subject: Gelesen: Test message +Thread-Topic: Test message +Thread-Index: AQHX7Dt/+5f88Aokk0KrqG0hbF8dN6wqFvxh +Date: Thu, 9 Dec 2021 12:25:24 +0000 +Message-ID: <1711fc3548cd4b2699ccd4fffac17713@anonymous> +In-Reply-To: <75dd051097b02468183707ad0dd62ebd@posteo.de> +Accept-Language: de-AT, de-DE, en-US +Content-Language: de-DE +X-MS-Has-Attach: +X-MS-TNEF-Correlator: +x-ms-exchange-transport-fromentityheader: Hosted +x-originating-ip: [192.168.120.215] +Content-Type: multipart/report; + boundary="_000_1711fc3548cd4b2699ccd4fffac17713anonymous_"; + report-type=disposition-notification +MIME-Version: 1.0 + +--_000_1711fc3548cd4b2699ccd4fffac17713anonymous_ +Content-Type: multipart/alternative; + boundary="_002_1711fc3548cd4b2699ccd4fffac17713anonymous_" + +--_002_1711fc3548cd4b2699ccd4fffac17713anonymous_ +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +Ihre Nachricht + + An: Anonymous + Betreff: Test message + Gesendet: Mittwoch, 8. Dezember 2021 14:57:05 (UTC+01:00) Amsterdam, Ber= +lin, Bern, Rom, Stockholm, Wien + + wurde am Donnerstag, 9. Dezember 2021 13:24:34 (UTC+01:00) Amsterdam, Berl= +in, Bern, Rom, Stockholm, Wien gelesen. + +--_002_1711fc3548cd4b2699ccd4fffac17713anonymous_ +Content-Type: text/html; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + + + + + + + + + +
Ihre Nachricht
+
+   An: Anonymous
+   Betreff: Test message
+   Gesendet: Mittwoch, 8. Dezember 2021 14:57:05 (UTC+01:00) = +Amsterdam, Berlin, Bern, Rom, Stockholm, Wien
+
+ wurde am Donnerstag, 9. Dezember 2021 13:24:34 (UTC+01:00) Amster= +dam, Berlin, Bern, Rom, Stockholm, Wien gelesen.
+
+ + + +--_002_1711fc3548cd4b2699ccd4fffac17713anonymous_-- + +--_000_1711fc3548cd4b2699ccd4fffac17713anonymous_ +Content-Type: message/disposition-notification + +Final-recipient: RFC822; anonymous@example.org +Disposition: automatic-action/MDN-sent-automatically; displayed +X-MSExch-Correlation-Key: coNC5vaCQkiAOjek1v1Uew== +X-Display-Name: Anonymous + + +--_000_1711fc3548cd4b2699ccd4fffac17713anonymous_--