[Mailer] Check email validity before opening an SMTP connection

This commit is contained in:
Fabien Potencier 2019-09-05 14:01:23 +02:00
parent b7371ea5c6
commit dc376f52a5
8 changed files with 61 additions and 30 deletions

View File

@ -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);
} }
} }

View File

@ -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;

View File

@ -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());

View File

@ -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 = [];

View File

@ -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) {

View File

@ -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, '@');

View File

@ -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
*/ */

View File

@ -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');