Compare commits

...

2 Commits

Author SHA1 Message Date
Molkobain
3063a79612 N°9584 - Refactor EMailSymfony transport to add unit tests 2026-04-30 21:14:00 +02:00
Molkobain
56d8e3d0a7 N°9584 - Fix "Unable to connect with STARTTLS" error when sending emails 2026-04-30 19:28:30 +02:00
2 changed files with 93 additions and 12 deletions

View File

@@ -29,6 +29,7 @@ use Symfony\Component\CssSelector\Exception\ParseException;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Mime\Email as SymfonyEmail;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
@@ -183,18 +184,7 @@ class EMailSymfony extends Email
$sDsn = sprintf('smtp://%s%s@%s%s', $sDsnUser, $sDsnPassword, $sDsnPort, $sEncQuery);
}
$oTransport = Transport::fromDsn($sDsn);
// Handle peer verification
$oStream = $oTransport->getStream();
$aOptions = $oStream->getStreamOptions();
if (!$bVerifyPeer && array_key_exists('ssl', $aOptions)) {
// Disable verification
$aOptions['ssl']['verify_peer'] = false;
$aOptions['ssl']['verify_peer_name'] = false;
$aOptions['ssl']['allow_self_signed'] = true;
}
$oStream->setStreamOptions($aOptions);
$oTransport = $this->CreateSmtpTransport($sDsn, $bVerifyPeer);
$oMailer = new Mailer($oTransport);
break;
@@ -260,6 +250,36 @@ class EMailSymfony extends Email
}
}
/**
* Build and configure an SMTP transport from a DSN string.
*
* Extracted from {@see SendSynchronous} to make SSL option handling independently testable.
* When $bVerifyPeer is false, the ssl stream context options must be written unconditionally:
* with STARTTLS the connection starts unencrypted, so the 'ssl' key is absent from the stream
* options at construction time and only used later when stream_socket_enable_crypto() is called.
*
* @param string $sDsn Full Symfony Mailer DSN (smtp:// or smtps://)
* @param bool $bVerifyPeer Whether to verify the peer SSL certificate
*
* @return \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport
*/
protected function CreateSmtpTransport(string $sDsn, bool $bVerifyPeer): EsmtpTransport
{
/** @var EsmtpTransport $oTransport */
$oTransport = Transport::fromDsn($sDsn);
$oStream = $oTransport->getStream();
$aOptions = $oStream->getStreamOptions();
if (!$bVerifyPeer) {
$aOptions['ssl']['verify_peer'] = false;
$aOptions['ssl']['verify_peer_name'] = false;
$aOptions['ssl']['allow_self_signed'] = true;
}
$oStream->setStreamOptions($aOptions);
return $oTransport;
}
/**
* Reprocess the body of the message (if it is an HTML message)
* to replace the URL of images based on attachments by a link

View File

@@ -1,6 +1,8 @@
<?php
use Combodo\iTop\Core\Email\EMailSymfony;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
class EmailSymfonyTest extends ItopTestCase
{
@@ -135,4 +137,63 @@ HTML;
$this->assertSame($sExpectedBody, $sActualBody);
}
/**
* @dataProvider provideCreateSmtpTransportSslOptions
*/
public function testCreateSmtpTransportSslOptions(string $sDsn, bool $bVerifyPeer, array $aExpectedSslOptions): void
{
$oEmail = new EMailSymfony();
/** @var EsmtpTransport $oTransport */
$oTransport = $this->InvokeNonPublicMethod(EMailSymfony::class, 'CreateSmtpTransport', $oEmail, [$sDsn, $bVerifyPeer]);
$aActualSslOptions = $oTransport->getStream()->getStreamOptions()['ssl'] ?? [];
$this->assertSame($aExpectedSslOptions, $aActualSslOptions);
}
public function provideCreateSmtpTransportSslOptions(): array
{
$aDisabledVerification = [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
];
return [
// Regression scenario (N°9584): STARTTLS starts the connection unencrypted, so the 'ssl' key
// is absent from stream options at construction time. verify_peer=false must still be applied.
'STARTTLS, verify_peer=false' => [
'smtp://localhost:587?encryption=starttls',
false,
$aDisabledVerification,
],
'implicit TLS (smtps), verify_peer=false' => [
'smtps://localhost:465',
false,
$aDisabledVerification,
],
'plain SMTP, verify_peer=false' => [
'smtp://localhost:25',
false,
$aDisabledVerification,
],
// Default behavior: verify_peer=true must leave stream options untouched (empty).
'STARTTLS, verify_peer=true (default)' => [
'smtp://localhost:587?encryption=starttls',
true,
[],
],
'implicit TLS (smtps), verify_peer=true (default)' => [
'smtps://localhost:465',
true,
[],
],
'plain SMTP, verify_peer=true (default)' => [
'smtp://localhost:25',
true,
[],
],
];
}
}