diff --git a/src/Structure.php b/src/Structure.php index 11f4cd66..7564adfe 100644 --- a/src/Structure.php +++ b/src/Structure.php @@ -115,7 +115,10 @@ private function parsePart(string $context, int $part_number = 0): array { if (($boundary = $headers->getBoundary()) !== null) { $parts = $this->detectParts($boundary, $body, $part_number); - if(count($parts) > 1) { + // we return these parts if there are multiple or if configured to allow a single part here (default) + // isolated cases a single part here causes an empty body, to workaround this set + // allow_single_parts to false in your config + if ( count($parts) > 1 || $config->get("options")['allow_single_parts'] ) { return $parts; } } diff --git a/src/config/imap.php b/src/config/imap.php index abf8e6e6..45df9fcf 100644 --- a/src/config/imap.php +++ b/src/config/imap.php @@ -134,6 +134,8 @@ | Default TRUE | -Flag download option | Default TRUE + | -Allow single parts option + | Default TRUE - In multipart/xxx structures return internal parts even if there is only one | -Soft fail | Default FALSE - Set to TRUE if you want to ignore certain exception while fetching bulk messages | -RFC822 @@ -171,6 +173,7 @@ 'sequence' => \Webklex\PHPIMAP\IMAP::ST_UID, 'fetch_body' => true, 'fetch_flags' => true, + 'allow_single_parts' => true, 'soft_fail' => false, 'rfc822' => true, 'debug' => false, diff --git a/tests/issues/Issue582Test.php b/tests/issues/Issue582Test.php new file mode 100644 index 00000000..492361fd --- /dev/null +++ b/tests/issues/Issue582Test.php @@ -0,0 +1,79 @@ +getFixture("issue-582a.eml"); + + self::assertSame("redacted_Balances_GBP_", (string)$message->subject); + + $attachments = $message->getAttachments(); + + self::assertSame(1, $attachments->count()); + + /** @var Attachment $attachment */ + $attachment = $attachments->first(); + self::assertSame("redacted_Balances_GBP_.csv", $attachment->name); + self::assertStringEndsWith("stake_limit,soft_limit,h", $attachment->content); + } + + /** + * @throws RuntimeException + * @throws MessageContentFetchingException + * @throws ResponseException + * @throws ImapBadRequestException + * @throws InvalidMessageDateException + * @throws ConnectionFailedException + * @throws \ReflectionException + * @throws ImapServerErrorException + * @throws AuthFailedException + * @throws MaskNotFoundException + */ + public function testContentEmail() { + $message = $this->getFixture("issue-582b.eml"); + + self::assertSame("html body in multipart related container is parsed as attachment", (string)$message->subject); + self::assertStringContainsString("This is a message in a multipart related container", $message->getHtmlBody()); + $attachments = $message->getAttachments(); + self::assertSame(0, $attachments->count()); + } +} \ No newline at end of file diff --git a/tests/messages/issue-582a.eml b/tests/messages/issue-582a.eml new file mode 100644 index 00000000..d8ea8af2 --- /dev/null +++ b/tests/messages/issue-582a.eml @@ -0,0 +1,41 @@ +Delivered-To: redacted@redacted.com +Received: by 2002:a05:7001:a40a:b0:662:9c3d:2d8b with SMTP id + vo10csp4472717mab; Tue, 27 May 2025 00:20:15 -0700 (PDT) +Return-Path: +From: redacted.services@redacted.co.uk +To: redacted@redacted.co.uk +Date: 27 May 2025 08:20:12 +0100 +Subject: redacted_Balances_GBP_ +Content-Type: multipart/mixed; + boundary=--boundary_108_1d32a5ba-d60e-4773-9939-d5614385ac16 +Return-Path: redacted.Services@redacted.co.uk +Message-ID: +MIME-Version: 1.0 + +----boundary_108_1d32a5ba-d60e-4773-9939-d5614385ac16 +Content-Type: multipart/mixed; + boundary=--boundary_109_fc604310-cc2e-459d-9cb7-9f39cf7d3a80 + +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +Single part body + + +----boundary_108_1d32a5ba-d60e-4773-9939-d5614385ac16 +Content-Type: multipart/mixed; + boundary=--boundary_109_fc604310-cc2e-459d-9cb7-9f39cf7d3a80 + +----boundary_109_fc604310-cc2e-459d-9cb7-9f39cf7d3a80 +Content-Type: text/csv; name=redacted_Balances_GBP_.csv +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="=?utf-8?B?Q2redactedbGFuY2VzX0dCUF8uY3N2?=" +Content-ID: <5df3148f-aa43-4dea-adeb-7d432289d3ea> + +77u/cmVwb3J0X2RhdGUsQ3VycmVuY3ksdXNlcm5hbWUsYWNjb3VudF9zdGF0dXMsY3VycmVudF9i +YWxhbmNlLGxhc3RfdHJhbnNhY3Rpb25fZGF0ZXRpbWUsc3Rha2VfbGltaXQsc29mdF9saW1pdCxo + +----boundary_109_fc604310-cc2e-459d-9cb7-9f39cf7d3a80-- + +----boundary_108_1d32a5ba-d60e-4773-9939-d5614385ac16-- diff --git a/tests/messages/issue-582b.eml b/tests/messages/issue-582b.eml new file mode 100644 index 00000000..f18d43c3 --- /dev/null +++ b/tests/messages/issue-582b.eml @@ -0,0 +1,37 @@ +Return-Path: +Date: Thu, 20 Feb 2025 03:44:22 +0100 (CET) +From: ggg@example.de +To: contact@your-company.de, ggg@example.de +Message-ID: <23975424.234.982154931647567.JavaMail.ggg@example.de> +Subject: html body in multipart related container is parsed as attachment +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_255_1307735605.1740019462622" + +------=_Part_255_1307735605.1740019462622 +Content-Type: multipart/related; + boundary="----=_Part_256_1484807935.1740019462623" + +------=_Part_256_1484807935.1740019462623 +Content-Type: text/html;charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + + + + + + + +

+ This is a message in a multipart related container +

+ + +------=_Part_256_1484807935.1740019462623-- + +------=_Part_255_1307735605.1740019462622-- \ No newline at end of file