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

This commit is contained in:
David Maicher 2020-03-23 13:15:15 +01:00
parent ad94b395d0
commit d057dffcd6
7 changed files with 36 additions and 26 deletions

View File

@ -41,11 +41,11 @@ final class DelayedEnvelope extends Envelope
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

View File

@ -44,6 +44,10 @@ class Envelope
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());
}

View File

@ -13,9 +13,11 @@ namespace Symfony\Component\Mailer\Tests;
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;
@ -27,6 +29,13 @@ class EnvelopeTest extends TestCase
$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')]);
@ -57,19 +66,27 @@ class EnvelopeTest extends TestCase
$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());
$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()
@ -78,7 +95,7 @@ class EnvelopeTest extends TestCase
$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()

View File

@ -11,16 +11,14 @@
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
*/
@ -28,8 +26,6 @@ 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
{
@ -38,10 +34,6 @@ final class IdnAddressEncoder implements AddressEncoderInterface
$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));
}

View File

@ -58,11 +58,10 @@ class MailboxHeaderTest extends TestCase
$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()

View File

@ -55,11 +55,10 @@ class MailboxListHeaderTest extends TestCase
$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()

View File

@ -49,11 +49,10 @@ class PathHeaderTest extends TestCase
$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()