Files
provider_imap/lib/Client/Protocol/Imap.php
Sebastian Krupinski e51c65bf19 feat: initial version
Signed-off-by: Sebastian Krupinski <root@LAPTOP-7DVOR6NC>
2026-02-20 21:44:49 +00:00

129 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol;
use Generator;
use Gricob\IMAP\Protocol\Command\Command;
use Gricob\IMAP\Protocol\Command\StartTlsCommand;
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\Transport\Connection;
use RuntimeException;
class Imap
{
protected Connection $connection;
private TagGenerator $tagGenerator;
private ResponseHandler $responseHandler;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->tagGenerator = new TagGenerator();
$this->responseHandler = new ResponseHandler(new Parser());
}
public function __destruct()
{
$this->disconnect();
}
public function connect(): void
{
if ($this->connection->isOpen()) {
return;
}
$this->connection->open();
$responseStream = $this->connection->receive();
$greeting = $this->responseHandler->handle('*', $responseStream, new UnexpectedContinuationHandler());
match ($greeting->status->type) {
StatusType::OK => null, // Do nothing
StatusType::PREAUTH => throw new RuntimeException('pre-auth is not supported'),
StatusType::BAD,
StatusType::NO,
StatusType::BYE => throw new ConnectionRejected($greeting->status->message),
};
}
public function disconnect(): void
{
$this->connection->close();
}
/**
* Perform STARTTLS negotiation (patch).
*
* Sends the STARTTLS command and upgrades the underlying socket to TLS.
* The connection must be a SocketConnection (or any Connection that
* implements upgradeTls()). Call this after connect() but before logIn().
*
* @throws \RuntimeException if the server rejects STARTTLS
* @throws \BadMethodCallException if the connection does not support TLS upgrade
*/
public function startTls(): void
{
if (!method_exists($this->connection, 'upgradeTls')) {
throw new \BadMethodCallException(
'The current Connection implementation does not support STARTTLS upgrade'
);
}
$response = $this->send(new StartTlsCommand());
if ($response->status->type !== StatusType::OK) {
throw new \RuntimeException(
'Server rejected STARTTLS: ' . $response->status->message
);
}
$this->connection->upgradeTls();
}
public function send(Command $command): Response
{
$interaction = new CommandInteraction(
$this->connection,
$this->responseHandler,
$this->tagGenerator->next(),
$command,
);
$response = $interaction->interact();
if ($response->status->type != StatusType::OK) {
throw CommandFailed::withStatus($response->status);
}
return $response;
}
/**
* Sends $command and returns a Generator that yields each untagged Line as
* it arrives from the socket. CommandFailed is thrown (inside the generator)
* if the server responds with NO or BAD.
*
* @return Generator<int, Line, mixed, Status>
*/
public function sendStreaming(Command $command): Generator
{
$this->connect();
$interaction = new CommandInteraction(
$this->connection,
$this->responseHandler,
$this->tagGenerator->next(),
$command,
);
yield from $interaction->streamInteract();
}
}