refactor: unify response-code parsing

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-09-25 11:54:28 -04:00
parent 09d0bbd817
commit 68ca777150
3 changed files with 51 additions and 26 deletions
+13 -4
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Command; namespace KTXM\ProviderImap\Client\Protocol\Command;
use KTXM\ProviderImap\Client\Protocol\Parser\ResponseCodeParser;
use KTXM\ProviderImap\Client\Protocol\StringEncoder; use KTXM\ProviderImap\Client\Protocol\StringEncoder;
use KTXM\ProviderImap\Client\ImapException; use KTXM\ProviderImap\Client\ImapException;
use KTXM\ProviderImap\Client\Protocol\RequestFrame; use KTXM\ProviderImap\Client\Protocol\RequestFrame;
@@ -26,6 +27,7 @@ use KTXM\ProviderImap\Client\Protocol\SessionState;
*/ */
final class AppendCommand implements CommandInterface final class AppendCommand implements CommandInterface
{ {
private readonly ResponseCodeParser $responseCodeParser;
private readonly string $literal; private readonly string $literal;
/** /**
@@ -36,6 +38,8 @@ final class AppendCommand implements CommandInterface
string $message, string $message,
private readonly array $flags = [], private readonly array $flags = [],
) { ) {
$this->responseCodeParser = new ResponseCodeParser();
// IMAP literals are octet-counted; normalise to CRLF line endings. // IMAP literals are octet-counted; normalise to CRLF line endings.
$this->literal = (string) preg_replace('/\r\n|\r|\n/', "\r\n", $message); $this->literal = (string) preg_replace('/\r\n|\r|\n/', "\r\n", $message);
} }
@@ -87,13 +91,18 @@ final class AppendCommand implements CommandInterface
throw new ImapException('APPEND did not receive a tagged completion response.'); throw new ImapException('APPEND did not receive a tagged completion response.');
} }
private function parseAppendUid(string $text): ?int private function parseAppendUid(string $text): ?int
{ {
if (preg_match('/\[APPENDUID\s+\d+\s+(\d+)\]/i', $text, $matches) === 1) { $responseCode = $this->responseCodeParser->parse($text);
return (int) $matches[1]; if ($responseCode === null || $responseCode['name'] !== 'APPENDUID') {
return null;
} }
return null; $arguments = $responseCode['arguments'];
if (count($arguments) !== 2 || !ctype_digit($arguments[0]) || !ctype_digit($arguments[1])) {
return null;
}
return (int) $arguments[1];
} }
} }
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Command; namespace KTXM\ProviderImap\Client\Protocol\Command;
use KTXM\ProviderImap\Client\Protocol\Parser\ResponseCodeParser;
use KTXM\ProviderImap\Client\Protocol\StringEncoder; use KTXM\ProviderImap\Client\Protocol\StringEncoder;
use KTXM\ProviderImap\Client\Result\MessageTransferResult; use KTXM\ProviderImap\Client\Result\MessageTransferResult;
use KTXM\ProviderImap\Client\Protocol\Command\Argument\MessageTarget; use KTXM\ProviderImap\Client\Protocol\Command\Argument\MessageTarget;
@@ -22,6 +23,7 @@ use KTXM\ProviderImap\Client\Protocol\SessionState;
*/ */
final class MessageTransferCommand implements CommandInterface final class MessageTransferCommand implements CommandInterface
{ {
private readonly ResponseCodeParser $responseCodeParser;
private readonly string $operation; private readonly string $operation;
private readonly SequenceSet $sequenceSet; private readonly SequenceSet $sequenceSet;
private readonly IdentifierMode $identifierMode; private readonly IdentifierMode $identifierMode;
@@ -31,6 +33,8 @@ final class MessageTransferCommand implements CommandInterface
MessageTarget|string|SequenceSet|null $target = null, MessageTarget|string|SequenceSet|null $target = null,
private readonly string $destinationMailbox = '', private readonly string $destinationMailbox = '',
) { ) {
$this->responseCodeParser = new ResponseCodeParser();
$resolvedTarget = match (true) { $resolvedTarget = match (true) {
$target instanceof MessageTarget => $target, $target instanceof MessageTarget => $target,
$target instanceof SequenceSet => MessageTarget::sequence($target), $target instanceof SequenceSet => MessageTarget::sequence($target),
@@ -184,7 +188,7 @@ final class MessageTransferCommand implements CommandInterface
bool &$tryCreate, bool &$tryCreate,
?string &$highestModSeq, ?string &$highestModSeq,
): void { ): void {
$responseCode = $this->parseResponseCode($text); $responseCode = $this->responseCodeParser->parse($text);
if ($responseCode === null) { if ($responseCode === null) {
return; return;
} }
@@ -216,24 +220,4 @@ final class MessageTransferCommand implements CommandInterface
'destinationUids' => $responseCode['arguments'][2], 'destinationUids' => $responseCode['arguments'][2],
]; ];
} }
}
/**
* @return ?array{name:string, arguments:list<string>, text:string}
*/
private function parseResponseCode(string $text): ?array
{
$text = trim($text);
if (preg_match('/^\[([A-Z0-9.-]+)(?:\s+([^\]]+))?\](?:\s*(.*))?$/i', $text, $matches) !== 1) {
return null;
}
$arguments = trim($matches[2] ?? '');
return [
'name' => strtoupper($matches[1]),
'arguments' => $arguments === '' ? [] : (preg_split('/\s+/', $arguments) ?: []),
'text' => trim($matches[3] ?? ''),
];
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Parser;
/**
* Parses the optional leading bracketed code in response text.
* Command-specific argument interpretation belongs to the caller.
*/
final class ResponseCodeParser
{
/**
* @return ?array{name:string, arguments:list<string>, text:string}
*/
public function parse(string $text): ?array
{
$text = trim($text);
if (preg_match('/^\[([A-Z0-9.-]+)(?:\s+([^\]]+))?\](?:\s*(.*))?$/i', $text, $matches) !== 1) {
return null;
}
$arguments = trim($matches[2] ?? '');
return [
'name' => strtoupper($matches[1]),
'arguments' => $arguments === '' ? [] : (preg_split('/\s+/', $arguments) ?: []),
'text' => trim($matches[3] ?? ''),
];
}
}