Problem/Motivation

After the upgrade to 9.2.x two of my sites began to WSoD with uncaught exceptions when they send emails:

Symfony\Component\Mime\Exception\RfcComplianceException: Email ""Foo" does not comply with addr-spec of RFC 2822. in Symfony\Component\Mime\Address->__construct() (line 56 of \path\to\drupal\vendor\symfony\mime\Address.php).

These two sites have names that contain commas. I tested the issue on a vanilla install of 9.3.x-dev and gave the site the name "Foo, Bar, and Baz". The above exception was produced by testing the issue with that site. The exception was thrown because Symfony's MIME component attempted to validate the From address for an email up to the first comma, not the entire address.

I backtraced the issue to a commit made two months ago for #84883: Unicode::mimeHeaderEncode() doesn't correctly follow RFC 2047. In comments #202 and #203 of that issue code was added to the patch that explodes multiple From addresses using commas as a delimiter. See the following snippet from core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php lines 87-92:

    $headers = new Headers();
    foreach ($message['headers'] as $name => $value) {
      if (in_array(strtolower($name), self::MAILBOX_LIST_HEADERS, TRUE)) {
        $value = explode(',', $value);
      }
      $headers->addHeader($name, $value);
    }

Unfortunately, it does not account for commas in the name part of addresses and explodes them too. Thus, I assume this issue would occur for any names that include commas, not only site names.

I attempted to write a failing test for this issue - which I will upload - and wasn't successful. I don't know why, but the test passes with the From address being generated correctly. That's despite the fact that this issue is really easy to reproduce with a vanilla D9 install. But this might explain why this issue wasn't caught before now. There is a test for special characters in email From fields, including commas. It could be that the email test is flawed in a way that causes this problem to not be caught.

This issue is major because it is preventing the site from carrying out normal operations that require sending emails. In my case, I discovered it when attempting to send password reset emails.

Steps to reproduce

1. Create a vanilla install of Drupal 9.
2. Set the site name to one that contains commas, e.g. "Foo, Bar, and Baz".
3. Log out (this might not be necessary, but I did it every time I tested).
4. Visit /user/password and attempt to send yourself a password reset email.

Expected behavior:
The front page should load with a message saying that a password reset email has been sent.

Actual behavior:
After submitting the password reset form the site gives you a WSoD with the above uncaught exception.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3226117

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

dcam created an issue. See original summary.

dcam’s picture

StatusFileSize
new1.32 KB

Here's my patch where I attempted to provide a failing test, but the test ended up passing.

dcam’s picture

longwave’s picture

I think the false positive in your test is because PhpMail::mail() isn't tested; kernel and functional tests set the mail plugin to test_mail_collector which subclasses PhpMail and overrides that method. This is by design; we don't want test runs trying to send real emails.

Perhaps we need to split the actual calls to mail() to their own method and only override that in TestMailCollector.

longwave’s picture

Status: Active » Needs review

Opened an MR with the patch from #2 plus changes to PhpMail::mail() and TestMailCollector from #5. MailTest now fails, let's see if anything else breaks.

longwave’s picture

Status: Needs review » Needs work

We should probably fix each of these tests to use valid email addresses.

longwave’s picture

I think I fixed the cases where the tests tried to send emails with no site email address set; this should just leave the one broken case where the comma triggers an error.

longwave’s picture

Status: Needs work » Needs review
  /**
   * A list of headers that can contain multiple email addresses.
   *
   * @see \Symfony\Component\Mime\Header\Headers::HEADER_CLASS_MAP
   */
  private const MAILBOX_LIST_HEADERS = ['from', 'to', 'reply-to', 'cc', 'bcc'];

Can 'from' really contain multiple email addresses? Even if it can, this seems like an edge case we don't really want to support and we should just pass the 'from' header straight through. I special cased 'from' to do this in the MR.

