You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bug symfony#46863 [Mime] Fix invalid DKIM signature with multiple parts (BrokenSourceCode)
This PR was squashed before being merged into the 4.4 branch.
Discussion
----------
[Mime] Fix invalid DKIM signature with multiple parts
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Tickets | Fixsymfony#42407, symfony#40131, symfony#39354
| License | MIT
After many, many hours of investigations, I finally isolated the issue. When sending an email with a DKIM signature, **2** boundaries are generated because **2** instances of [`AbstractMultipartPart`](https://github.com/symfony/symfony/blob/04ae33a1f92bd76e17c7f0e7bbaacb5248cde79e/src/Symfony/Component/Mime/Part/AbstractMultipartPart.php) ([`AlternativePart`](https://github.com/symfony/symfony/blob/04ae33a1f92bd76e17c7f0e7bbaacb5248cde79e/src/Symfony/Component/Mime/Part/Multipart/AlternativePart.php) in my case) are created for **1** email.
Backtrace of the first boundary:
```
Symfony\Component\Mime\Part\AbstractMultipartPart->getBoundary()
Symfony\Component\Mime\Part\AbstractMultipartPart->bodyToIterable()
Symfony\Component\Mime\Crypto\DkimSigner->hashBody()
Symfony\Component\Mime\Crypto\DkimSigner->sign()
```
Backtrace of the second boundary:
```
Symfony\Component\Mime\Part\AbstractMultipartPart->getBoundary()
Symfony\Component\Mime\Part\AbstractMultipartPart->getPreparedHeaders()
Symfony\Component\Mime\Part\AbstractPart->toIterable()
Symfony\Component\Mime\Message->toIterable()
Symfony\Component\Mime\RawMessage->toIterable()
Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream::replace()
Symfony\Component\Mailer\Transport\Smtp\SmtpTransport->doSend()
Symfony\Component\Mailer\Transport\AbstractTransport->send()
Symfony\Component\Mailer\Transport\Smtp\SmtpTransport->send()
Symfony\Component\Mailer\Mailer->send()
```
The fix is to use a single instance of [`AbstractMultipartPart`](https://github.com/symfony/symfony/blob/04ae33a1f92bd76e17c7f0e7bbaacb5248cde79e/src/Symfony/Component/Mime/Part/AbstractMultipartPart.php). I suggest to use a cache system for the method [`Email->getBody()`](https://github.com/symfony/symfony/blob/04ae33a1f92bd76e17c7f0e7bbaacb5248cde79e/src/Symfony/Component/Mime/Email.php#L409-L416) with a property named `$cachedBody`. In this way, only one instance will be created. The property `$cachedBody` will be reset whenever necessary.
Thanks to @calebsolano for putting me on the right way with his comment symfony#39354 (comment).
> My instincts tell me it has something to do with the multipart boundary, in particular with it being random...but I can't determine where in the code it instantiates a second one that would have a different value between creating the body hash and the actual body
I hope this solution will suit you, I couldn't think of an easier one.
Commits
-------
f09f960 [Mime] Fix invalid DKIM signature with multiple parts
Copy file name to clipboardExpand all lines: src/Symfony/Component/Mime/Email.php
+14Lines changed: 14 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -43,6 +43,7 @@ class Email extends Message
43
43
private$html;
44
44
private$htmlCharset;
45
45
private$attachments = [];
46
+
private ?AbstractPart$cachedBody = null; // Used to avoid wrong body hash in DKIM signatures with multiple parts (e.g. HTML + TEXT) due to multiple boundaries.
46
47
47
48
/**
48
49
* @return $this
@@ -282,6 +283,7 @@ public function text($body, string $charset = 'utf-8')
282
283
thrownew \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
283
284
}
284
285
286
+
$this->cachedBody = null;
285
287
$this->text = $body;
286
288
$this->textCharset = $charset;
287
289
@@ -312,6 +314,7 @@ public function html($body, string $charset = 'utf-8')
312
314
thrownew \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
313
315
}
314
316
317
+
$this->cachedBody = null;
315
318
$this->html = $body;
316
319
$this->htmlCharset = $charset;
317
320
@@ -342,6 +345,7 @@ public function attach($body, string $name = null, string $contentType = null)
342
345
thrownew \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
$this->assertSame($body1, $body2, 'The two bodies must reference the same object, so the body cache ensures that the hash for the DKIM signature is unique.');
$email->html('<b>bar</b>'); // We change a part to reset the body cache.
478
+
$body2 = $email->getBody();
479
+
$this->assertNotSame($body1, $body2, 'The two bodies must not reference the same object, so the body cache does not ensure that the hash for the DKIM signature is unique.');
0 commit comments