generated from Nodarx/template
refactor: smtp client
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -644,7 +644,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
$smtp = $this->remoteService->submitClient();
|
$smtp = $this->remoteService->smtpClient();
|
||||||
try {
|
try {
|
||||||
$queueId = $smtp->send(trim($sender->getAddress()), $recipients, $raw);
|
$queueId = $smtp->send(trim($sender->getAddress()), $recipients, $raw);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+23
-23
@@ -10,9 +10,9 @@ declare(strict_types=1);
|
|||||||
namespace KTXM\ProviderImap\Smtp;
|
namespace KTXM\ProviderImap\Smtp;
|
||||||
|
|
||||||
use KTXM\ProviderImap\Smtp\Auth\AuthMechanism;
|
use KTXM\ProviderImap\Smtp\Auth\AuthMechanism;
|
||||||
use KTXM\ProviderImap\Smtp\Protocol\CommandWriter;
|
use KTXM\ProviderImap\Smtp\Protocol\ProtocolWriter;
|
||||||
use KTXM\ProviderImap\Smtp\Protocol\Reply;
|
use KTXM\ProviderImap\Smtp\Protocol\Response\Response;
|
||||||
use KTXM\ProviderImap\Smtp\Protocol\ReplyReader;
|
use KTXM\ProviderImap\Smtp\Protocol\ProtocolReader;
|
||||||
use KTXM\ProviderImap\Smtp\Transport\ConnectionFactoryInterface;
|
use KTXM\ProviderImap\Smtp\Transport\ConnectionFactoryInterface;
|
||||||
use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface;
|
use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface;
|
||||||
use KTXM\ProviderImap\Smtp\Transport\SocketConnectionFactory;
|
use KTXM\ProviderImap\Smtp\Transport\SocketConnectionFactory;
|
||||||
@@ -30,8 +30,8 @@ use Psr\Log\LoggerInterface;
|
|||||||
final class Client
|
final class Client
|
||||||
{
|
{
|
||||||
private ?ConnectionInterface $connection = null;
|
private ?ConnectionInterface $connection = null;
|
||||||
private ?ReplyReader $reader = null;
|
private ?ProtocolReader $reader = null;
|
||||||
private ?CommandWriter $writer = null;
|
private ?ProtocolWriter $writer = null;
|
||||||
private ?ConnectionConfig $config = null;
|
private ?ConnectionConfig $config = null;
|
||||||
private ?SmtpCapabilities $capabilities = null;
|
private ?SmtpCapabilities $capabilities = null;
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ final class Client
|
|||||||
|
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->reader = new ReplyReader($connection, $this->logger);
|
$this->reader = new ProtocolReader($connection, $this->logger);
|
||||||
$this->writer = new CommandWriter($connection, $this->logger);
|
$this->writer = new ProtocolWriter($connection, $this->logger);
|
||||||
|
|
||||||
// Server greeting.
|
// Server greeting.
|
||||||
$greeting = $this->reader()->read();
|
$greeting = $this->reader()->read();
|
||||||
@@ -101,17 +101,17 @@ final class Client
|
|||||||
}
|
}
|
||||||
$mailFrom .= ' SIZE=' . $size;
|
$mailFrom .= ' SIZE=' . $size;
|
||||||
}
|
}
|
||||||
$this->command($mailFrom, static fn (Reply $r): bool => $r->isPositiveCompletion(), 'MAIL FROM');
|
$this->command($mailFrom, static fn (Response $r): bool => $r->isPositiveCompletion(), 'MAIL FROM');
|
||||||
|
|
||||||
foreach ($recipients as $recipient) {
|
foreach ($recipients as $recipient) {
|
||||||
$this->command(
|
$this->command(
|
||||||
sprintf('RCPT TO:<%s>', $recipient),
|
sprintf('RCPT TO:<%s>', $recipient),
|
||||||
static fn (Reply $r): bool => $r->isPositiveCompletion(),
|
static fn (Response $r): bool => $r->isPositiveCompletion(),
|
||||||
'RCPT TO',
|
'RCPT TO',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->command('DATA', static fn (Reply $r): bool => $r->isPositiveIntermediate(), 'DATA');
|
$this->command('DATA', static fn (Response $r): bool => $r->isPositiveIntermediate(), 'DATA');
|
||||||
|
|
||||||
$this->writer()->writeRaw($this->dotStuff($rawMessage) . "\r\n.\r\n");
|
$this->writer()->writeRaw($this->dotStuff($rawMessage) . "\r\n.\r\n");
|
||||||
$reply = $this->reader()->read();
|
$reply = $this->reader()->read();
|
||||||
@@ -161,7 +161,7 @@ final class Client
|
|||||||
throw new SmtpException('SMTP server does not advertise STARTTLS.');
|
throw new SmtpException('SMTP server does not advertise STARTTLS.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->command('STARTTLS', static fn (Reply $r): bool => $r->isPositiveCompletion(), 'STARTTLS');
|
$this->command('STARTTLS', static fn (Response $r): bool => $r->isPositiveCompletion(), 'STARTTLS');
|
||||||
$this->connection()->upgradeToTls();
|
$this->connection()->upgradeToTls();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ final class Client
|
|||||||
{
|
{
|
||||||
$this->command(
|
$this->command(
|
||||||
'AUTH PLAIN ' . AuthMechanism::plainToken($username, $password),
|
'AUTH PLAIN ' . AuthMechanism::plainToken($username, $password),
|
||||||
static fn (Reply $r): bool => $r->isPositiveCompletion(),
|
static fn (Response $r): bool => $r->isPositiveCompletion(),
|
||||||
'AUTH PLAIN',
|
'AUTH PLAIN',
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@@ -193,27 +193,27 @@ final class Client
|
|||||||
|
|
||||||
private function authLogin(string $username, string $password): void
|
private function authLogin(string $username, string $password): void
|
||||||
{
|
{
|
||||||
$this->command('AUTH LOGIN', static fn (Reply $r): bool => $r->isPositiveIntermediate(), 'AUTH LOGIN');
|
$this->command('AUTH LOGIN', static fn (Response $r): bool => $r->isPositiveIntermediate(), 'AUTH LOGIN');
|
||||||
|
|
||||||
$this->writer()->writeLine(base64_encode($username), true);
|
$this->writer()->writeLine(base64_encode($username), true);
|
||||||
$userReply = $this->reader()->read();
|
$userResponse = $this->reader()->read();
|
||||||
if (!$userReply->isPositiveIntermediate()) {
|
if (!$userResponse->isPositiveIntermediate()) {
|
||||||
throw new SmtpException('SMTP AUTH LOGIN username rejected: ' . $userReply->text());
|
throw new SmtpException('SMTP AUTH LOGIN username rejected: ' . $userResponse->text());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->writer()->writeLine(base64_encode($password), true);
|
$this->writer()->writeLine(base64_encode($password), true);
|
||||||
$passReply = $this->reader()->read();
|
$passResponse = $this->reader()->read();
|
||||||
if (!$passReply->isPositiveCompletion()) {
|
if (!$passResponse->isPositiveCompletion()) {
|
||||||
throw new SmtpException('SMTP authentication failed: ' . $passReply->text());
|
throw new SmtpException('SMTP authentication failed: ' . $passResponse->text());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue a command line and assert the reply satisfies $accept.
|
* Issue a command line and assert the reply satisfies $accept.
|
||||||
*
|
*
|
||||||
* @param callable(Reply):bool $accept
|
* @param callable(Response):bool $accept
|
||||||
*/
|
*/
|
||||||
private function command(string $line, callable $accept, string $label, bool $sensitive = false): Reply
|
private function command(string $line, callable $accept, string $label, bool $sensitive = false): Response
|
||||||
{
|
{
|
||||||
$this->writer()->writeLine($line, $sensitive);
|
$this->writer()->writeLine($line, $sensitive);
|
||||||
$reply = $this->reader()->read();
|
$reply = $this->reader()->read();
|
||||||
@@ -242,12 +242,12 @@ final class Client
|
|||||||
return $this->connection ?? throw new SmtpException('SMTP client is not connected.');
|
return $this->connection ?? throw new SmtpException('SMTP client is not connected.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function reader(): ReplyReader
|
private function reader(): ProtocolReader
|
||||||
{
|
{
|
||||||
return $this->reader ?? throw new SmtpException('SMTP client is not connected.');
|
return $this->reader ?? throw new SmtpException('SMTP client is not connected.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function writer(): CommandWriter
|
private function writer(): ProtocolWriter
|
||||||
{
|
{
|
||||||
return $this->writer ?? throw new SmtpException('SMTP client is not connected.');
|
return $this->writer ?? throw new SmtpException('SMTP client is not connected.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderImap\Smtp\Protocol;
|
namespace KTXM\ProviderImap\Smtp\Protocol;
|
||||||
|
|
||||||
|
use KTXM\ProviderImap\Smtp\Protocol\Response\Response;
|
||||||
use KTXM\ProviderImap\Smtp\SmtpException;
|
use KTXM\ProviderImap\Smtp\SmtpException;
|
||||||
use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface;
|
use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
@@ -20,14 +21,14 @@ use Psr\Log\LoggerInterface;
|
|||||||
* ("250-text") to indicate continuation and a space ("250 text") on the final
|
* ("250-text") to indicate continuation and a space ("250 text") on the final
|
||||||
* line. See RFC 5321 §4.2.
|
* line. See RFC 5321 §4.2.
|
||||||
*/
|
*/
|
||||||
final class ReplyReader
|
final class ProtocolReader
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ConnectionInterface $connection,
|
private readonly ConnectionInterface $connection,
|
||||||
private readonly ?LoggerInterface $logger = null,
|
private readonly ?LoggerInterface $logger = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function read(): Reply
|
public function read(): Response
|
||||||
{
|
{
|
||||||
$lines = [];
|
$lines = [];
|
||||||
$raw = '';
|
$raw = '';
|
||||||
@@ -63,6 +64,6 @@ final class ReplyReader
|
|||||||
'raw' => rtrim($raw, "\r\n"),
|
'raw' => rtrim($raw, "\r\n"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return new Reply($code ?? 0, $lines, rtrim($raw, "\r\n"));
|
return new Response($code ?? 0, $lines, rtrim($raw, "\r\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace KTXM\ProviderImap\Smtp\Protocol;
|
|||||||
use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface;
|
use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
final class CommandWriter
|
final class ProtocolWriter
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ConnectionInterface $connection,
|
private readonly ConnectionInterface $connection,
|
||||||
@@ -7,12 +7,12 @@ declare(strict_types=1);
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace KTXM\ProviderImap\Smtp\Protocol;
|
namespace KTXM\ProviderImap\Smtp\Protocol\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parsed SMTP reply: a 3-digit status code plus one or more text lines.
|
* A parsed SMTP reply: a 3-digit status code plus one or more text lines.
|
||||||
*/
|
*/
|
||||||
final class Reply
|
final class Response
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param int $code 3-digit SMTP status code
|
* @param int $code 3-digit SMTP status code
|
||||||
@@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderImap\Smtp;
|
namespace KTXM\ProviderImap\Smtp;
|
||||||
|
|
||||||
use KTXM\ProviderImap\Smtp\Protocol\Reply;
|
use KTXM\ProviderImap\Smtp\Protocol\Response\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsed EHLO capabilities.
|
* Parsed EHLO capabilities.
|
||||||
@@ -27,7 +27,7 @@ final class SmtpCapabilities
|
|||||||
private readonly array $keywords,
|
private readonly array $keywords,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static function fromEhlo(Reply $reply): self
|
public static function fromEhlo(Response $reply): self
|
||||||
{
|
{
|
||||||
$keywords = [];
|
$keywords = [];
|
||||||
$lines = $reply->lines();
|
$lines = $reply->lines();
|
||||||
|
|||||||
Reference in New Issue
Block a user