edit: https://serverfault.com/questions/554520/smtp-allows-for-multiple-from-a... suggests this is technically valid but unlikely to be used in practice, especially I think in our case where a single website is responsible for sending the email - I can't see a reason why we would have multiple senders.

longwave’s picture

Issue tags: +Bug Smash Initiative
longwave’s picture

Fixing attribution.

larowlan’s picture

Whilst I agree that a single from address makes sense, there's always https://xkcd.com/1172/

But I'm not sure what other options we have here.

longwave’s picture

Status: Needs review » Needs work

There might be a rare case where someone altered the From header in hook_mail or hook_mail_alter. But by default we set $message['headers']['From'] ourselves in MailManager; we even set it up as a single MailboxHeader there and then render it to string to preserve backward compatibility in the hooks. So as #13 says there may be an edge case that we break here, but I don't see what other option we have, as Symfony seems to provide no way of parsing a header that might contain multiple values - we can only set up headers for sending.

MegaChriz made their first commit to this issue’s fork.

megachriz’s picture

Status: Needs work » Needs review

I've added a test and a possible fix for this issue.

In the branch 3226117-phpmail-from-not-rfc-compliant-tests_only, I did the following:

  • For \Drupal\Core\Mail\Plugin\Mail::mail(), I added a test using message with a From address with a comma in it.
  • I wrapped the call to PHP's mail() function (used in \Drupal\Core\Mail\Plugin\Mail::mail()) in a separate method called doMail(). This way I could override that part of the code in the test and avoid an actual call to PHP's mail() function.

I created a merge request for this branch just so that the testbot runs the tests. Not sure if there's an other way on drupal.org for demonstrating a bug without using patches.

Additionally, in the branch 3226117-phpmail-from-not-rfc-compliant I came up with a possible fix for the issue:

  • Instead of using explode() to split From addresses by comma, use str_getcsv() instead. That function supports ignoring comma's encapsulated in double quotes. Besides that, it seems to have the same effect as explode().

I found this solution by searching on "php split values by comma but not if they are in double quotes" and I had found the following: https://stackoverflow.com/questions/41449352/php-split-comma-separated-v...
I saw no value in retaining the quotes however, so I think just using str_getcsv() was fine.

megachriz’s picture

Oops, when checking @longwave's work, I clicked on " https://git.drupalcode.org/project/drupal/-/merge_requests/992/diffs?commit_id=3bc0c9265b3475636e53b52731046f57e8ffc3dd">3bc0c926" in #9, figuring there were only two changes.

I see I should have looked at https://git.drupalcode.org/project/drupal/-/merge_requests/992/diffs instead.

I'll see if I can merge the two branches.

megachriz’s picture

I added two commits from @longwave to the branch 3226117-phpmail-from-not-rfc-compliant. Not sure if the changes from the commit "Split PhpMail::mail() in two." (I made a similar split) is still needed. I'm assuming that the changes from commit "Disallow multiple email addresses in 'from' header." is redundant now, based on the comment from @larowlan in #13.

xrayfish’s picture

Not sure if this is related but after upgrading to 9.2.x I'm seeing:

The website encountered an unexpected error. Please try again later.
Error: Call to undefined method Symfony\Component\Mime\Header\Headers::addHeader() in Drupal\Core\Mail\Plugin\Mail\PhpMail->mail() (line 92 of core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php).

This happens only when attempting to submit forms whilst logged in.

attraktive’s picture

Same pb for me

shawn dearmond’s picture

Status: Needs review » Reviewed & tested by the community

Verified that @MegaChriz MR 1070 fixes the Uncaught RfcComplianceException issue with sending email from sites that have a comma in the name.

catch’s picture

Status: Reviewed & tested by the community » Needs work

Back to needs work for a comment on the MR.

megachriz’s picture

Status: Needs work » Needs review

I've done the following:

  • Added a comment to explain the usage of str_getcsv();
  • Locally, rebased branch "3226117-phpmail-from-not-rfc-compliant" on 9.3.x;
  • git push --force

Is a forced git push the recommended way of getting an issue branch mergeable again?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

z3cka’s picture

Tested MR !1070 with Drupal 9.3.3: works like a charm. Thanks so much for this work.

itaran’s picture

StatusFileSize
new2.99 KB

Created a patch from https://git.drupalcode.org/project/drupal/-/merge_requests/1070 diffs (without tests) to apply while PR haven't merged yet.

longwave’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC given that @catch's comment was addressed and @z3cka reported that the patch works for them.

chrissnyder’s picture

Thank you @itaran for creating that patch!

Status: Reviewed & tested by the community » Needs work
megachriz’s picture

Status: Needs work » Reviewed & tested by the community

The patch is not the one the testbot should keep testing because it contains the fix without the tests. The test failures also look unrelated to the changes, so setting back to RTBC. Also requeued the tests on the issue fork.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs reroll

Can we get a D10 version of this too?

The D10 version can use union types for the new method type-hint.

Adding the needs-reroll tag for adding a D10 version

ankithashetty made their first commit to this issue’s fork.

ankithashetty’s picture

Issue tags: -Needs reroll

Created an MR for the D10 version, thanks!

megachriz’s picture

Does the following line in \Drupal\Core\Mail\Plugin\Mail\PhpMail::mail() cause the test failure on PHP 8.1?
$return_path_set = strpos(ini_get('sendmail_path'), ' -f');

sinn’s picture

There is an issue after refactoring of core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php.

Steps to reproduce:

  • Create an user without email. Set status "Blocked"
  • Update user. Set status "Active". Save

Error is shown because Drupal tries to send mail, but user doesn't have it:
Got error 'PHP message: TypeError: Drupal\\Core\\Mail\\Plugin\\Mail\\PhpMail::doMail(): Argument #1 ($to) must be of type string, null given, called in /var/www/html/build/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php on line 120 in /var/www/html/build/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php on line 164 #0 /var/www/html/build/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php(120): Drupal\\Core\\Mail\\Plugin\\Mail\\PhpMail->doMail()\n#1 /var/www/html/build/core/lib/Drupal/Core/Mail/MailManager.php(311): Drupal\\Core\\Mail\\Plugin\\Mail\\PhpMail->mail()\n#2 /var/www/html/build/core/lib/Drupal/Core/Mail/MailManager.php(180): Drupal\\Core\\Mail\\MailManager->doMail()\n#3 /var/www/html/build/core/lib/Drupal/Core/Render/Renderer.php(564): Drupal\\Core\\Mail\\MailManager->Drupal\\Core\\Mail\\{closure}()\n#4 /var/www/html/build/core/lib/Drupal/Core/Mail/MailManager.php(181): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#5 /var/www/html/build/core/modules/user/user.module(1067): Drupal\\Core\\Mail\\MailManager->mail()

tasc’s picture

#38 works well for me and webforms, thanks!

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

dinesh18’s picture

#38. doesn't work for me

dydave’s picture

Thank you very much for raising this issue and for your code contributions.

We encountered the same error:

Drupal\Core\Entity\EntityStorageException: Email "[SITENAME]" does not comply with addr-spec of RFC 2822. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 811 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).

when trying to submit a Webform with an email handler using the site name in the from field.
Since the site name contained a comma, it seems to have triggered this exception, see related issue:
#3219222-12: After 9.2.0 update, "Email "" does not comply with addr-spec of RFC 2822"
(Tested with webform:6.1.3)

After applying the patch from #38 with composer on Drupal core 9.4.3 (PHP: 7.4), the error seems to have been fixed and the Webform could be properly submitted with the site name containing a comma.

Additionally, we've given the email a quick test with the reroute_email module, to confirm the from and reply-to fields of the array generated for the email would have the correct values, with the comma.

Without the patch, a temporary solution in this particular case would be to remove the comma from the site name, or for example replaced with a pipe (|).
 

Not sure exactly why the tests failed on PHP 8.1 for the patch, but is there anything else holding off on this change?
We would greatly appreciate additional reviews and feedback on the code changes, in particular if you would see anything missing in the tests, or that would need to be updated.

Thanks in advance.

allella’s picture

I'm not sure if testing the #38 is still viable for Drupal core 9.4.7, but we're also getting this issue with a comma in the sitename.

I can try to test something if that's the most applicable patch and still relevant to 9.4.7.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

carma03’s picture

Confirming that #38 but the one for D9.3.x worked on 9.4.8(drupal-3226117-phpmail-from-not-rfc-compliant-38.patch).

The patch for 9.4.x didn't work(28-38.diff).

marthinal’s picture

Hi guys. Thanks for the work on this issue. I refactored the patch:

#38 I'm not sure why we need to create users without email...

#33 Needs a D10 version

#35 I've created the patch from this MR

We are testing twice. See testCancelMessage() and PhpMailTest.

Let's see what the Testbot says.

marthinal’s picture

Status: Needs review » Needs work

The last submitted patch, 47: RfcComplianceException-3226117-47.patch, failed testing. View results

marthinal’s picture

Status: Needs work » Needs review

The last submitted patch, 49: RfcComplianceException-3226117-49-only-test.patch, failed testing. View results

jwilson3’s picture

Title: Uncaught RfcComplianceException thrown when sending email from a site whose name contains a comma » Uncaught RfcComplianceException when email From name contains a comma

I've generalized the issue title because this issue also affects Webform email handlers if the "From name" is configured for user-entered form data, which may contain a comma, eg "Lastname, Firstname"

nod_’s picture

Status: Needs review » Reviewed & tested by the community

Patch in #49 show the failing test and fix

  • larowlan committed 9d3ea0a on 10.1.x
    Issue #3226117 by MegaChriz, marthinal, longwave, sinn, dcam,...

  • larowlan committed 88fd310 on 10.0.x
    Issue #3226117 by MegaChriz, marthinal, longwave, sinn, dcam,...
larowlan’s picture

Title: Uncaught RfcComplianceException when email From name contains a comma » [Needs backport] Uncaught RfcComplianceException when email From name contains a comma
Version: 10.1.x-dev » 9.5.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed this to 10.1.x and cherry-picked to 10.0.x

With the union type, this won't pass on PHP 7.3 so can we get an adapted version of the patch for the 9.x branches?

Thanks

cilefen’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new13.69 KB
cilefen’s picture

The only change in #57 is to the doMail() signature.

Status: Needs review » Needs work

The last submitted patch, 57: 3226117-57-d9.patch, failed testing. View results

cilefen’s picture

Status: Needs work » Needs review

I don’t understand which test run failed but it looks like a random fail.

rschwab’s picture

Status: Needs review » Reviewed & tested by the community

#57 Works for me on drupal 9.4.8 using both php 7.3 and 8.0.

  • larowlan committed d18b6cc on 9.5.x
    Issue #3226117 by MegaChriz, marthinal, longwave, sinn, cilefen, dcam,...

  • larowlan committed 056f7f3 on 9.4.x
    Issue #3226117 by MegaChriz, marthinal, longwave, sinn, cilefen, dcam,...

  • larowlan committed e85f57e on 9.4.x
    Revert "Issue #3226117 by MegaChriz, marthinal, longwave, sinn, cilefen...

  • larowlan committed b7dc5da on 9.5.x
    Revert "Issue #3226117 by MegaChriz, marthinal, longwave, sinn, cilefen...
larowlan’s picture

Status: Reviewed & tested by the community » Needs work

I committed to 9.5.x and backported to 9.4.x as the risk of disruption is low

But then I thought to check if anything in contrib overrode the constructor without calling the parent, as they'd now be broken by the setting of $this->request.

