Skip to content

parsePart() discards single-part multipart containers — body lost, treated as attachment #614

@gyoztes

Description

@gyoztes

Description

When a multipart/alternative (or any multipart container) has only one sub-part (e.g. only text/html without a text/plain counterpart), the body is lost and misclassified as an attachment. getHTMLBody()
returns "", getTextBody() returns "".

This is common with transactional/automated emails (government portals, notification systems, etc.) that only send HTML without a text/plain alternative.

Root cause

In Structure.php, method parsePart(), line 118:

if (($boundary = $headers->getBoundary()) !== null) {
$parts = $this->detectParts($boundary, $body, $part_number);

  if(count($parts) > 1) {   // <-- BUG: should be > 0
      return $parts;
  }

}

return [new Part($body, $this->header->getConfig(), $headers, $part_number)];

When detectParts() finds exactly 1 sub-part inside the multipart container, count($parts) > 1 evaluates to false. The method falls through and wraps the entire raw multipart body as a single Part with
subtype=alternative.

isAttachment() then returns true for this part because "alternative" is not in the ["plain", "html"] whitelist (Part.php line 295), so the HTML content is never added to $this->bodies['html'] — it becomes a
ghost attachment instead.

Steps to reproduce

  1. Receive an email with this MIME structure:
    Content-Type: multipart/mixed; boundary="outer"
    └── Content-Type: multipart/alternative; boundary="inner"
    └── Content-Type: text/html; charset="utf-8" ← only 1 sub-part, no text/plain
  2. Fetch with php-imap
  3. $message->getHTMLBody() returns "" (empty string)
  4. $message->getAttachments() contains the HTML body as a fake attachment

Suggested fix

Change > 1 to > 0 in Structure.php:118:

if(count($parts) > 0) {
return $parts;
}

If detectParts() returns 0 parts, the existing fallthrough behavior is preserved. If it returns 1 or more, they are correctly returned as parsed sub-parts.

Environment

  • php-imap version: 6.2.0
  • PHP version: 8.4.18
  • IMAP server: Microsoft 365 (Outlook)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions