Skip to content

[Mime] allow non-ASCII characters in local part of email #36178

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

Merged
merged 1 commit into from
Jul 10, 2020

Conversation

dmaicher
Copy link
Contributor

Q A
Branch? 4.4
Bug fix? yes
New feature? no
Deprecations? no
Tickets #34932
License MIT
Doc PR -

This fixes #34932 by allowing non-ASCII characters in the local part of emails.

I tried this using 3 different smtp servers (gmail, mailgun and local postfix) and for me this just works in case there are non-ASCII characters in the local part of emails. Emails are correctly delivered.

PHPMailer does this in the same way: https://github.com/PHPMailer/PHPMailer/blob/master/src/PHPMailer.php#L1411

This is also in line with the behavior of Swiftmailer (< 6.1) before this commit that introduced the IdnAddressEncoder: swiftmailer/swiftmailer@6a87efd#diff-e5f85d26733017e183b2633ae3c433f0R31

I'm not an expert when it comes to SMTP and all the different RFCs out there 😕 But for me this exception seems not needed.

Maybe @c960657 can help here?

@dmaicher
Copy link
Contributor Author

dmaicher commented Apr 2, 2020

@fabpot seeing that you wrote this component: Any idea from your side about this issue?

@dmaicher
Copy link
Contributor Author

@symfony/mergers anyone able to help with this one?

@javiereguiluz
Copy link
Member

@dmaicher Python supports this too via the SMTPUTF8 extension.

Go supports it too:

// String formats the address as a valid RFC 5322 address.
// If the address's name contains non-ASCII characters
// the name will be rendered according to RFC 2047.

And that's the RFC that describes this: https://www.ietf.org/rfc/rfc2047.txt

@dmaicher
Copy link
Contributor Author

dmaicher commented May 3, 2020

@javiereguiluz as far as I understand rfc 2047 does not apply for email addresses?

+ An 'encoded-word' MUST NOT appear in any portion of an 'addr-spec'.

It would only apply for the "name" part of some recipient address header. But not the address itself.

@javiereguiluz
Copy link
Member

@dmaicher I read too fast then. Sorry 😞

@dmaicher
Copy link
Contributor Author

@javiereguiluz any idea how to move this forward? This issue keeps me from using the symfony/mailer unfortunately 😢

@nicolas-grekas
Copy link
Member

I think we can do this change, but only if we also forbid non-ASCII chars in the local part of the envelope sender. The reason is that there always must be a path to receive bounces, and the envelope-sender is their destination.

@dmaicher
Copy link
Contributor Author

dmaicher commented Jun 30, 2020

@nicolas-grekas first of all thank you for taking to time to look into this! 😊

I think we can do this change, but only if we also forbid non-ASCII chars in the local part of the envelope sender. The reason is that there always must be a path to receive bounces, and the envelope-sender is their destination.

Not sure I follow this comment. So currently this change should impact the envelope sender as well, right?

Seeing that the envelope sender is also an Address and thus will use the encoder internally: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Mime/Address.php#L72

So you mean we should change this behavior for the envelope sender?

@nicolas-grekas
Copy link
Member

We shouldn't necessarily change the behavior of going through the encoder, but we should add a check, in Mailer, to reject any local part with non-ASCII characters. This will ensure that any bounce message will be able to come back to the sender independently of whether SMTP hops are able to deal with UTF-8 or not. This looks like a critical property to me.

@dmaicher
Copy link
Contributor Author

We shouldn't necessarily change the behavior of going through the encoder, but we should add a check, in Mailer, to reject any local part with non-ASCII characters. This will ensure that any bounce message will be able to come back to the sender independently of whether SMTP hops are able to deal with UTF-8 or not. This looks like a critical property to me.

Ok fair enough. That seems good to me 👍

How should we do this? Inside Mailer or inside Envelope? Should it throw Symfony\Component\Mime\Exception\AddressEncoderException?

@nicolas-grekas
Copy link
Member

Inside Envelope? this would allow reusing AddressEncoderException isn't it?

@dmaicher dmaicher force-pushed the non-ascii-encoder branch 3 times, most recently from 3f1ee9b to 7609bac Compare June 30, 2020 18:23
@dmaicher dmaicher requested a review from nicolas-grekas June 30, 2020 18:46
@dmaicher dmaicher changed the base branch from 4.4 to master July 1, 2020 17:27
@nicolas-grekas nicolas-grekas modified the milestones: 4.4, next Jul 1, 2020
@nicolas-grekas nicolas-grekas added Feature and removed Bug labels Jul 1, 2020
@dmaicher
Copy link
Contributor Author

dmaicher commented Jul 1, 2020

This is a new feature, so it must be rebased on master before merging.

@fabpot would have been great to see this change in 4.4 LTS... but ok. Why do you think this cannot be treated as a bugfix?

Anyway I rebased it 😉

@dmaicher dmaicher requested a review from fabpot July 10, 2020 15:19
@fabpot
Copy link
Member

fabpot commented Jul 10, 2020

Thank you @dmaicher.

@nicolas-grekas
Copy link
Member

