Skip to content

Resolves #582 - Default to returning all parts, add allow_single_parts op… #583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Structure.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'] ) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( count($parts) > 1 || $config->get("options")['allow_single_parts'] ) {
if ( count($parts) > 1 || $config->get("options.allow_single_parts") ) {

Copy link

@radek-hill radek-hill Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to check, if there are some parts, otherwise it will return empty parts
if ( count($parts) > 1 || $config->get("options.allow_single_parts") && count($parts) > 0 ) {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not always allow single parts? I think just 'count() == 0' needs to be disregarded.

return $parts;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/config/imap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
79 changes: 79 additions & 0 deletions tests/issues/Issue582Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/*
* File: Issue582Test.php
* Category: -
* Author: Mark Hewitt
* Created: 29.05.25
* Updated: -
*
* Description:
* Test PR and fix for issue 582 and potentially 567/580 and 578
*/

namespace Tests\issues;

use PHPUnit\Framework\TestCase;
use Tests\fixtures\FixtureTestCase;
use Webklex\PHPIMAP\Attachment;
use Webklex\PHPIMAP\ClientManager;
use Webklex\PHPIMAP\Exceptions\AuthFailedException;
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
use Webklex\PHPIMAP\Exceptions\ImapServerErrorException;
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
use Webklex\PHPIMAP\Exceptions\ResponseException;
use Webklex\PHPIMAP\Exceptions\RuntimeException;
use Webklex\PHPIMAP\Message;

class Issue582Test extends FixtureTestCase {

/**
* @throws RuntimeException
* @throws MessageContentFetchingException
* @throws ResponseException
* @throws ImapBadRequestException
* @throws InvalidMessageDateException
* @throws ConnectionFailedException
* @throws \ReflectionException
* @throws ImapServerErrorException
* @throws AuthFailedException
* @throws MaskNotFoundException
*/
public function testAttachmentEmail() {
$message = $this->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());
}
}
41 changes: 41 additions & 0 deletions tests/messages/issue-582a.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Delivered-To: [email protected]
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: <[email protected]>
From: [email protected]
To: [email protected]
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: [email protected]
Message-ID: <CWLP123MB51345AE20743EB4587D3CA32AC64A@CWLP123MB5134.GBRP123.PROD.OUTLOOK.COM>
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--
37 changes: 37 additions & 0 deletions tests/messages/issue-582b.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Return-Path: <[email protected]>
Date: Thu, 20 Feb 2025 03:44:22 +0100 (CET)
From: [email protected]
To: [email protected], [email protected]
Message-ID: <[email protected]>
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

<!DOCTYPE html>
<html>
<head>
<style>
p {
font-family: "Helvetica Neue", "Segoe UI", Roboto, Arial, sans-serif;
font-size: 13px;
}
</style>
</head>
<body>
<p>
This is a message in a multipart related container
</p>
</body>
</html>
------=_Part_256_1484807935.1740019462623--

------=_Part_255_1307735605.1740019462622--