[Mailer] Check email validity before opening an SMTP connection
This commit is contained in:
parent
b7371ea5c6
commit
dc376f52a5
@ -50,7 +50,8 @@ class SendgridApiTransportTest extends TestCase
|
|||||||
$email = new Email();
|
$email = new Email();
|
||||||
$email->from('foo@example.com')
|
$email->from('foo@example.com')
|
||||||
->to('bar@example.com')
|
->to('bar@example.com')
|
||||||
->bcc('baz@example.com');
|
->bcc('baz@example.com')
|
||||||
|
->text('content');
|
||||||
|
|
||||||
$response = $this->createMock(ResponseInterface::class);
|
$response = $this->createMock(ResponseInterface::class);
|
||||||
|
|
||||||
@ -74,14 +75,15 @@ class SendgridApiTransportTest extends TestCase
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'from' => ['email' => 'foo@example.com'],
|
'from' => ['email' => 'foo@example.com'],
|
||||||
'content' => [],
|
'content' => [
|
||||||
|
['type' => 'text/plain', 'value' => 'content'],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'auth_bearer' => 'foo',
|
'auth_bearer' => 'foo',
|
||||||
])
|
])
|
||||||
->willReturn($response);
|
->willReturn($response);
|
||||||
|
|
||||||
$mailer = new SendgridApiTransport('foo', $httpClient);
|
$mailer = new SendgridApiTransport('foo', $httpClient);
|
||||||
|
|
||||||
$mailer->send($email);
|
$mailer->send($email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ class SentMessage
|
|||||||
*/
|
*/
|
||||||
public function __construct(RawMessage $message, SmtpEnvelope $envelope)
|
public function __construct(RawMessage $message, SmtpEnvelope $envelope)
|
||||||
{
|
{
|
||||||
|
$message->ensureValidity();
|
||||||
|
|
||||||
$this->raw = $message instanceof Message ? new RawMessage($message->toIterable()) : $message;
|
$this->raw = $message instanceof Message ? new RawMessage($message->toIterable()) : $message;
|
||||||
$this->original = $message;
|
$this->original = $message;
|
||||||
$this->envelope = $envelope;
|
$this->envelope = $envelope;
|
||||||
|
@ -104,18 +104,15 @@ class SmtpTransport extends AbstractTransport
|
|||||||
|
|
||||||
public function send(RawMessage $message, SmtpEnvelope $envelope = null): ?SentMessage
|
public function send(RawMessage $message, SmtpEnvelope $envelope = null): ?SentMessage
|
||||||
{
|
{
|
||||||
$this->ping();
|
|
||||||
if (!$this->started) {
|
|
||||||
$this->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$message = parent::send($message, $envelope);
|
$message = parent::send($message, $envelope);
|
||||||
} catch (TransportExceptionInterface $e) {
|
} catch (TransportExceptionInterface $e) {
|
||||||
try {
|
if ($this->started) {
|
||||||
$this->executeCommand("RSET\r\n", [250]);
|
try {
|
||||||
} catch (TransportExceptionInterface $_) {
|
$this->executeCommand("RSET\r\n", [250]);
|
||||||
// ignore this exception as it probably means that the server error was final
|
} catch (TransportExceptionInterface $_) {
|
||||||
|
// ignore this exception as it probably means that the server error was final
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
@ -163,6 +160,11 @@ class SmtpTransport extends AbstractTransport
|
|||||||
|
|
||||||
protected function doSend(SentMessage $message): void
|
protected function doSend(SentMessage $message): void
|
||||||
{
|
{
|
||||||
|
$this->ping();
|
||||||
|
if (!$this->started) {
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$envelope = $message->getEnvelope();
|
$envelope = $message->getEnvelope();
|
||||||
$this->doMailFromCommand($envelope->getSender()->getAddress());
|
$this->doMailFromCommand($envelope->getSender()->getAddress());
|
||||||
|
@ -26,7 +26,7 @@ final class SocketStream extends AbstractStream
|
|||||||
private $url;
|
private $url;
|
||||||
private $host = 'localhost';
|
private $host = 'localhost';
|
||||||
private $port = 465;
|
private $port = 465;
|
||||||
private $timeout = 15;
|
private $timeout = 5;
|
||||||
private $tls = true;
|
private $tls = true;
|
||||||
private $sourceIp;
|
private $sourceIp;
|
||||||
private $streamContextOptions = [];
|
private $streamContextOptions = [];
|
||||||
|
@ -399,6 +399,15 @@ class Email extends Message
|
|||||||
return $this->generateBody();
|
return $this->generateBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ensureValidity()
|
||||||
|
{
|
||||||
|
if (null === $this->text && null === $this->html && !$this->attachments) {
|
||||||
|
throw new LogicException('A message must have a text or an HTML part or attachments.');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::ensureValidity();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an AbstractPart based on the raw body of a message.
|
* Generates an AbstractPart based on the raw body of a message.
|
||||||
*
|
*
|
||||||
@ -421,10 +430,9 @@ class Email extends Message
|
|||||||
*/
|
*/
|
||||||
private function generateBody(): AbstractPart
|
private function generateBody(): AbstractPart
|
||||||
{
|
{
|
||||||
|
$this->ensureValidity();
|
||||||
|
|
||||||
[$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts();
|
[$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts();
|
||||||
if (null === $this->text && null === $this->html && !$attachmentParts) {
|
|
||||||
throw new LogicException('A message must have a text or an HTML part or attachments.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
|
$part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
|
||||||
if (null !== $htmlPart) {
|
if (null !== $htmlPart) {
|
||||||
|
@ -123,6 +123,15 @@ class Message extends RawMessage
|
|||||||
yield from $body->toIterable();
|
yield from $body->toIterable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ensureValidity()
|
||||||
|
{
|
||||||
|
if (!$this->headers->has('From')) {
|
||||||
|
throw new LogicException('An email must have a "From" header.');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::ensureValidity();
|
||||||
|
}
|
||||||
|
|
||||||
private function generateMessageId(string $email): string
|
private function generateMessageId(string $email): string
|
||||||
{
|
{
|
||||||
return bin2hex(random_bytes(16)).strstr($email, '@');
|
return bin2hex(random_bytes(16)).strstr($email, '@');
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Mime;
|
namespace Symfony\Component\Mime;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\LogicException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
@ -51,6 +53,13 @@ class RawMessage implements \Serializable
|
|||||||
$this->message = $message;
|
$this->message = $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LogicException if the message is not valid
|
||||||
|
*/
|
||||||
|
public function ensureValidity()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -251,62 +251,62 @@ class EmailTest extends TestCase
|
|||||||
$att = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r'));
|
$att = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r'));
|
||||||
$img = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif');
|
$img = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif');
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$this->assertEquals($text, $e->getBody());
|
$this->assertEquals($text, $e->getBody());
|
||||||
$this->assertEquals('text content', $e->getTextBody());
|
$this->assertEquals('text content', $e->getTextBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html('html content');
|
$e->html('html content');
|
||||||
$this->assertEquals($html, $e->getBody());
|
$this->assertEquals($html, $e->getBody());
|
||||||
$this->assertEquals('html content', $e->getHtmlBody());
|
$this->assertEquals('html content', $e->getHtmlBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html('html content');
|
$e->html('html content');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$this->assertEquals(new AlternativePart($text, $html), $e->getBody());
|
$this->assertEquals(new AlternativePart($text, $html), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html('html content', 'iso-8859-1');
|
$e->html('html content', 'iso-8859-1');
|
||||||
$e->text('text content', 'iso-8859-1');
|
$e->text('text content', 'iso-8859-1');
|
||||||
$this->assertEquals('iso-8859-1', $e->getTextCharset());
|
$this->assertEquals('iso-8859-1', $e->getTextCharset());
|
||||||
$this->assertEquals('iso-8859-1', $e->getHtmlCharset());
|
$this->assertEquals('iso-8859-1', $e->getHtmlCharset());
|
||||||
$this->assertEquals(new AlternativePart(new TextPart('text content', 'iso-8859-1'), new TextPart('html content', 'iso-8859-1', 'html')), $e->getBody());
|
$this->assertEquals(new AlternativePart(new TextPart('text content', 'iso-8859-1'), new TextPart('html content', 'iso-8859-1', 'html')), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$this->assertEquals(new MixedPart($text, $att), $e->getBody());
|
$this->assertEquals(new MixedPart($text, $att), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$e->html('html content');
|
$e->html('html content');
|
||||||
$this->assertEquals(new MixedPart($html, $att), $e->getBody());
|
$this->assertEquals(new MixedPart($html, $att), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$this->assertEquals(new MixedPart($att), $e->getBody());
|
$this->assertEquals(new MixedPart($att), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html('html content');
|
$e->html('html content');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att), $e->getBody());
|
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html('html content');
|
$e->html('html content');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$e->attach($image, 'test.gif');
|
$e->attach($image, 'test.gif');
|
||||||
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att, $img), $e->getBody());
|
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att, $img), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$e->attach($image, 'test.gif');
|
$e->attach($image, 'test.gif');
|
||||||
$this->assertEquals(new MixedPart($text, $att, $img), $e->getBody());
|
$this->assertEquals(new MixedPart($text, $att, $img), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html($content = 'html content <img src="test.gif">');
|
$e->html($content = 'html content <img src="test.gif">');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
@ -314,13 +314,12 @@ class EmailTest extends TestCase
|
|||||||
$fullhtml = new TextPart($content, 'utf-8', 'html');
|
$fullhtml = new TextPart($content, 'utf-8', 'html');
|
||||||
$this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $att, $img), $e->getBody());
|
$this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $att, $img), $e->getBody());
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html($content = 'html content <img src="cid:test.gif">');
|
$e->html($content = 'html content <img src="cid:test.gif">');
|
||||||
$e->text('text content');
|
$e->text('text content');
|
||||||
$e->attach($file);
|
$e->attach($file);
|
||||||
$e->attach($image, 'test.gif');
|
$e->attach($image, 'test.gif');
|
||||||
$fullhtml = new TextPart($content, 'utf-8', 'html');
|
$fullhtml = new TextPart($content, 'utf-8', 'html');
|
||||||
$inlinedimg = (new DataPart($image, 'test.gif'))->asInline();
|
|
||||||
$body = $e->getBody();
|
$body = $e->getBody();
|
||||||
$this->assertInstanceOf(MixedPart::class, $body);
|
$this->assertInstanceOf(MixedPart::class, $body);
|
||||||
$this->assertCount(2, $related = $body->getParts());
|
$this->assertCount(2, $related = $body->getParts());
|
||||||
@ -336,7 +335,7 @@ class EmailTest extends TestCase
|
|||||||
fwrite($r, $content);
|
fwrite($r, $content);
|
||||||
rewind($r);
|
rewind($r);
|
||||||
|
|
||||||
$e = new Email();
|
$e = (new Email())->from('me@example.com');
|
||||||
$e->html($r);
|
$e->html($r);
|
||||||
// embedding the same image twice results in one image only in the email
|
// embedding the same image twice results in one image only in the email
|
||||||
$e->embed($image, 'test.gif');
|
$e->embed($image, 'test.gif');
|
||||||
|
Reference in New Issue
Block a user