https://git.drupalcode.org/search?group_id=2&scope=blobs&search=%22exten...

I found two instances

* https://git.drupalcode.org/project/ubercart_funds/-/blob/8.x-1.x/src/Plu... - this only has a D7 release, and currently would be broken as it doesn't set the config factory, safe to ignore
* https://git.drupalcode.org/project/mimemail/-/blob/8.x-1.x/src/Plugin/Ma... - this has 24k installs and would be broken too
* https://git.drupalcode.org/project/sendgrid_mailer/-/blob/1.1.x/src/Plug... - 60 installs, would be broken

Mimemail doesn't have a D10 version and I've pointed to the impending break in #3288663: [meta] Add support for Drupal 10.
Send Grid Mail does, so I commented on their D10 compatibility issue here #3289557: Automated Drupal 10 compatibility fixes.

I think its OK for this API change in the next major, even though constructors are not part of an API.

However, I don't think its ok to break 24k sites in D9.4 (and 9.5). So I then reverted this change.

Can we reroll this without the property assignment in the constructor to allow for those downstream modules that are technically doing it wrong. Even though we could also make this BC compliant and not break their D10 version, I think a major version is the appropriate time to communicate that they need to call the parent constructor.

Thanks

rschwab’s picture

Status: Needs work » Needs review
StatusFileSize
new13.12 KB

Addresses #66 and also fixes the doc block of the doMail function now that its parameter is not a union type.

rschwab’s picture

StatusFileSize
new1.67 KB

The last submitted patch, 67: 3226117-67.patch, failed testing. View results

longwave’s picture

Status: Needs review » Reviewed & tested by the community

The change to support 9.4.x/9.5.x looks good to me.

+++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
@@ -106,10 +108,7 @@ public function mail(array $message) {
+    if (!$request->server->has('WINDIR') && !str_contains($request->server->get('SERVER_SOFTWARE'), 'Win32')) {

I suspect this check should be updated anyway - there are language-level constants that should be more reliable now: https://www.php.net/manual/en/reserved.constants.php#constant.php-os

Probably worth opening a followup to change this, then we don't need $request at all?

longwave’s picture

In fact we use this pattern in a number of places elsewhere in core:

  if (substr(PHP_OS, 0, 3) == 'WIN') {

The last submitted patch, 67: 3226117-67.patch, failed testing. View results

The last submitted patch, 67: 3226117-67.patch, failed testing. View results

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Tests are failing

rschwab’s picture

Status: Needs work » Needs review
StatusFileSize
new10.14 KB
new4.45 KB

This one incorporates the feedback from longwave in #70 and #71. $request is now removed in favor of the PHP_OS constant.

joelpittet’s picture

Status: Needs review » Reviewed & tested by the community

Thank you @rschwab and @longwave for the idea to remove the request

rschwab’s picture

Bumping in the hopes this can make the next release for 9.x.

  • larowlan committed 831f7992 on 9.5.x
    Issue #3226117 by MegaChriz, marthinal, longwave, rschwab, sinn, dcam,...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Backported to 9.5.x - thanks all

larowlan’s picture

Title: [Needs backport] Uncaught RfcComplianceException when email From name contains a comma » Uncaught RfcComplianceException when email From name contains a comma

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

tonypaulbarker’s picture

Hi @larowlan

I think this was released in 9.5.4 but I am observing it in 9.5.5.

The log message is:

Error sending email: Email xxxx does not comply with addr-spec of RFC 2822

Remove comma from the 'From' field and the mail sends.

larowlan’s picture

@tonypaulbarker yeah this should be fixed, can you confirm you're seeing the code changes of the above patch in your codebase?

tonypaulbarker’s picture

@larowlan yes the update seems to have all applied fine and I see the new code in the codebase.

I will see if I can reproduce the issue on a fresh copy, run some tests and do some debugging and see if I can spot anything.