It looks like this broke some tests for bridges, could you please have a look @dmaicher?

@dmaicher dmaicher deleted the non-ascii-encoder branch July 12, 2020 09:33
@dmaicher
Copy link
Contributor Author

dmaicher commented Jul 12, 2020

@nicolas-grekas ah sorry I missed those test fails.

@fabpot I could simply fix those test fails but not sure the change in behavior is really correct 😕

For example the test fail in MandrillApiTransportTest:

        $transport = new MandrillApiTransport('KEY', $client);

        $mail = new Email();
        $mail->subject('Hello!')
            ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
            ->from(new Address('fabpot@symfony.com', 'Fabien'))
            ->text('Hello There!');

        $message = $transport->send($mail);

Now with my changes the name "Fabien" is dropped from the sender address and before it was passed on to Mandrill as from_name.

EDIT: or maybe the mandrill transport (and others) should not use the sender as "from"? As per RFC the "Sender" can only be an email address without a name as @fabpot mentioned here: #36178 (comment)

EDIT: So for example

diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php
index 50be4e02cb..027c67cd10 100644
--- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php
+++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php
@@ -72,19 +72,25 @@ class MandrillApiTransport extends AbstractApiTransport
 
     private function getPayload(Email $email, Envelope $envelope): array
     {
+        $fromAddresses = $email->getFrom();
+        $from = reset($fromAddresses);
+
         $payload = [
             'key' => $this->key,
             'message' => [
                 'html' => $email->getHtmlBody(),
                 'text' => $email->getTextBody(),
                 'subject' => $email->getSubject(),
-                'from_email' => $envelope->getSender()->getAddress(),
                 'to' => $this->getRecipients($email, $envelope),
             ],
         ];
 
-        if ('' !== $envelope->getSender()->getName()) {
-            $payload['message']['from_name'] = $envelope->getSender()->getName();
+        if ($from) {
+            $payload['message']['from_email'] = $from->getAddress();
+
+            if ('' !== $from->getName()) {
+                $payload['message']['from_name'] = $from->getName();
+            }
         }
 
         foreach ($email->getAttachments() as $attachment) {

@nicolas-grekas
Copy link
Member

Dunno if it helps decide, but this affects deps=high on 4.4 also.

@xabbuh
Copy link
Member

xabbuh commented Jul 13, 2020

How does Mandrill use the from_email field? Is it used like the Sender or like the From header? If it's the latter, using the sender as the input for this attribute looks indeed wrong to me (same would apply to other APIs).

@dmaicher
Copy link
Contributor Author

@xabbuh not sure how Mandrill is handling that but Mailgun seems to use it for the From header.

See https://documentation.mailgun.com/en/latest/api-sending.html#sending

Parameter Description
from Email address for From header

And there we also use the envelope sender: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php#L94

But that means the "Sender customization feature" using EnvelopeListener would then not work anymore if we change that? 😕

@fabpot
Copy link
Member

fabpot commented Jul 15, 2020

@dmaicher see #37580

fabpot added a commit that referenced this pull request Jul 15, 2020
…sports (fabpot)

This PR was merged into the 4.4 branch.

Discussion
----------

[Mime] Keep Sender full address when used by non-SMTP transports

| Q             | A
| ------------- | ---
| Branch?       | 4.4 <!-- see below -->
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | n/a <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT
| Doc PR        | n/a

refs #36178

The `Envelope` is an SMTP concept. The Sender is used in the `MAIL FROM` SMTP command, where only an address is supported. But we are also supporting non-SMTP transports, where the Sender might also be used as the `From` header, where a full mailbox is supported.

To take into account the 2 usages, this PR keeps the full mailbox in the Envelope and let the SMTP class only use the address (which was already the case anyway).

Commits
-------

1ca9be7 [Mime] Keep Sender full address when used by non-SMTP transports
@MaSpeng
Copy link

MaSpeng commented Aug 12, 2020

I'am sorry but i have to ask, will this "feature" be part of the 5.2 release or will it be an addition to 5.1.4?

Another Question is there currently any way to temporarily replace/bypass the \Symfony\Component\Mime\Encoder\IdnAddressEncoder or worse the \Symfony\Component\Mime\Address to create an workaround for this issue?

@javiereguiluz
Copy link
Member

@MaSpeng this was merged as a feature in master branch (see 63f8827) which corresponds to the upcoming 5.2 version.

According to the Symfony Release Process minor versions (e.g. 5.1.4) only contains bug fixes and never new features. So, this will be included only in 5.2 and higher versions, but not in 5.1.*. The good news is that Symfony 5.2 release is just a few months ago (feature freeze starts in just 6 weeks!) and you can upgrade your app from 5.1 to 5.2 without breaking changes.

@MaSpeng
Copy link

MaSpeng commented Aug 12, 2020

@javiereguiluz Many thanks for the explanations.

@nicolas-grekas nicolas-grekas modified the milestones: next, 5.2 Oct 5, 2020
@fabpot fabpot mentioned this pull request Oct 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Mime] Email address with non-ascii characters not supported
7 participants