feat: initial version

Signed-off-by: Sebastian Krupinski <root@LAPTOP-7DVOR6NC>
This commit was merged in pull request #1.
This commit is contained in:
Sebastian Krupinski
2026-02-20 16:41:19 -05:00
committed by Sebastian Krupinski
parent a313767846
commit e51c65bf19
139 changed files with 11256 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol;
use Generator;
use Gricob\IMAP\Protocol\Response\Line\CommandContinuation;
use Gricob\IMAP\Protocol\Response\Line\Line;
use Gricob\IMAP\Protocol\Response\Line\Status\Status;
use Gricob\IMAP\Protocol\Response\Line\Status\StatusType;
use Gricob\IMAP\Protocol\Response\Parser\Parser;
use Gricob\IMAP\Protocol\Response\Response;
use Gricob\IMAP\Protocol\Response\ResponseBuilder;
use Gricob\IMAP\Transport\ResponseStream;
use RuntimeException;
readonly class ResponseHandler
{
public function __construct(private Parser $parser)
{
}
public function handle(string $statusTag, ResponseStream $stream, ContinuationHandler $continuationHandler): Response
{
$responseBuilder = new ResponseBuilder($statusTag);
do {
$raw = $stream->readLine();
while (preg_match('/\{(?<bytes>\d+)}\r\n$/', $raw, $matches)) {
$raw .= $stream->read((int) $matches['bytes']);
$raw .= $stream->readLine();
}
$line = $this->parser->parse($raw);
if ($line instanceof CommandContinuation) {
$continuationHandler->continue();
continue;
}
$responseBuilder->addLine($line);
} while (!$responseBuilder->hasStatus());
return $responseBuilder->build();
}
/**
* Streams parsed response lines one at a time as a Generator, yielding each
* untagged Line immediately as it arrives from the socket. The terminal
* Status line is NOT yielded; instead it is set as the generator return
* value so callers can retrieve it via $gen->getReturn() after exhaustion.
*
* @throws CommandFailed if the tagged status is NO or BAD
*
* @return Generator<int, Line, mixed, Status>
*/
public function stream(string $statusTag, ResponseStream $stream, ContinuationHandler $continuationHandler): Generator
{
$status = null;
do {
$raw = $stream->readLine();
while (preg_match('/\{(?<bytes>\d+)}\r\n$/', $raw, $matches)) {
$raw .= $stream->read((int) $matches['bytes']);
$raw .= $stream->readLine();
}
$line = $this->parser->parse($raw);
if ($line instanceof CommandContinuation) {
$continuationHandler->continue();
continue;
}
if ($line instanceof Status && $line->tag === $statusTag) {
$status = $line;
break;
}
yield $line;
} while (true);
if ($status->type !== StatusType::OK) {
throw CommandFailed::withStatus($status);
}
return $status;
}
}