diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml index 7cb290dc24d5..c2c3ace06f17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml @@ -4,7 +4,7 @@ imports: framework: mailer: - dsn: 'smtp://null' + dsn: 'null://null' envelope: sender: sender@example.org recipients: diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php new file mode 100644 index 000000000000..a1b0ae7f1a81 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiTransport; + +class SesApiTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(SesApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new SesApiTransport('ACCESS_KEY', 'SECRET_KEY'), + 'ses+api://ACCESS_KEY@email.eu-west-1.amazonaws.com', + ], + [ + new SesApiTransport('ACCESS_KEY', 'SECRET_KEY', 'us-east-1'), + 'ses+api://ACCESS_KEY@email.us-east-1.amazonaws.com', + ], + [ + (new SesApiTransport('ACCESS_KEY', 'SECRET_KEY'))->setHost('example.com'), + 'ses+api://ACCESS_KEY@example.com', + ], + [ + (new SesApiTransport('ACCESS_KEY', 'SECRET_KEY'))->setHost('example.com')->setPort(99), + 'ses+api://ACCESS_KEY@example.com:99', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php new file mode 100644 index 000000000000..4e7cbd66aa15 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpTransport; + +class SesHttpTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(SesHttpTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new SesHttpTransport('ACCESS_KEY', 'SECRET_KEY'), + 'ses+https://ACCESS_KEY@email.eu-west-1.amazonaws.com', + ], + [ + new SesHttpTransport('ACCESS_KEY', 'SECRET_KEY', 'us-east-1'), + 'ses+https://ACCESS_KEY@email.us-east-1.amazonaws.com', + ], + [ + (new SesHttpTransport('ACCESS_KEY', 'SECRET_KEY'))->setHost('example.com'), + 'ses+https://ACCESS_KEY@example.com', + ], + [ + (new SesHttpTransport('ACCESS_KEY', 'SECRET_KEY'))->setHost('example.com')->setPort(99), + 'ses+https://ACCESS_KEY@example.com:99', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesTransportFactoryTest.php index 8e21f56f5044..7fada879ddc6 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesTransportFactoryTest.php @@ -29,28 +29,33 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('api', 'ses'), + new Dsn('ses+api', 'default'), true, ]; yield [ - new Dsn('http', 'ses'), + new Dsn('ses+https', 'default'), true, ]; yield [ - new Dsn('smtp', 'ses'), + new Dsn('ses', 'default'), true, ]; yield [ - new Dsn('smtps', 'ses'), + new Dsn('ses+smtp', 'default'), true, ]; yield [ - new Dsn('smtp', 'example.com'), - false, + new Dsn('ses+smtps', 'default'), + true, + ]; + + yield [ + new Dsn('ses+smtp', 'example.com'), + true, ]; } @@ -61,37 +66,52 @@ public function createProvider(): iterable $logger = $this->getLogger(); yield [ - new Dsn('api', 'ses', self::USER, self::PASSWORD), + new Dsn('ses+api', 'default', self::USER, self::PASSWORD), new SesApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), ]; yield [ - new Dsn('api', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Dsn('ses+api', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), new SesApiTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger), ]; yield [ - new Dsn('http', 'ses', self::USER, self::PASSWORD), + new Dsn('ses+api', 'example.com', self::USER, self::PASSWORD, 8080), + (new SesApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('ses+https', 'default', self::USER, self::PASSWORD), new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), ]; yield [ - new Dsn('http', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Dsn('ses', 'default', self::USER, self::PASSWORD), + new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('ses+https', 'example.com', self::USER, self::PASSWORD, 8080), + (new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('ses+https', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), new SesHttpTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger), ]; yield [ - new Dsn('smtp', 'ses', self::USER, self::PASSWORD), + new Dsn('ses+smtp', 'default', self::USER, self::PASSWORD), new SesSmtpTransport(self::USER, self::PASSWORD, null, $dispatcher, $logger), ]; yield [ - new Dsn('smtp', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Dsn('ses+smtp', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), new SesSmtpTransport(self::USER, self::PASSWORD, 'eu-west-1', $dispatcher, $logger), ]; yield [ - new Dsn('smtps', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Dsn('ses+smtps', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), new SesSmtpTransport(self::USER, self::PASSWORD, 'eu-west-1', $dispatcher, $logger), ]; } @@ -99,15 +119,15 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('foo', 'ses', self::USER, self::PASSWORD), - 'The "foo" scheme is not supported for mailer "ses". Supported schemes are: "api", "http", "smtp", "smtps".', + new Dsn('ses+foo', 'default', self::USER, self::PASSWORD), + 'The "ses+foo" scheme is not supported. Supported schemes for mailer "ses" are: "ses", "ses+api", "ses+https", "ses+smtp", "ses+smtps".', ]; } public function incompleteDsnProvider(): iterable { - yield [new Dsn('smtp', 'ses', self::USER)]; + yield [new Dsn('ses+smtp', 'default', self::USER)]; - yield [new Dsn('smtp', 'ses', null, self::PASSWORD)]; + yield [new Dsn('ses+smtp', 'default', null, self::PASSWORD)]; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index 6bd2b2cb9a00..1bfa9db341d2 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -25,7 +25,7 @@ */ class SesApiTransport extends AbstractApiTransport { - private const ENDPOINT = 'https://email.%region%.amazonaws.com'; + private const HOST = 'email.%region%.amazonaws.com'; private $accessKey; private $secretKey; @@ -45,7 +45,7 @@ public function __construct(string $accessKey, string $secretKey, string $region public function __toString(): string { - return sprintf('api://%s@ses?region=%s', $this->accessKey, $this->region); + return sprintf('ses+api://%s@%s', $this->accessKey, $this->getEndpoint()); } protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInterface @@ -53,8 +53,7 @@ protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInte $date = gmdate('D, d M Y H:i:s e'); $auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date)); - $endpoint = str_replace('%region%', $this->region, self::ENDPOINT); - $response = $this->client->request('POST', $endpoint, [ + $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), [ 'headers' => [ 'X-Amzn-Authorization' => $auth, 'Date' => $date, @@ -72,6 +71,11 @@ protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInte return $response; } + private function getEndpoint(): ?string + { + return ($this->host ?: str_replace('%region%', $this->region, self::HOST)).($this->port ? ':'.$this->port : ''); + } + private function getSignature(string $string): string { return base64_encode(hash_hmac('sha256', $string, $this->secretKey, true)); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index 202aef7baa1f..be828b0dfc9e 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -24,7 +24,7 @@ */ class SesHttpTransport extends AbstractHttpTransport { - private const ENDPOINT = 'https://email.%region%.amazonaws.com'; + private const HOST = 'email.%region%.amazonaws.com'; private $accessKey; private $secretKey; @@ -44,7 +44,7 @@ public function __construct(string $accessKey, string $secretKey, string $region public function __toString(): string { - return sprintf('http://%s@ses?region=%s', $this->accessKey, $this->region); + return sprintf('ses+https://%s@%s', $this->accessKey, $this->getEndpoint()); } protected function doSendHttp(SentMessage $message): ResponseInterface @@ -52,8 +52,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $date = gmdate('D, d M Y H:i:s e'); $auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date)); - $endpoint = str_replace('%region%', $this->region, self::ENDPOINT); - $response = $this->client->request('POST', $endpoint, [ + $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), [ 'headers' => [ 'X-Amzn-Authorization' => $auth, 'Date' => $date, @@ -73,6 +72,11 @@ protected function doSendHttp(SentMessage $message): ResponseInterface return $response; } + private function getEndpoint(): ?string + { + return ($this->host ?: str_replace('%region%', $this->region, self::HOST)).($this->port ? ':'.$this->port : ''); + } + private function getSignature(string $string): string { return base64_encode(hash_hmac('sha256', $string, $this->secretKey, true)); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php index 0dba1d998b46..5977d2f37682 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php @@ -27,24 +27,26 @@ public function create(Dsn $dsn): TransportInterface $user = $this->getUser($dsn); $password = $this->getPassword($dsn); $region = $dsn->getOption('region'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); - if ('api' === $scheme) { - return new SesApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + if ('ses+api' === $scheme) { + return (new SesApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('http' === $scheme) { - return new SesHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + if ('ses+https' === $scheme || 'ses' === $scheme) { + return (new SesHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('smtp' === $scheme || 'smtps' === $scheme) { + if ('ses+smtp' === $scheme || 'ses+smtps' === $scheme) { return new SesSmtpTransport($user, $password, $region, $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['api', 'http', 'smtp', 'smtps']); + throw new UnsupportedSchemeException($dsn, 'ses', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'ses' === $dsn->getHost(); + return ['ses', 'ses+api', 'ses+https', 'ses+smtp', 'ses+smtps']; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Google/Tests/Transport/GmailTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Google/Tests/Transport/GmailTransportFactoryTest.php index 803b3b4e2473..456be7848306 100644 --- a/src/Symfony/Component/Mailer/Bridge/Google/Tests/Transport/GmailTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Google/Tests/Transport/GmailTransportFactoryTest.php @@ -18,30 +18,40 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('smtp', 'gmail'), + new Dsn('gmail', 'default'), true, ]; yield [ - new Dsn('smtps', 'gmail'), + new Dsn('gmail+smtp', 'default'), true, ]; yield [ - new Dsn('smtp', 'example.com'), - false, + new Dsn('gmail+smtps', 'default'), + true, + ]; + + yield [ + new Dsn('gmail+smtp', 'example.com'), + true, ]; } public function createProvider(): iterable { yield [ - new Dsn('smtp', 'gmail', self::USER, self::PASSWORD), + new Dsn('gmail', 'default', self::USER, self::PASSWORD), + new GmailSmtpTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()), + ]; + + yield [ + new Dsn('gmail+smtp', 'default', self::USER, self::PASSWORD), new GmailSmtpTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()), ]; yield [ - new Dsn('smtps', 'gmail', self::USER, self::PASSWORD), + new Dsn('gmail+smtps', 'default', self::USER, self::PASSWORD), new GmailSmtpTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()), ]; } @@ -49,15 +59,15 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('foo', 'gmail', self::USER, self::PASSWORD), - 'The "foo" scheme is not supported for mailer "gmail". Supported schemes are: "smtp", "smtps".', + new Dsn('gmail+foo', 'default', self::USER, self::PASSWORD), + 'The "gmail+foo" scheme is not supported. Supported schemes for mailer "gmail" are: "gmail", "gmail+smtp", "gmail+smtps".', ]; } public function incompleteDsnProvider(): iterable { - yield [new Dsn('smtp', 'gmail', self::USER)]; + yield [new Dsn('gmail+smtp', 'default', self::USER)]; - yield [new Dsn('smtp', 'gmail', null, self::PASSWORD)]; + yield [new Dsn('gmail+smtp', 'default', null, self::PASSWORD)]; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailTransportFactory.php index 346a2a7e93a4..8a0bd5626699 100644 --- a/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailTransportFactory.php @@ -23,15 +23,15 @@ final class GmailTransportFactory extends AbstractTransportFactory { public function create(Dsn $dsn): TransportInterface { - if ('smtp' === $dsn->getScheme() || 'smtps' === $dsn->getScheme()) { + if (\in_array($dsn->getScheme(), $this->getSupportedSchemes())) { return new GmailSmtpTransport($this->getUser($dsn), $this->getPassword($dsn), $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['smtp', 'smtps']); + throw new UnsupportedSchemeException($dsn, 'gmail', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'gmail' === $dsn->getHost(); + return ['gmail', 'gmail+smtp', 'gmail+smtps']; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillApiTransportTest.php new file mode 100644 index 000000000000..2bec48281842 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillApiTransportTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailchimp\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillApiTransport; + +class MandrillApiTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(MandrillApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new MandrillApiTransport('KEY'), + 'mandrill+api://mandrillapp.com', + ], + [ + (new MandrillApiTransport('KEY'))->setHost('example.com'), + 'mandrill+api://example.com', + ], + [ + (new MandrillApiTransport('KEY'))->setHost('example.com')->setPort(99), + 'mandrill+api://example.com:99', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php new file mode 100644 index 000000000000..dd72c848f14f --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailchimp\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillHttpTransport; + +class MandrillHttpTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(MandrillHttpTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new MandrillHttpTransport('KEY'), + 'mandrill+https://mandrillapp.com', + ], + [ + (new MandrillHttpTransport('KEY'))->setHost('example.com'), + 'mandrill+https://example.com', + ], + [ + (new MandrillHttpTransport('KEY'))->setHost('example.com')->setPort(99), + 'mandrill+https://example.com:99', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillTransportFactoryTest.php index 2e8e2c0c0cc7..317cbb35cdcf 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillTransportFactoryTest.php @@ -29,28 +29,33 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('api', 'mandrill'), + new Dsn('mandrill', 'default'), true, ]; yield [ - new Dsn('http', 'mandrill'), + new Dsn('mandrill+api', 'default'), true, ]; yield [ - new Dsn('smtp', 'mandrill'), + new Dsn('mandrill+https', 'default'), true, ]; yield [ - new Dsn('smtps', 'mandrill'), + new Dsn('mandrill+smtp', 'default'), true, ]; yield [ - new Dsn('smtp', 'example.com'), - false, + new Dsn('mandrill+smtps', 'default'), + true, + ]; + + yield [ + new Dsn('mandrill+smtp', 'example.com'), + true, ]; } @@ -61,22 +66,37 @@ public function createProvider(): iterable $logger = $this->getLogger(); yield [ - new Dsn('api', 'mandrill', self::USER), + new Dsn('mandrill+api', 'default', self::USER), new MandrillApiTransport(self::USER, $client, $dispatcher, $logger), ]; yield [ - new Dsn('http', 'mandrill', self::USER), + new Dsn('mandrill+api', 'example.com', self::USER, '', 8080), + (new MandrillApiTransport(self::USER, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('mandrill', 'default', self::USER), new MandrillHttpTransport(self::USER, $client, $dispatcher, $logger), ]; yield [ - new Dsn('smtp', 'mandrill', self::USER, self::PASSWORD), + new Dsn('mandrill+https', 'default', self::USER), + new MandrillHttpTransport(self::USER, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('mandrill+https', 'example.com', self::USER, '', 8080), + (new MandrillHttpTransport(self::USER, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('mandrill+smtp', 'default', self::USER, self::PASSWORD), new MandrillSmtpTransport(self::USER, self::PASSWORD, $dispatcher, $logger), ]; yield [ - new Dsn('smtps', 'mandrill', self::USER, self::PASSWORD), + new Dsn('mandrill+smtps', 'default', self::USER, self::PASSWORD), new MandrillSmtpTransport(self::USER, self::PASSWORD, $dispatcher, $logger), ]; } @@ -84,15 +104,15 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('foo', 'mandrill', self::USER), - 'The "foo" scheme is not supported for mailer "mandrill". Supported schemes are: "api", "http", "smtp", "smtps".', + new Dsn('mandrill+foo', 'default', self::USER), + 'The "mandrill+foo" scheme is not supported. Supported schemes for mailer "mandrill" are: "mandrill", "mandrill+api", "mandrill+https", "mandrill+smtp", "mandrill+smtps".', ]; } public function incompleteDsnProvider(): iterable { - yield [new Dsn('api', 'mandrill')]; + yield [new Dsn('mandrill+api', 'default')]; - yield [new Dsn('smtp', 'mandrill', self::USER)]; + yield [new Dsn('mandrill+smtp', 'default', self::USER)]; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php index 879611b88484..904f66994c58 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php @@ -25,7 +25,7 @@ */ class MandrillApiTransport extends AbstractApiTransport { - private const ENDPOINT = 'https://mandrillapp.com/api/1.0/messages/send.json'; + private const HOST = 'mandrillapp.com'; private $key; @@ -38,12 +38,12 @@ public function __construct(string $key, HttpClientInterface $client = null, Eve public function __toString(): string { - return sprintf('api://mandrill'); + return sprintf('mandrill+api://%s', $this->getEndpoint()); } protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInterface { - $response = $this->client->request('POST', self::ENDPOINT, [ + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send.json', [ 'json' => $this->getPayload($email, $envelope), ]); @@ -59,6 +59,11 @@ protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInte return $response; } + private function getEndpoint(): ?string + { + return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : ''); + } + private function getPayload(Email $email, SmtpEnvelope $envelope): array { $payload = [ diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index a24afa86a8e9..cec26aaf03d0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -24,7 +24,7 @@ */ class MandrillHttpTransport extends AbstractHttpTransport { - private const ENDPOINT = 'https://mandrillapp.com/api/1.0/messages/send-raw.json'; + private const HOST = 'mandrillapp.com'; private $key; public function __construct(string $key, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) @@ -36,13 +36,13 @@ public function __construct(string $key, HttpClientInterface $client = null, Eve public function __toString(): string { - return sprintf('http://mandrill'); + return sprintf('mandrill+https://%s', $this->getEndpoint()); } protected function doSendHttp(SentMessage $message): ResponseInterface { $envelope = $message->getEnvelope(); - $response = $this->client->request('POST', self::ENDPOINT, [ + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send-raw.json', [ 'json' => [ 'key' => $this->key, 'to' => $this->stringifyAddresses($envelope->getRecipients()), @@ -62,4 +62,9 @@ protected function doSendHttp(SentMessage $message): ResponseInterface return $response; } + + private function getEndpoint(): ?string + { + return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : ''); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillTransportFactory.php index b00b2bee748e..1ba963e00d7d 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillTransportFactory.php @@ -25,26 +25,28 @@ public function create(Dsn $dsn): TransportInterface { $scheme = $dsn->getScheme(); $user = $this->getUser($dsn); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); - if ('api' === $scheme) { - return new MandrillApiTransport($user, $this->client, $this->dispatcher, $this->logger); + if ('mandrill+api' === $scheme) { + return (new MandrillApiTransport($user, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('http' === $scheme) { - return new MandrillHttpTransport($user, $this->client, $this->dispatcher, $this->logger); + if ('mandrill+https' === $scheme || 'mandrill' === $scheme) { + return (new MandrillHttpTransport($user, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('smtp' === $scheme || 'smtps' === $scheme) { + if ('mandrill+smtp' === $scheme || 'mandrill+smtps' === $scheme) { $password = $this->getPassword($dsn); return new MandrillSmtpTransport($user, $password, $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['api', 'http', 'smtp', 'smtps']); + throw new UnsupportedSchemeException($dsn, 'mandrill', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'mandrill' === $dsn->getHost(); + return ['mandrill', 'mandrill+api', 'mandrill+https', 'mandrill+smtp', 'mandrill+smtps']; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php new file mode 100644 index 000000000000..eb9838390aed --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailgun\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunApiTransport; + +class MailgunApiTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(MailgunApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new MailgunApiTransport('ACCESS_KEY', 'DOMAIN'), + 'mailgun+api://api.mailgun.net?domain=DOMAIN', + ], + [ + new MailgunApiTransport('ACCESS_KEY', 'DOMAIN', 'us-east-1'), + 'mailgun+api://api.us-east-1.mailgun.net?domain=DOMAIN', + ], + [ + (new MailgunApiTransport('ACCESS_KEY', 'DOMAIN'))->setHost('example.com'), + 'mailgun+api://example.com?domain=DOMAIN', + ], + [ + (new MailgunApiTransport('ACCESS_KEY', 'DOMAIN'))->setHost('example.com')->setPort(99), + 'mailgun+api://example.com:99?domain=DOMAIN', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunHttpTransportTest.php new file mode 100644 index 000000000000..9b57b2b35e77 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunHttpTransportTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailgun\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunHttpTransport; + +class MailgunHttpTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(MailgunHttpTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new MailgunHttpTransport('ACCESS_KEY', 'DOMAIN'), + 'mailgun+https://api.mailgun.net?domain=DOMAIN', + ], + [ + new MailgunHttpTransport('ACCESS_KEY', 'DOMAIN', 'us-east-1'), + 'mailgun+https://api.us-east-1.mailgun.net?domain=DOMAIN', + ], + [ + (new MailgunHttpTransport('ACCESS_KEY', 'DOMAIN'))->setHost('example.com'), + 'mailgun+https://example.com?domain=DOMAIN', + ], + [ + (new MailgunHttpTransport('ACCESS_KEY', 'DOMAIN'))->setHost('example.com')->setPort(99), + 'mailgun+https://example.com:99?domain=DOMAIN', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunTransportFactoryTest.php index 829d880fca62..67a3ed51c429 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunTransportFactoryTest.php @@ -29,28 +29,33 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('api', 'mailgun'), + new Dsn('mailgun+api', 'default'), true, ]; yield [ - new Dsn('http', 'mailgun'), + new Dsn('mailgun', 'default'), true, ]; yield [ - new Dsn('smtp', 'mailgun'), + new Dsn('mailgun+https', 'default'), true, ]; yield [ - new Dsn('smtps', 'mailgun'), + new Dsn('mailgun+smtp', 'default'), true, ]; yield [ - new Dsn('smtp', 'example.com'), - false, + new Dsn('mailgun+smtps', 'default'), + true, + ]; + + yield [ + new Dsn('mailgun+smtp', 'example.com'), + true, ]; } @@ -61,27 +66,42 @@ public function createProvider(): iterable $logger = $this->getLogger(); yield [ - new Dsn('api', 'mailgun', self::USER, self::PASSWORD), + new Dsn('mailgun+api', 'default', self::USER, self::PASSWORD), new MailgunApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), ]; yield [ - new Dsn('api', 'mailgun', self::USER, self::PASSWORD, null, ['region' => 'eu']), + new Dsn('mailgun+api', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu']), new MailgunApiTransport(self::USER, self::PASSWORD, 'eu', $client, $dispatcher, $logger), ]; yield [ - new Dsn('http', 'mailgun', self::USER, self::PASSWORD), + new Dsn('mailgun+api', 'example.com', self::USER, self::PASSWORD, 8080), + (new MailgunApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('mailgun', 'default', self::USER, self::PASSWORD), new MailgunHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), ]; yield [ - new Dsn('smtp', 'mailgun', self::USER, self::PASSWORD), + new Dsn('mailgun+https', 'default', self::USER, self::PASSWORD), + new MailgunHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('mailgun+https', 'example.com', self::USER, self::PASSWORD, 8080), + (new MailgunHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('mailgun+smtp', 'default', self::USER, self::PASSWORD), new MailgunSmtpTransport(self::USER, self::PASSWORD, null, $dispatcher, $logger), ]; yield [ - new Dsn('smtps', 'mailgun', self::USER, self::PASSWORD), + new Dsn('mailgun+smtps', 'default', self::USER, self::PASSWORD), new MailgunSmtpTransport(self::USER, self::PASSWORD, null, $dispatcher, $logger), ]; } @@ -89,15 +109,15 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('foo', 'mailgun', self::USER, self::PASSWORD), - 'The "foo" scheme is not supported for mailer "mailgun". Supported schemes are: "api", "http", "smtp", "smtps".', + new Dsn('mailgun+foo', 'default', self::USER, self::PASSWORD), + 'The "mailgun+foo" scheme is not supported. Supported schemes for mailer "mailgun" are: "mailgun", "mailgun+api", "mailgun+https", "mailgun+smtp", "mailgun+smtps".', ]; } public function incompleteDsnProvider(): iterable { - yield [new Dsn('api', 'mailgun', self::USER)]; + yield [new Dsn('mailgun+api', 'default', self::USER)]; - yield [new Dsn('api', 'mailgun', null, self::PASSWORD)]; + yield [new Dsn('mailgun+api', 'default', null, self::PASSWORD)]; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php index f58e18373688..1838a0106150 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php @@ -26,7 +26,7 @@ */ class MailgunApiTransport extends AbstractApiTransport { - private const ENDPOINT = 'https://api.%region_dot%mailgun.net/v3/%domain%/messages'; + private const HOST = 'api.%region_dot%mailgun.net'; private $key; private $domain; @@ -43,7 +43,7 @@ public function __construct(string $key, string $domain, string $region = null, public function __toString(): string { - return sprintf('api://%s@mailgun?region=%s', $this->domain, $this->region); + return sprintf('mailgun+api://%s?domain=%s', $this->getEndpoint(), $this->domain); } protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInterface @@ -54,8 +54,8 @@ protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInte $headers[] = $header->toString(); } - $endpoint = str_replace(['%domain%', '%region_dot%'], [urlencode($this->domain), 'us' !== ($this->region ?: 'us') ? $this->region.'.' : ''], self::ENDPOINT); - $response = $this->client->request('POST', $endpoint, [ + $endpoint = str_replace('%domain%', urlencode($this->domain), $this->getEndpoint()).'/v3/%domain%/messages'; + $response = $this->client->request('POST', 'https://'.$endpoint, [ 'auth_basic' => 'api:'.$this->key, 'headers' => $headers, 'body' => $body->bodyToIterable(), @@ -137,4 +137,11 @@ private function prepareAttachments(Email $email, ?string $html): array return [$attachments, $inlines, $html]; } + + private function getEndpoint(): ?string + { + $host = $this->host ?: str_replace('%region_dot%', 'us' !== ($this->region ?: 'us') ? $this->region.'.' : '', self::HOST); + + return $host.($this->port ? ':'.$this->port : ''); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php index ad77efea6be7..b5c9db3ba72b 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php @@ -26,7 +26,8 @@ */ class MailgunHttpTransport extends AbstractHttpTransport { - private const ENDPOINT = 'https://api.%region_dot%mailgun.net/v3/%domain%/messages.mime'; + private const HOST = 'api.%region_dot%mailgun.net'; + private $key; private $domain; private $region; @@ -42,7 +43,7 @@ public function __construct(string $key, string $domain, string $region = null, public function __toString(): string { - return sprintf('http://%s@mailgun?region=%s', $this->domain, $this->region); + return sprintf('mailgun+https://%s?domain=%s', $this->getEndpoint(), $this->domain); } protected function doSendHttp(SentMessage $message): ResponseInterface @@ -55,8 +56,9 @@ protected function doSendHttp(SentMessage $message): ResponseInterface foreach ($body->getPreparedHeaders()->all() as $header) { $headers[] = $header->toString(); } - $endpoint = str_replace(['%domain%', '%region_dot%'], [urlencode($this->domain), 'us' !== ($this->region ?: 'us') ? $this->region.'.' : ''], self::ENDPOINT); - $response = $this->client->request('POST', $endpoint, [ + + $endpoint = str_replace('%domain%', urlencode($this->domain), $this->getEndpoint()).'/v3/%domain%/messages.mime'; + $response = $this->client->request('POST', 'https://'.$endpoint, [ 'auth_basic' => 'api:'.$this->key, 'headers' => $headers, 'body' => $body->bodyToIterable(), @@ -70,4 +72,11 @@ protected function doSendHttp(SentMessage $message): ResponseInterface return $response; } + + private function getEndpoint(): ?string + { + $host = $this->host ?: str_replace('%region_dot%', 'us' !== ($this->region ?: 'us') ? $this->region.'.' : '', self::HOST); + + return $host.($this->port ? ':'.$this->port : ''); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunTransportFactory.php index 486dd6661935..c238f832fae8 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunTransportFactory.php @@ -27,24 +27,26 @@ public function create(Dsn $dsn): TransportInterface $user = $this->getUser($dsn); $password = $this->getPassword($dsn); $region = $dsn->getOption('region'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); - if ('api' === $scheme) { - return new MailgunApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + if ('mailgun+api' === $scheme) { + return (new MailgunApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('http' === $scheme) { - return new MailgunHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + if ('mailgun+https' === $scheme || 'mailgun' === $scheme) { + return (new MailgunHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('smtp' === $scheme || 'smtps' === $scheme) { + if ('mailgun+smtp' === $scheme || 'mailgun+smtps' === $scheme) { return new MailgunSmtpTransport($user, $password, $region, $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['api', 'http', 'smtp', 'smtps']); + throw new UnsupportedSchemeException($dsn, 'mailgun', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'mailgun' === $dsn->getHost(); + return ['mailgun', 'mailgun+api', 'mailgun+https', 'mailgun+smtp', 'mailgun+smtps']; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php new file mode 100644 index 000000000000..b6568706f830 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport; + +class PostmarkApiTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(PostmarkApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new PostmarkApiTransport('KEY'), + 'postmark+api://api.postmarkapp.com', + ], + [ + (new PostmarkApiTransport('KEY'))->setHost('example.com'), + 'postmark+api://example.com', + ], + [ + (new PostmarkApiTransport('KEY'))->setHost('example.com')->setPort(99), + 'postmark+api://example.com:99', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkTransportFactoryTest.php index 721af087a74d..d93a1a2081bb 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkTransportFactoryTest.php @@ -28,23 +28,28 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('api', 'postmark'), + new Dsn('postmark+api', 'default'), true, ]; yield [ - new Dsn('smtp', 'postmark'), + new Dsn('postmark', 'default'), true, ]; yield [ - new Dsn('smtps', 'postmark'), + new Dsn('postmark+smtp', 'default'), true, ]; yield [ - new Dsn('smtp', 'example.com'), - false, + new Dsn('postmark+smtps', 'default'), + true, + ]; + + yield [ + new Dsn('postmark+smtp', 'example.com'), + true, ]; } @@ -54,17 +59,27 @@ public function createProvider(): iterable $logger = $this->getLogger(); yield [ - new Dsn('api', 'postmark', self::USER), + new Dsn('postmark+api', 'default', self::USER), new PostmarkApiTransport(self::USER, $this->getClient(), $dispatcher, $logger), ]; yield [ - new Dsn('smtp', 'postmark', self::USER), + new Dsn('postmark+api', 'example.com', self::USER, '', 8080), + (new PostmarkApiTransport(self::USER, $this->getClient(), $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('postmark', 'default', self::USER), + new PostmarkSmtpTransport(self::USER, $dispatcher, $logger), + ]; + + yield [ + new Dsn('postmark+smtp', 'default', self::USER), new PostmarkSmtpTransport(self::USER, $dispatcher, $logger), ]; yield [ - new Dsn('smtps', 'postmark', self::USER), + new Dsn('postmark+smtps', 'default', self::USER), new PostmarkSmtpTransport(self::USER, $dispatcher, $logger), ]; } @@ -72,13 +87,13 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('foo', 'postmark', self::USER), - 'The "foo" scheme is not supported for mailer "postmark". Supported schemes are: "api", "smtp", "smtps".', + new Dsn('postmark+foo', 'default', self::USER), + 'The "postmark+foo" scheme is not supported. Supported schemes for mailer "postmark" are: "postmark", "postmark+api", "postmark+smtp", "postmark+smtps".', ]; } public function incompleteDsnProvider(): iterable { - yield [new Dsn('api', 'postmark')]; + yield [new Dsn('postmark+api', 'default')]; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index a13e8ae7300c..d7b8344c6b68 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -25,7 +25,7 @@ */ class PostmarkApiTransport extends AbstractApiTransport { - private const ENDPOINT = 'http://api.postmarkapp.com/email'; + private const HOST = 'api.postmarkapp.com'; private $key; @@ -38,12 +38,12 @@ public function __construct(string $key, HttpClientInterface $client = null, Eve public function __toString(): string { - return sprintf('api://postmark'); + return sprintf('postmark+api://%s', $this->getEndpoint()); } protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInterface { - $response = $this->client->request('POST', self::ENDPOINT, [ + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/email', [ 'headers' => [ 'Accept' => 'application/json', 'X-Postmark-Server-Token' => $this->key, @@ -111,4 +111,9 @@ private function getAttachments(Email $email): array return $attachments; } + + private function getEndpoint(): ?string + { + return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : ''); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkTransportFactory.php index fbe6add0c287..983f41a4503e 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkTransportFactory.php @@ -26,19 +26,22 @@ public function create(Dsn $dsn): TransportInterface $scheme = $dsn->getScheme(); $user = $this->getUser($dsn); - if ('api' === $scheme) { - return new PostmarkApiTransport($user, $this->client, $this->dispatcher, $this->logger); + if ('postmark+api' === $scheme) { + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new PostmarkApiTransport($user, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('smtp' === $scheme || 'smtps' === $scheme) { + if ('postmark+smtp' === $scheme || 'postmark+smtps' === $scheme || 'postmark' === $scheme) { return new PostmarkSmtpTransport($user, $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['api', 'smtp', 'smtps']); + throw new UnsupportedSchemeException($dsn, 'postmark', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'postmark' === $dsn->getHost(); + return ['postmark', 'postmark+api', 'postmark+smtp', 'postmark+smtps']; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php index 083c04d3173d..ea5e91af1a94 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php @@ -19,6 +19,32 @@ class SendgridApiTransportTest extends TestCase { + /** + * @dataProvider getTransportData + */ + public function testToString(SendgridApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public function getTransportData() + { + return [ + [ + new SendgridApiTransport('KEY'), + 'sendgrid+api://api.sendgrid.com', + ], + [ + (new SendgridApiTransport('KEY'))->setHost('example.com'), + 'sendgrid+api://example.com', + ], + [ + (new SendgridApiTransport('KEY'))->setHost('example.com')->setPort(99), + 'sendgrid+api://example.com:99', + ], + ]; + } + public function testSend() { $email = new Email(); diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridTransportFactoryTest.php index e6494649cda6..b32c37e0c8b4 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridTransportFactoryTest.php @@ -28,23 +28,28 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('api', 'sendgrid'), + new Dsn('sendgrid+api', 'default'), true, ]; yield [ - new Dsn('smtp', 'sendgrid'), + new Dsn('sendgrid', 'default'), true, ]; yield [ - new Dsn('smtps', 'sendgrid'), + new Dsn('sendgrid+smtp', 'default'), true, ]; yield [ - new Dsn('smtp', 'example.com'), - false, + new Dsn('sendgrid+smtps', 'default'), + true, + ]; + + yield [ + new Dsn('sendgrid+smtp', 'example.com'), + true, ]; } @@ -54,17 +59,27 @@ public function createProvider(): iterable $logger = $this->getLogger(); yield [ - new Dsn('api', 'sendgrid', self::USER), + new Dsn('sendgrid+api', 'default', self::USER), new SendgridApiTransport(self::USER, $this->getClient(), $dispatcher, $logger), ]; yield [ - new Dsn('smtp', 'sendgrid', self::USER), + new Dsn('sendgrid+api', 'example.com', self::USER, '', 8080), + (new SendgridApiTransport(self::USER, $this->getClient(), $dispatcher, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('sendgrid', 'default', self::USER), + new SendgridSmtpTransport(self::USER, $dispatcher, $logger), + ]; + + yield [ + new Dsn('sendgrid+smtp', 'default', self::USER), new SendgridSmtpTransport(self::USER, $dispatcher, $logger), ]; yield [ - new Dsn('smtps', 'sendgrid', self::USER), + new Dsn('sendgrid+smtps', 'default', self::USER), new SendgridSmtpTransport(self::USER, $dispatcher, $logger), ]; } @@ -72,13 +87,13 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('foo', 'sendgrid', self::USER), - 'The "foo" scheme is not supported for mailer "sendgrid". Supported schemes are: "api", "smtp", "smtps".', + new Dsn('sendgrid+foo', 'sendgrid', self::USER), + 'The "sendgrid+foo" scheme is not supported. Supported schemes for mailer "sendgrid" are: "sendgrid", "sendgrid+api", "sendgrid+smtp", "sendgrid+smtps".', ]; } public function incompleteDsnProvider(): iterable { - yield [new Dsn('api', 'sendgrid')]; + yield [new Dsn('sendgrid+api', 'default')]; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index 8536b8eb6ce4..c945d96de9af 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -26,7 +26,7 @@ */ class SendgridApiTransport extends AbstractApiTransport { - private const ENDPOINT = 'https://api.sendgrid.com/v3/mail/send'; + private const HOST = 'api.sendgrid.com'; private $key; @@ -39,12 +39,12 @@ public function __construct(string $key, HttpClientInterface $client = null, Eve public function __toString(): string { - return sprintf('api://sendgrid'); + return sprintf('sendgrid+api://%s', $this->getEndpoint()); } protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInterface { - $response = $this->client->request('POST', self::ENDPOINT, [ + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v3/mail/send', [ 'json' => $this->getPayload($email, $envelope), 'auth_bearer' => $this->key, ]); @@ -136,4 +136,9 @@ private function getAttachments(Email $email): array return $attachments; } + + private function getEndpoint(): ?string + { + return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : ''); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridTransportFactory.php index 70d87a08dabf..a4734c7213d7 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridTransportFactory.php @@ -25,19 +25,22 @@ public function create(Dsn $dsn): TransportInterface { $key = $this->getUser($dsn); - if ('api' === $dsn->getScheme()) { - return new SendgridApiTransport($key, $this->client, $this->dispatcher, $this->logger); + if ('sendgrid+api' === $dsn->getScheme()) { + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new SendgridApiTransport($key, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port); } - if ('smtp' === $dsn->getScheme() || 'smtps' === $dsn->getScheme()) { + if ('sendgrid+smtp' === $dsn->getScheme() || 'sendgrid+smtps' === $dsn->getScheme() || 'sendgrid' === $dsn->getScheme()) { return new SendgridSmtpTransport($key, $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['api', 'smtp', 'smtps']); + throw new UnsupportedSchemeException($dsn, 'sendgrid', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'sendgrid' === $dsn->getHost(); + return ['sendgrid', 'sendgrid+api', 'sendgrid+smtp', 'sendgrid+smtps']; } } diff --git a/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php index e67733630cea..165d644cb301 100644 --- a/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php @@ -18,8 +18,8 @@ */ class UnsupportedSchemeException extends LogicException { - public function __construct(Dsn $dsn, array $supported) + public function __construct(Dsn $dsn, string $name, array $supported) { - parent::__construct(sprintf('The "%s" scheme is not supported for mailer "%s". Supported schemes are: "%s".', $dsn->getScheme(), $dsn->getHost(), implode('", "', $supported))); + parent::__construct(sprintf('The "%s" scheme is not supported. Supported schemes for mailer "%s" are: "%s".', $dsn->getScheme(), $name, implode('", "', $supported))); } } diff --git a/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php b/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php index 6c73354bb385..34a264be7d6b 100644 --- a/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php +++ b/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php @@ -69,7 +69,7 @@ public function testCreate(Dsn $dsn, TransportInterface $transport): void $factory = $this->getFactory(); $this->assertEquals($transport, $factory->create($dsn)); - if ('smtp' !== $dsn->getScheme() && 'smtps' !== $dsn->getScheme()) { + if (false !== strpos('smtp', $dsn->getScheme())) { $this->assertStringMatchesFormat($dsn->getScheme().'://%S'.$dsn->getHost().'%S', (string) $transport); } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php index 06248f34b51c..9b39a6140b6c 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php @@ -27,29 +27,16 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('smtp', 'null'), + new Dsn('null', ''), true, ]; - - yield [ - new Dsn('smtp', 'example.com'), - false, - ]; } public function createProvider(): iterable { yield [ - new Dsn('smtp', 'null'), + new Dsn('null', 'null'), new NullTransport($this->getDispatcher(), $this->getLogger()), ]; } - - public function unsupportedSchemeProvider(): iterable - { - yield [ - new Dsn('foo', 'null'), - 'The "foo" scheme is not supported for mailer "null". Supported schemes are: "smtp".', - ]; - } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/NullTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/NullTransportTest.php index c43bf90f9951..34c2a41392b1 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/NullTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/NullTransportTest.php @@ -19,6 +19,6 @@ class NullTransportTest extends TestCase public function testToString() { $t = new NullTransport(); - $this->assertEquals('smtp://null', (string) $t); + $this->assertEquals('null://', (string) $t); } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php index 84d8d92ca74a..078b00b6f5b5 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php @@ -27,20 +27,15 @@ public function getFactory(): TransportFactoryInterface public function supportsProvider(): iterable { yield [ - new Dsn('smtp', 'sendmail'), + new Dsn('sendmail+smtp', 'default'), true, ]; - - yield [ - new Dsn('smtp', 'example.com'), - false, - ]; } public function createProvider(): iterable { yield [ - new Dsn('smtp', 'sendmail'), + new Dsn('sendmail+smtp', 'default'), new SendmailTransport(null, $this->getDispatcher(), $this->getLogger()), ]; } @@ -48,8 +43,8 @@ public function createProvider(): iterable public function unsupportedSchemeProvider(): iterable { yield [ - new Dsn('http', 'sendmail'), - 'The "http" scheme is not supported for mailer "sendmail". Supported schemes are: "smtp".', + new Dsn('sendmail+http', 'default'), + 'The "sendmail+http" scheme is not supported. Supported schemes for mailer "sendmail" are: "sendmail", "sendmail+smtp".', ]; } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php index a3b13b624c42..7dcea33e9648 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php @@ -64,7 +64,7 @@ public function createProvider(): iterable $transport = new EsmtpTransport('example.com', 465, true, $eventDispatcher, $logger); yield [ - new Dsn('smtp', 'example.com', '', '', 465), + new Dsn('smtps', 'example.com', '', '', 465), $transport, ]; } diff --git a/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php index 17deb9fe614c..5480810b0d37 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php @@ -24,6 +24,8 @@ */ abstract class AbstractHttpTransport extends AbstractTransport { + protected $host; + protected $port; protected $client; public function __construct(HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) @@ -40,6 +42,26 @@ public function __construct(HttpClientInterface $client = null, EventDispatcherI parent::__construct($dispatcher, $logger); } + /** + * @return $this + */ + public function setHost(?string $host) + { + $this->host = $host; + + return $this; + } + + /** + * @return $this + */ + public function setPort(?int $port) + { + $this->port = $port; + + return $this; + } + abstract protected function doSendHttp(SentMessage $message): ResponseInterface; protected function doSend(SentMessage $message): void diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php index 959fca574660..17c87224df4a 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php @@ -32,6 +32,13 @@ public function __construct(EventDispatcherInterface $dispatcher = null, HttpCli $this->logger = $logger; } + public function supports(Dsn $dsn): bool + { + return \in_array($dsn->getScheme(), $this->getSupportedSchemes()); + } + + abstract protected function getSupportedSchemes(): array; + protected function getUser(Dsn $dsn): string { $user = $dsn->getUser(); diff --git a/src/Symfony/Component/Mailer/Transport/NullTransport.php b/src/Symfony/Component/Mailer/Transport/NullTransport.php index 71a0958b53d6..92fb82a478cb 100644 --- a/src/Symfony/Component/Mailer/Transport/NullTransport.php +++ b/src/Symfony/Component/Mailer/Transport/NullTransport.php @@ -26,6 +26,6 @@ protected function doSend(SentMessage $message): void public function __toString(): string { - return 'smtp://null'; + return 'null://'; } } diff --git a/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php b/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php index d874e5b583c4..4c45f39e1197 100644 --- a/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php @@ -20,15 +20,15 @@ final class NullTransportFactory extends AbstractTransportFactory { public function create(Dsn $dsn): TransportInterface { - if ('smtp' === $dsn->getScheme()) { + if ('null' === $dsn->getScheme()) { return new NullTransport($this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['smtp']); + throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'null' === $dsn->getHost(); + return ['null']; } } diff --git a/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php b/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php index 5e89a2070e06..77d6d49a4a46 100644 --- a/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php @@ -20,15 +20,15 @@ final class SendmailTransportFactory extends AbstractTransportFactory { public function create(Dsn $dsn): TransportInterface { - if ('smtp' === $dsn->getScheme()) { + if ('sendmail+smtp' === $dsn->getScheme() || 'sendmail' === $dsn->getScheme()) { return new SendmailTransport(null, $this->dispatcher, $this->logger); } - throw new UnsupportedSchemeException($dsn, ['smtp']); + throw new UnsupportedSchemeException($dsn, 'sendmail', $this->getSupportedSchemes()); } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'sendmail' === $dsn->getHost(); + return ['sendmail', 'sendmail+smtp']; } } diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php index a1438f91f49f..6613145f68f8 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php @@ -39,8 +39,8 @@ public function create(Dsn $dsn): TransportInterface return $transport; } - public function supports(Dsn $dsn): bool + protected function getSupportedSchemes(): array { - return 'smtp' === $dsn->getScheme() || 'smtps' === $dsn->getScheme(); + return ['smtp', 'smtps']; } }