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']; } }
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: