laminas / laminas-mail

Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages
https://docs.laminas.dev/laminas-mail/
BSD 3-Clause "New" or "Revised" License
93 stars 64 forks source link

`Message->setBody` sets Content-Type but keeps parameters such as `charset` #265

Open vlk-charles opened 3 weeks ago

vlk-charles commented 3 weeks ago

Bug Report

Q A
Version(s) 2.26

Summary

If a Mail\Message had a single-part body previously and setBody() is called again with a multi-part Mime\Message, the Content-Type gets changed to multipart/mixed but the additional parameters such as charset are kept. I don't think charset is allowed on multipart/mixed or at least it doesn't make much sense.

Current behavior

Content-Type: multipart/mixed;
 charset="UTF-8";
 boundary="=_e3ca65ea3f6735b5b583897c5f6a8609"

How to reproduce

$mailMessage1 = new Laminas\Mail\Message();
$mailMessage2 = new Laminas\Mail\Message();
$mimeMessage = new Laminas\Mime\Message();
$mimeMessage->addPart((new Laminas\Mime\Part('part one'))->setType('text/plain')->setCharset('UTF-8'));
$mailMessage1->setBody($mimeMessage);
$mimeMessage->addPart((new Laminas\Mime\Part('part two'))->setFileName('attachment.txt')->setDisposition(Laminas\Mime\Mime::DISPOSITION_ATTACHMENT)->setEncoding(Laminas\Mime\Mime::ENCODING_BASE64));
$mailMessage1->setBody($mimeMessage);
$mailMessage2->setBody($mimeMessage);

$transport = new Laminas\Mail\Transport\File(new Laminas\Mail\Transport\FileOptions(['path' => '/tmp', 'callback' => function() {return 'mail1.log';}]));
$transport->send($mailMessage1);
$transport->getOptions()->setCallback(function() {return 'mail2.log';});
$transport->send($mailMessage2);
$ for i in 1 2; do head -n 6 </tmp/mail$i.log; echo; done
Date: Fri, 23 Aug 2024 23:55:35 +0000
MIME-Version: 1.0
Content-Type: multipart/mixed;
 charset="UTF-8";
 boundary="=_2bba31450910012ee491af25ee700887"
Content-Transfer-Encoding: 8bit

Date: Fri, 23 Aug 2024 23:55:59 +0000
MIME-Version: 1.0
Content-Type: multipart/mixed;
 boundary="=_2bba31450910012ee491af25ee700887"

This is a message in Mime Format.  If you see this, your mail reader does not support this format.

Note that mailMessage2->setBody was only called at the end and has therefore no charset whereas mailMessage1->setBody was also called intermediately and has charset="UTF-8" set.

Expected behavior

Content-Type: multipart/mixed;
 boundary="=_e3ca65ea3f6735b5b583897c5f6a8609"
vlk-charles commented 3 weeks ago

Also I just noticed that Content-Transfer-Encoding: 8bit is also only present in mail1.log. This is allowed for multipart/mixed as it is one of the "identity" encodings. If the value is taken from the first part however, this could easily end up being one of the transformative encodings (quoted-printable or base64). That is not allowed.

vlk-charles commented 3 weeks ago

If transformative encoding is set on the first part like so:

$mailMessage1 = new Laminas\Mail\Message();
$mailMessage2 = new Laminas\Mail\Message();
$mimeMessage = new Laminas\Mime\Message();
$mimeMessage->addPart((new Laminas\Mime\Part('part one'))->setType('text/plain')->setCharset('UTF-8')->setEncoding(Laminas\Mime\Mime::ENCODING_BASE64));
$mailMessage1->setBody($mimeMessage);
$mimeMessage->addPart((new Laminas\Mime\Part('part two'))->setFileName('attachment.txt')->setDisposition(Laminas\Mime\Mime::DISPOSITION_ATTACHMENT));
$mailMessage1->setBody($mimeMessage);
$mailMessage2->setBody($mimeMessage);

$transport = new Laminas\Mail\Transport\File(new Laminas\Mail\Transport\FileOptions(['path' => '/tmp', 'callback' => function() {return 'mail1.log';}]));
$transport->send($mailMessage1);
$transport->getOptions()->setCallback(function() {return 'mail2.log';});
$transport->send($mailMessage2);

mailMessage1 is now completely broken:

for i in 1 2; do head -n 6 </tmp/mail$i.log; echo; done
Date: Sat, 24 Aug 2024 00:15:40 +0000
MIME-Version: 1.0
Content-Type: multipart/mixed;
 charset="UTF-8";
 boundary="=_526f145ae69cd5c075e04bf74285380d"
Content-Transfer-Encoding: base64

Date: Sat, 24 Aug 2024 00:15:40 +0000
MIME-Version: 1.0
Content-Type: multipart/mixed;
 boundary="=_526f145ae69cd5c075e04bf74285380d"

This is a message in Mime Format.  If you see this, your mail reader does not support this format.

Basically calling setBody more than once has unexpected results.

vlk-charles commented 2 weeks ago

I think simply adding

$this->clearHeaderByName('content-type');
$this->clearHeaderByName('content-transfer-encoding');

before this line would pretty much solve it.