From 67a0dbbee7b63e604c7476b6741e95348d4f872a Mon Sep 17 00:00:00 2001 From: Sebastian Krupinski Date: Tue, 23 Jun 2026 16:51:54 -0400 Subject: [PATCH] refactor: smtp client Signed-off-by: Sebastian Krupinski --- lib/Providers/Service.php | 2 +- lib/Smtp/Client.php | 46 +++++++++---------- .../{ReplyReader.php => ProtocolReader.php} | 7 +-- .../{CommandWriter.php => ProtocolWriter.php} | 2 +- .../{Reply.php => Response/Response.php} | 4 +- lib/Smtp/SmtpCapabilities.php | 4 +- 6 files changed, 33 insertions(+), 32 deletions(-) rename lib/Smtp/Protocol/{ReplyReader.php => ProtocolReader.php} (90%) rename lib/Smtp/Protocol/{CommandWriter.php => ProtocolWriter.php} (98%) rename lib/Smtp/Protocol/{Reply.php => Response/Response.php} (95%) diff --git a/lib/Providers/Service.php b/lib/Providers/Service.php index 360a601..4c6982b 100644 --- a/lib/Providers/Service.php +++ b/lib/Providers/Service.php @@ -644,7 +644,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC } $this->initialize(); - $smtp = $this->remoteService->submitClient(); + $smtp = $this->remoteService->smtpClient(); try { $queueId = $smtp->send(trim($sender->getAddress()), $recipients, $raw); } finally { diff --git a/lib/Smtp/Client.php b/lib/Smtp/Client.php index b546201..65d06ce 100644 --- a/lib/Smtp/Client.php +++ b/lib/Smtp/Client.php @@ -10,9 +10,9 @@ declare(strict_types=1); namespace KTXM\ProviderImap\Smtp; use KTXM\ProviderImap\Smtp\Auth\AuthMechanism; -use KTXM\ProviderImap\Smtp\Protocol\CommandWriter; -use KTXM\ProviderImap\Smtp\Protocol\Reply; -use KTXM\ProviderImap\Smtp\Protocol\ReplyReader; +use KTXM\ProviderImap\Smtp\Protocol\ProtocolWriter; +use KTXM\ProviderImap\Smtp\Protocol\Response\Response; +use KTXM\ProviderImap\Smtp\Protocol\ProtocolReader; use KTXM\ProviderImap\Smtp\Transport\ConnectionFactoryInterface; use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface; use KTXM\ProviderImap\Smtp\Transport\SocketConnectionFactory; @@ -30,8 +30,8 @@ use Psr\Log\LoggerInterface; final class Client { private ?ConnectionInterface $connection = null; - private ?ReplyReader $reader = null; - private ?CommandWriter $writer = null; + private ?ProtocolReader $reader = null; + private ?ProtocolWriter $writer = null; private ?ConnectionConfig $config = null; private ?SmtpCapabilities $capabilities = null; @@ -47,8 +47,8 @@ final class Client $this->connection = $connection; $this->config = $config; - $this->reader = new ReplyReader($connection, $this->logger); - $this->writer = new CommandWriter($connection, $this->logger); + $this->reader = new ProtocolReader($connection, $this->logger); + $this->writer = new ProtocolWriter($connection, $this->logger); // Server greeting. $greeting = $this->reader()->read(); @@ -101,17 +101,17 @@ final class Client } $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) { $this->command( sprintf('RCPT TO:<%s>', $recipient), - static fn (Reply $r): bool => $r->isPositiveCompletion(), + static fn (Response $r): bool => $r->isPositiveCompletion(), '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"); $reply = $this->reader()->read(); @@ -161,7 +161,7 @@ final class Client 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(); } @@ -185,7 +185,7 @@ final class Client { $this->command( 'AUTH PLAIN ' . AuthMechanism::plainToken($username, $password), - static fn (Reply $r): bool => $r->isPositiveCompletion(), + static fn (Response $r): bool => $r->isPositiveCompletion(), 'AUTH PLAIN', true, ); @@ -193,27 +193,27 @@ final class Client 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); - $userReply = $this->reader()->read(); - if (!$userReply->isPositiveIntermediate()) { - throw new SmtpException('SMTP AUTH LOGIN username rejected: ' . $userReply->text()); + $userResponse = $this->reader()->read(); + if (!$userResponse->isPositiveIntermediate()) { + throw new SmtpException('SMTP AUTH LOGIN username rejected: ' . $userResponse->text()); } $this->writer()->writeLine(base64_encode($password), true); - $passReply = $this->reader()->read(); - if (!$passReply->isPositiveCompletion()) { - throw new SmtpException('SMTP authentication failed: ' . $passReply->text()); + $passResponse = $this->reader()->read(); + if (!$passResponse->isPositiveCompletion()) { + throw new SmtpException('SMTP authentication failed: ' . $passResponse->text()); } } /** * 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); $reply = $this->reader()->read(); @@ -242,12 +242,12 @@ final class Client 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.'); } - private function writer(): CommandWriter + private function writer(): ProtocolWriter { return $this->writer ?? throw new SmtpException('SMTP client is not connected.'); } diff --git a/lib/Smtp/Protocol/ReplyReader.php b/lib/Smtp/Protocol/ProtocolReader.php similarity index 90% rename from lib/Smtp/Protocol/ReplyReader.php rename to lib/Smtp/Protocol/ProtocolReader.php index db9f37c..7a8ec6e 100644 --- a/lib/Smtp/Protocol/ReplyReader.php +++ b/lib/Smtp/Protocol/ProtocolReader.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace KTXM\ProviderImap\Smtp\Protocol; +use KTXM\ProviderImap\Smtp\Protocol\Response\Response; use KTXM\ProviderImap\Smtp\SmtpException; use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface; 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 * line. See RFC 5321 ยง4.2. */ -final class ReplyReader +final class ProtocolReader { public function __construct( private readonly ConnectionInterface $connection, private readonly ?LoggerInterface $logger = null, ) {} - public function read(): Reply + public function read(): Response { $lines = []; $raw = ''; @@ -63,6 +64,6 @@ final class ReplyReader '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")); } } diff --git a/lib/Smtp/Protocol/CommandWriter.php b/lib/Smtp/Protocol/ProtocolWriter.php similarity index 98% rename from lib/Smtp/Protocol/CommandWriter.php rename to lib/Smtp/Protocol/ProtocolWriter.php index 440bc1a..1299667 100644 --- a/lib/Smtp/Protocol/CommandWriter.php +++ b/lib/Smtp/Protocol/ProtocolWriter.php @@ -12,7 +12,7 @@ namespace KTXM\ProviderImap\Smtp\Protocol; use KTXM\ProviderImap\Smtp\Transport\ConnectionInterface; use Psr\Log\LoggerInterface; -final class CommandWriter +final class ProtocolWriter { public function __construct( private readonly ConnectionInterface $connection, diff --git a/lib/Smtp/Protocol/Reply.php b/lib/Smtp/Protocol/Response/Response.php similarity index 95% rename from lib/Smtp/Protocol/Reply.php rename to lib/Smtp/Protocol/Response/Response.php index 03f3305..ff873eb 100644 --- a/lib/Smtp/Protocol/Reply.php +++ b/lib/Smtp/Protocol/Response/Response.php @@ -7,12 +7,12 @@ declare(strict_types=1); * 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. */ -final class Reply +final class Response { /** * @param int $code 3-digit SMTP status code diff --git a/lib/Smtp/SmtpCapabilities.php b/lib/Smtp/SmtpCapabilities.php index a28e535..29b2b9c 100644 --- a/lib/Smtp/SmtpCapabilities.php +++ b/lib/Smtp/SmtpCapabilities.php @@ -9,7 +9,7 @@ declare(strict_types=1); namespace KTXM\ProviderImap\Smtp; -use KTXM\ProviderImap\Smtp\Protocol\Reply; +use KTXM\ProviderImap\Smtp\Protocol\Response\Response; /** * Parsed EHLO capabilities. @@ -27,7 +27,7 @@ final class SmtpCapabilities private readonly array $keywords, ) {} - public static function fromEhlo(Reply $reply): self + public static function fromEhlo(Response $reply): self { $keywords = []; $lines = $reply->lines();