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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Symfony/Component/Mailer/DelayedEnvelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public function setSender(Address $sender): void

public function getSender(): Address
{
if ($this->senderSet) {
return parent::getSender();
if (!$this->senderSet) {
parent::setSender(self::getSenderFromHeaders($this->message->getHeaders()));
}

return self::getSenderFromHeaders($this->message->getHeaders());
return parent::getSender();
}

public function setRecipients(array $recipients): void
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/Mailer/Envelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public static function create(RawMessage $message): self

public function setSender(Address $sender): void
{
// to ensure deliverability of bounce emails independent of UTF-8 capabilities of SMTP servers
if (!preg_match('/^[^@\x80-\xFF]++@/', $sender->getAddress())) {
throw new InvalidArgumentException(sprintf('Invalid sender "%s": non-ASCII characters not supported in local-part of email.', $sender->getAddress()));
}
$this->sender = new Address($sender->getAddress());
}

Expand Down
25 changes: 21 additions & 4 deletions src/Symfony/Component/Mailer/Tests/EnvelopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\InvalidArgumentException;
use Symfony\Component\Mailer\Exception\LogicException;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\Header\PathHeader;
use Symfony\Component\Mime\Message;
use Symfony\Component\Mime\RawMessage;

Expand All @@ -27,6 +29,13 @@ public function testConstructorWithAddressSender()
$this->assertEquals(new Address('fabien@symfony.com'), $e->getSender());
}

public function testConstructorWithAddressSenderAndNonAsciiCharactersInLocalPartOfAddress()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid sender "fabièn@symfony.com": non-ASCII characters not supported in local-part of email.');
new Envelope(new Address('fabièn@symfony.com'), [new Address('thomas@symfony.com')]);
}

public function testConstructorWithNamedAddressSender()
{
$e = new Envelope(new Address('fabien@symfony.com', 'Fabien'), [new Address('thomas@symfony.com')]);
Expand Down Expand Up @@ -57,19 +66,27 @@ public function testSenderFromHeaders()
$headers->addPathHeader('Return-Path', new Address('return@symfony.com', 'return'));
$headers->addMailboxListHeader('To', ['from@symfony.com']);
$e = Envelope::create(new Message($headers));
$this->assertEquals(new Address('return@symfony.com', 'return'), $e->getSender());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabpot here is a slight change in behavior now since we use parent::setAddress and it will strip away the name of the address

$this->assertEquals(new Address('return@symfony.com'), $e->getSender());

$headers = new Headers();
$headers->addMailboxHeader('Sender', new Address('sender@symfony.com', 'sender'));
$headers->addMailboxListHeader('To', ['from@symfony.com']);
$e = Envelope::create(new Message($headers));
$this->assertEquals(new Address('sender@symfony.com', 'sender'), $e->getSender());
$this->assertEquals(new Address('sender@symfony.com'), $e->getSender());

$headers = new Headers();
$headers->addMailboxListHeader('From', [new Address('from@symfony.com', 'from'), 'some@symfony.com']);
$headers->addMailboxListHeader('To', ['from@symfony.com']);
$e = Envelope::create(new Message($headers));
$this->assertEquals(new Address('from@symfony.com', 'from'), $e->getSender());
$this->assertEquals(new Address('from@symfony.com'), $e->getSender());
}

public function testSenderFromHeadersFailsWithNonAsciiCharactersInLocalPart()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid sender "fabièn@symfony.com": non-ASCII characters not supported in local-part of email.');
$message = new Message(new Headers(new PathHeader('Return-Path', new Address('fabièn@symfony.com'))));
Envelope::create($message)->getSender();
}

public function testSenderFromHeadersWithoutFrom()
Expand All @@ -78,7 +95,7 @@ public function testSenderFromHeadersWithoutFrom()
$headers->addMailboxListHeader('To', ['from@symfony.com']);
$e = Envelope::create($message = new Message($headers));
$message->getHeaders()->addMailboxListHeader('From', [new Address('from@symfony.com', 'from')]);
$this->assertEquals(new Address('from@symfony.com', 'from'), $e->getSender());
$this->assertEquals(new Address('from@symfony.com'), $e->getSender());
}

public function testRecipientsFromHeaders()
Expand Down
12 changes: 2 additions & 10 deletions src/Symfony/Component/Mime/Encoder/IdnAddressEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,21 @@

namespace Symfony\Component\Mime\Encoder;

use Symfony\Component\Mime\Exception\AddressEncoderException;

/**
* An IDN email address encoder.
*
* Encodes the domain part of an address using IDN. This is compatible will all
* SMTP servers.
*
* This encoder does not support email addresses with non-ASCII characters in
* local-part (the substring before @).
* Note: It leaves the local part as is. In case there are non-ASCII characters
* in the local part then it depends on the SMTP Server if this is supported.
*
* @author Christian Schmidt
*/
final class IdnAddressEncoder implements AddressEncoderInterface
{
/**
* Encodes the domain part of an address using IDN.
*
* @throws AddressEncoderException If local-part contains non-ASCII characters
*/
public function encodeString(string $address): string
{
Expand All @@ -38,10 +34,6 @@ public function encodeString(string $address): string
$local = substr($address, 0, $i);
$domain = substr($address, $i + 1);

if (preg_match('/[^\x00-\x7F]/', $local)) {
throw new AddressEncoderException(sprintf('Non-ASCII characters not supported in local-part os "%s".', $address));
}

if (preg_match('/[^\x00-\x7F]/', $domain)) {
$address = sprintf('%s@%s', $local, idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46));
}
Expand Down
5 changes: 2 additions & 3 deletions src/Symfony/Component/Mime/Tests/Header/MailboxHeaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,10 @@ public function testgetBodyAsString()
$this->assertEquals('Fabien =?'.$header->getCharset().'?Q?P=8Ftencier?= <fabien@symfony.com>', $header->getBodyAsString());
}

public function testUtf8CharsInLocalPartThrows()
public function testUtf8CharsInLocalPart()
{
$this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException');
$header = new MailboxHeader('Sender', new Address('fabïen@symfony.com'));
$header->getBodyAsString();
$this->assertSame('fabïen@symfony.com', $header->getBodyAsString());
}

public function testToString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ public function testUtf8CharsInDomainAreIdnEncoded()
$this->assertEquals(['Chris Corbyn <chris@xn--swftmailer-78a.org>'], $header->getAddressStrings());
}

public function testUtf8CharsInLocalPartThrows()
public function testUtf8CharsInLocalPart()
{
$this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException');
$header = new MailboxListHeader('From', [new Address('chrïs@swiftmailer.org', 'Chris Corbyn')]);
$header->getAddressStrings();
$this->assertSame(['Chris Corbyn <chrïs@swiftmailer.org>'], $header->getAddressStrings());
}

public function testGetMailboxesReturnsNameValuePairs()
Expand Down
5 changes: 2 additions & 3 deletions src/Symfony/Component/Mime/Tests/Header/PathHeaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,10 @@ public function testAddressIsIdnEncoded()
$this->assertEquals('<chris@xn--swftmailer-78a.org>', $header->getBodyAsString());
}

public function testAddressMustBeEncodable()
public function testAddressMustBeEncodableWithUtf8CharsInLocalPart()
{
$this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException');
$header = new PathHeader('Return-Path', new Address('chrïs@swiftmailer.org'));
$header->getBodyAsString();
$this->assertSame('<chrïs@swiftmailer.org>', $header->getBodyAsString());
}

public function testSetBody()
Expand Down