Skip to content

Commit

Permalink
fix: detect imip messages from outlook.com
Browse files Browse the repository at this point in the history
Adjust the parsing logic in MessageMapper to correctly detect iMIP messages in flat email structures where the text/calendar part is not wrapped in multipart/alternative.

Signed-off-by: Daniel Kesselberg <[email protected]>
  • Loading branch information
kesselb committed Feb 11, 2025
1 parent 2f441a7 commit 4e36943
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 6 deletions.
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ precedence = "aggregate"
SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["tests/data/imip/*"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ".github/CODEOWNERS"
precedence = "aggregate"
Expand Down
10 changes: 4 additions & 6 deletions lib/IMAP/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -888,15 +888,13 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client,
$structure = $fetchData->getStructure();

/** @var Horde_Mime_Part $part */
foreach ($structure->getParts() as $part) {
foreach ($structure->partIterator() as $part) {
if ($part->isAttachment()) {
$hasAttachments = true;
}
$bodyParts = $part->getParts();
/** @var Horde_Mime_Part $bodyPart */
foreach ($bodyParts as $bodyPart) {
$contentParameters = $bodyPart->getAllContentTypeParameters();
if ($bodyPart->getType() === 'text/calendar' && isset($contentParameters['method'])) {

if ($part->getType() === 'text/calendar') {
if ($part->getContentTypeParameter('method') !== null) {
$isImipMessage = true;
}
}
Expand Down
41 changes: 41 additions & 0 deletions tests/Unit/IMAP/MessageMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -593,4 +593,45 @@ public function testGetFlaggedSearchResultUnexpectedStructure() {
$result = $this->mapper->getFlagged($imapClient, $mailbox, $flag);
$this->assertEquals($result, []);
}

/**
* This test ensures that we correctly identify iMIP messages from various
* sources as valid iMIP messages. The test cases are based on original
* iMIP messages from different vendors. The focus is on the MIME message
* structure and verifying that we traverse the MIME tree properly.
*
* @dataProvider isImipMessageProvider
*/
public function testGetBodyStructureIsImipMessage(string $filename, bool $expected): void {
$text = file_get_contents(__DIR__ . '/../../data/imip/' . $filename . '.txt');
$part = \Horde_Mime_Part::parseMessage($text);

$fetchData = new Horde_Imap_Client_Data_Fetch();
$fetchData->setStructure($part);
$fetchData->setUid(100);

$fetchResult = new Horde_Imap_Client_Fetch_Results();
$fetchResult[0] = $fetchData;

$imapClient = $this->createMock(Horde_Imap_Client_Socket::class);
$imapClient->method('fetch')
->willReturn($fetchResult);

$data = $this->mapper->getBodyStructureData(
$imapClient,
'INBOX',
[100],
'[email protected]'
);

$this->assertCount(1, $data);
$this->assertEquals($expected, $data[0]->isImipMessage());
}

public function isImipMessageProvider(): array {
return [
'google request' => ['request_google', true],
'outlook.com request' => ['request_outlook_com', true],
];
}
}
74 changes: 74 additions & 0 deletions tests/data/imip/request_google.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
MIME-Version: 1.0
Message-ID: <[email protected]>
Date: Thu, 06 Feb 2025 16:21:41 +0000
From: [email protected]
To: [email protected], [email protected]
Content-Type: multipart/mixed; boundary="f2d8330e8efc4039bd8073c70ec6cf24"
Subject: Invitation: Imip Testing @ Thu Feb 20, 2025 7pm - 8pm
(CET) ([email protected])

--f2d8330e8efc4039bd8073c70ec6cf24
Content-Type: multipart/alternative; boundary="f55a70234308486098b936b62a6d5e33"

--f55a70234308486098b936b62a6d5e33
Content-Type: text/plain; charset="UTF-8"; format=flowed; delsp=yes
Content-Transfer-Encoding: base64

SW1pcCBUZXN0aW5nICh0ZXh0L3BsYWluKQo=
--f55a70234308486098b936b62a6d5e33
Content-Type: text/calendar; charset="UTF-8"; method=REQUEST
Content-Transfer-Encoding: 7bit

BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VTIMEZONE
TZID:Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:GMT+2
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:GMT+1
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=Europe/Berlin:20250220T190000
DTEND;TZID=Europe/Berlin:20250220T200000
DTSTAMP:20250206T162141Z
ORGANIZER;[email protected]:mailto:[email protected]
UID:[email protected]
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=
TRUE;[email protected];X-NUM-GUESTS=0:mailto:[email protected]
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE
;[email protected];X-NUM-GUESTS=0:mailto:[email protected]
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=
TRUE;[email protected];X-NUM-GUESTS=0:mailto:[email protected]
CREATED:20250206T162140Z
DESCRIPTION:
LAST-MODIFIED:20250206T162140Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Imip Testing
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H30M0S
END:VALARM
END:VEVENT
END:VCALENDAR

--f55a70234308486098b936b62a6d5e33--
--f2d8330e8efc4039bd8073c70ec6cf24--
53 changes: 53 additions & 0 deletions tests/data/imip/request_outlook_com.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
From: [email protected]
To: [email protected], [email protected]
Date: Thu, 6 Feb 2025 10:26:13 +0000
Message-ID: <[email protected]>
Accept-Language: en-US, de-DE
Content-Language: en-US
Content-Type: multipart/alternative;
boundary="a6ccc118fe8d4081837903c834923d7a"
MIME-Version: 1.0
Subject: Imip Testing

--a6ccc118fe8d4081837903c834923d7a
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable



--a6ccc118fe8d4081837903c834923d7a
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable


--a6ccc118fe8d4081837903c834923d7a
Content-Type: text/calendar; charset="utf-8"; method=REQUEST
Content-Transfer-Encoding: base64

QkVHSU46VkNBTEVOREFSCk1FVEhPRDpSRVFVRVNUClBST0RJRDpNaWNyb3NvZnQgRXhjaGFuZ2Ug
U2VydmVyIDIwMTAKVkVSU0lPTjoyLjAKQkVHSU46VlRJTUVaT05FClRaSUQ6Vy4gRXVyb3BlIFN0
YW5kYXJkIFRpbWUKQkVHSU46U1RBTkRBUkQKRFRTVEFSVDoxNjAxMDEwMVQwMzAwMDAKVFpPRkZT
RVRGUk9NOiswMjAwClRaT0ZGU0VUVE86KzAxMDAKUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9
MTtCWURBWT0tMVNVO0JZTU9OVEg9MTAKRU5EOlNUQU5EQVJECkJFR0lOOkRBWUxJR0hUCkRUU1RB
UlQ6MTYwMTAxMDFUMDIwMDAwClRaT0ZGU0VURlJPTTorMDEwMApUWk9GRlNFVFRPOiswMjAwClJS
VUxFOkZSRVE9WUVBUkxZO0lOVEVSVkFMPTE7QllEQVk9LTFTVTtCWU1PTlRIPTMKRU5EOkRBWUxJ
R0hUCkVORDpWVElNRVpPTkUKQkVHSU46VkVWRU5UCk9SR0FOSVpFUjtDTj1hbGljZUBleGFtcGxl
Lm9yZzptYWlsdG86YWxpY2VAZXhhbXBsZS5vcmcKQVRURU5ERUU7Uk9MRT1SRVEtUEFSVElDSVBB
TlQ7UEFSVFNUQVQ9TkVFRFMtQUNUSU9OO1JTVlA9VFJVRTtDTj1ib2JAZXhhbXBsZS5vcmc6bWFp
bHRvOmJvYkBleGFtcGxlLm9yZwpERVNDUklQVElPTjtMQU5HVUFHRT1lbi1VUzpcbgpVSUQ6MUYw
QkQzRjZGRUZDNDIxQUFBNUJFOTkyRDY5OTJCNkEKU1VNTUFSWTtMQU5HVUFHRT1lbi1VUzpJbWlw
IFRlc3RpbmcKRFRTVEFSVDtUWklEPVcuIEV1cm9wZSBTdGFuZGFyZCBUaW1lOjIwMjUwMjI2VDA4
MDAwMApEVEVORDtUWklEPVcuIEV1cm9wZSBTdGFuZGFyZCBUaW1lOjIwMjUwMjI2VDA4MzAwMApD
TEFTUzpQVUJMSUMKUFJJT1JJVFk6NQpEVFNUQU1QOjIwMjUwMjA2VDEwMjYxMloKVFJBTlNQOk9Q
QVFVRQpTVEFUVVM6Q09ORklSTUVEClNFUVVFTkNFOjAKTE9DQVRJT047TEFOR1VBR0U9ZW4tVVM6
ClgtTUlDUk9TT0ZULUNETy1BUFBULVNFUVVFTkNFOjAKWC1NSUNST1NPRlQtQ0RPLUJVU1lTVEFU
VVM6VEVOVEFUSVZFClgtTUlDUk9TT0ZULUNETy1JTlRFTkRFRFNUQVRVUzpCVVNZClgtTUlDUk9T
T0ZULUNETy1BTExEQVlFVkVOVDpGQUxTRQpYLU1JQ1JPU09GVC1DRE8tSU1QT1JUQU5DRToxClgt
TUlDUk9TT0ZULUNETy1JTlNUVFlQRTowClgtTUlDUk9TT0ZULURPTk9URk9SV0FSRE1FRVRJTkc6
RkFMU0UKWC1NSUNST1NPRlQtRElTQUxMT1ctQ09VTlRFUjpGQUxTRQpYLU1JQ1JPU09GVC1SRVFV
RVNURURBVFRFTkRBTkNFTU9ERTpERUZBVUxUClgtTUlDUk9TT0ZULUlTUkVTUE9OU0VSRVFVRVNU
RUQ6VFJVRQpYLU1JQ1JPU09GVC1MT0NBVElPTlM6W10KQkVHSU46VkFMQVJNCkRFU0NSSVBUSU9O
OlJFTUlOREVSClRSSUdHRVI7UkVMQVRFRD1TVEFSVDotUFQxNU0KQUNUSU9OOkRJU1BMQVkKRU5E
OlZBTEFSTQpFTkQ6VkVWRU5UCkVORDpWQ0FMRU5EQVIK

--a6ccc118fe8d4081837903c834923d7a--

0 comments on commit 4e36943

Please sign in to comment.