refactor: use custom imap client

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-05-08 00:16:43 -04:00
parent a728aeb11c
commit a8764747fd
179 changed files with 6782 additions and 5907 deletions

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Response;
final class ContinuationResponse implements ResponseInterface
{
public function __construct(
private readonly string $text,
private readonly string $raw,
) {}
public function text(): string
{
return $this->text;
}
public function raw(): string
{
return $this->raw;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Response;
final class GreetingResponse implements ResponseInterface
{
public function __construct(
private readonly string $status,
private readonly string $text,
private readonly string $raw,
) {}
public function status(): string
{
return $this->status;
}
public function text(): string
{
return $this->text;
}
public function raw(): string
{
return $this->raw;
}
}

View File

@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line;
final readonly class CommandContinuation implements Line
{
public function __construct(
public string $message,
) {
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final readonly class CapabilityData implements Data
{
/**
* @param list<string> $capabilities
*/
public function __construct(public array $capabilities)
{
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
use Gricob\IMAP\Protocol\Response\Line\Line;
interface Data extends Line
{
}

View File

@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final readonly class ExistsData implements Data
{
public function __construct(public int $numberOfMessages)
{
}
}

View File

@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final readonly class ExpungeData implements Data
{
public function __construct(public int $id)
{
}
}

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch;
final readonly class Address
{
public function __construct(
public ?string $displayName,
public ?string $atDomainList,
public ?string $mailboxName,
public ?string $hostName,
) {
}
}

View File

@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch;
final readonly class BodySection
{
public function __construct(public string $section, public string $text)
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch;
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure\Part;
class BodyStructure
{
public function __construct(
public Part $part,
) {
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
final readonly class Disposition
{
/**
* @param array<string, string> $attributes
*/
public function __construct(
public string $type,
public array $attributes,
) {
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\Envelope;
readonly class MessagePart extends SinglePart
{
/**
* @param array<string, string> $attributes
* @param string[]|null $language
*/
public function __construct(
array $attributes,
?string $id,
?string $description,
string $encoding,
int $size,
public Envelope $envelope,
public BodyStructure $bodyStructure,
public int $textLines,
?string $md5,
?Disposition $disposition,
?array $language,
?string $location,
) {
parent::__construct(
'MESSAGE',
'RFC822',
$attributes,
$id,
$description,
$encoding,
$size,
$md5,
$disposition,
$language,
$location,
);
}
}

View File

@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
final readonly class MultiPart extends Part
{
/**
* @param array<string,string> $attributes
* @param string[] $language
* @param list<Part> $parts
*/
public function __construct(
string $subtype,
array $attributes,
public array $parts,
public ?Disposition $disposition,
public ?array $language,
public ?string $location,
) {
parent::__construct('MULTIPART', $subtype, $attributes);
}
}

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
abstract readonly class Part
{
/**
* @param array<string,string> $attributes
*/
public function __construct(
public string $type,
public string $subtype,
public array $attributes,
) {
}
}

View File

@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
readonly class SinglePart extends Part
{
/**
* @param array<string,string> $attributes
* @param string[]|null $language
*/
public function __construct(
string $type,
string $subtype,
array $attributes,
public ?string $id,
public ?string $description,
public string $encoding,
public int $size,
public ?string $md5,
public ?Disposition $disposition,
public ?array $language,
public ?string $location,
) {
parent::__construct($type, $subtype, $attributes);
}
}

View File

@@ -1,38 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
final readonly class TextPart extends SinglePart
{
/**
* @param array<string, string> $attributes
* @param string[]|null $language
*/
public function __construct(
string $subtype,
array $attributes,
?string $id,
?string $description,
string $encoding,
int $size,
public int $textLines,
?string $md5,
?Disposition $disposition,
?array $language,
?string $location,
) {
parent::__construct(
'TEXT',
$subtype,
$attributes,
$id,
$description,
$encoding,
$size,
$md5,
$disposition,
$language,
$location,
);
}
}

View File

@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data\Fetch;
use DateTimeImmutable;
final readonly class Envelope
{
/**
* @param Address[]|null $from
* @param Address[]|null $sender
* @param Address[]|null $replyTo
* @param Address[]|null $to
* @param Address[]|null $cc
* @param Address[]|null $bcc
*/
public function __construct(
public ?DateTimeImmutable $date,
public ?string $subject,
public ?array $from,
public ?array $sender,
public ?array $replyTo,
public ?array $to,
public ?array $cc,
public ?array $bcc,
public ?string $inReplyTo,
public ?string $messageId,
) {
}
}

View File

@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodySection;
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\Envelope;
final readonly class FetchData implements Data
{
/**
* @param array<string>|null $flags
* @param BodySection[] $bodySections
*/
public function __construct(
public int $id,
public ?array $flags = null,
public ?\DateTimeImmutable $internalDate = null,
public ?Envelope $envelope = null,
public ?int $rfc822Size = null,
public ?string $rfc822 = null,
public ?int $uid = null,
public ?BodyStructure $bodyStructure = null,
public array $bodySections = [],
) {
}
public function getBodySection(string $name): ?BodySection
{
foreach (($this->bodySections ?? []) as $bodySection) {
if ($bodySection->section == $name) {
return $bodySection;
}
}
return null;
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final readonly class FlagsData implements Data
{
/**
* @param list<string> $flags
*/
public function __construct(public array $flags)
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final class ListData implements Data
{
/**
* @param list<string> $nameAttributes
*/
public function __construct(
public array $nameAttributes,
public string $hierarchyDelimiter,
public string $name
) {
}
}

View File

@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final class RecentData implements Data
{
public function __construct(public int $numberOfMessages)
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Data;
final readonly class SearchData implements Data
{
/**
* @param list<int> $numbers
*/
public function __construct(public array $numbers)
{
}
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line;
interface Line
{
}

View File

@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class AppendUidCode implements Code
{
public function __construct(
public int $uidValidity,
public int $uid,
) {
}
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
interface Code
{
}

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class PermanentFlagsCode implements Code
{
/**
* @param string[] $flags
*/
public function __construct(
public array $flags,
) {
}
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class ReadOnlyCode implements Code
{
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class ReadWriteCode implements Code
{
}

View File

@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class UidNextCode implements Code
{
public function __construct(
public int $value,
) {
}
}

View File

@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class UidValidityCode implements Code
{
public function __construct(
public int $value,
) {
}
}

View File

@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status\Code;
final readonly class UnseenCode implements Code
{
public function __construct(
public int $seq,
) {
}
}

View File

@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response\Line\Status;
use Gricob\IMAP\Protocol\Response\Line\Line;
use Gricob\IMAP\Protocol\Response\Line\Status\Code\Code;
final readonly class Status implements Line
{
final public function __construct(
public string $tag,
public StatusType $type,
public ?Code $code,
public string $message
) {
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Line\Status;
enum StatusType: string
{
case OK = 'OK';
case NO = 'NO';
case BAD = 'BAD';
case PREAUTH = 'PREAUTH';
case BYE = 'BYE';
}

View File

@@ -1,79 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Parser;
use Doctrine\Common\Lexer\AbstractLexer;
/**
* @extends AbstractLexer<TokenType, string>
*/
class Lexer extends AbstractLexer
{
protected function getCatchablePatterns(): array
{
return [
'[a-zA-Z0-9\.\-]+',
'\r\n',
];
}
protected function getNonCatchablePatterns(): array
{
return [];
}
protected function getType(string &$value)
{
$normalizedValue = strtoupper($value);
return match($normalizedValue) {
' ' => TokenType::SP,
'.' => TokenType::DOT,
'*' => TokenType::ASTERISK,
'%' => TokenType::PERCENT_SIGN,
'+' => TokenType::PLUS_SIGN,
'=' => TokenType::EQUALS_SIGN,
'"' => TokenType::DOUBLE_QUOTE,
'[' => TokenType::OPEN_BRACKETS,
']' => TokenType::CLOSE_BRACKETS,
'{' => TokenType::OPEN_BRACES,
'}' => TokenType::CLOSE_BRACES,
'(' => TokenType::OPEN_PARENTHESIS,
')' => TokenType::CLOSE_PARENTHESIS,
'\\' => TokenType::BACKSLASH,
"\r\n" => TokenType::CRLF,
'NIL' => TokenType::NIL,
'OK', 'NO', 'BAD', 'BYE', 'PREAUTH' => TokenType::STATUS,
'APPENDUID' => TokenType::APPENDUID,
'UNSEEN' => TokenType::UNSEEN,
'UIDVALIDITY' => TokenType::UIDVALIDITY,
'UIDNEXT' => TokenType::UIDNEXT,
'PERMANENTFLAGS' => TokenType::PERMANENTFLAGS,
'READ-WRITE' => TokenType::READ_WRITE,
'READ-ONLY' => TokenType::READ_ONLY,
'CAPABILITY' => TokenType::CAPABILITY,
'LIST' => TokenType::LIST,
'FLAGS' => TokenType::FLAGS,
'RECENT' => TokenType::RECENT,
'FETCH' => TokenType::FETCH,
'INTERNALDATE' => TokenType::INTERNALDATE,
'SEARCH' => TokenType::SEARCH,
'EXISTS' => TokenType::EXISTS,
'EXPUNGE' => TokenType::EXPUNGE,
'BODY' => TokenType::BODY,
'BODYSTRUCTURE' => TokenType::BODYSTRUCTURE,
'ENVELOPE' => TokenType::ENVELOPE,
'RFC822' => TokenType::RFC822,
'RFC822.SIZE' => TokenType::RFC822_SIZE,
'RFC822.TEXT' => TokenType::RFC822_TEXT,
'RFC822.HEAD' => TokenType::RFC822_HEAD,
'UID' => TokenType::UID,
default => match (true) {
is_numeric($value) => TokenType::NUMBER,
ctype_alnum($value) => TokenType::ALPHANUMERIC,
ctype_cntrl($value) => TokenType::CTL,
default => TokenType::UNKNOWN,
},
};
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Parser;
final class ParseError extends \Exception
{
/**
* @param TokenType[] $expected
*/
public static function unexpectedToken(?TokenType $given, array $expected, string $input): self
{
return new self(
sprintf(
"Expected token of type %s. Given %s.\n%s",
implode(
' or ',
array_map(fn (TokenType $type) => $type->name, $expected)
),
$given?->name ?? 'null',
$input
)
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Response\Parser;
enum TokenType
{
case SP;
case DOT;
case ASTERISK;
case PERCENT_SIGN;
case PLUS_SIGN;
case EQUALS_SIGN;
case DOUBLE_QUOTE;
case NUMBER;
case ALPHANUMERIC;
case NIL;
case OPEN_BRACKETS;
case CLOSE_BRACKETS;
case OPEN_BRACES;
case CLOSE_BRACES;
case OPEN_PARENTHESIS;
case CLOSE_PARENTHESIS;
case BACKSLASH;
case CRLF;
case CTL;
case STATUS;
case APPENDUID;
case UNSEEN;
case UIDVALIDITY;
case UIDNEXT;
case PERMANENTFLAGS;
case READ_WRITE;
case READ_ONLY;
case CAPABILITY;
case LIST;
case FLAGS;
case INTERNALDATE;
case RECENT;
case FETCH;
case SEARCH;
case EXISTS;
case EXPUNGE;
case BODY;
case BODYSTRUCTURE;
case ENVELOPE;
case RFC822;
case RFC822_SIZE;
case RFC822_HEAD;
case RFC822_TEXT;
case UID;
case UNKNOWN;
}

View File

@@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response;
use Gricob\IMAP\Protocol\Response\Line\Line;
use Gricob\IMAP\Protocol\Response\Line\Status\Status;
final readonly class Response
{
/**
* @param list<Line> $data
*/
public function __construct(
public Status $status,
public array $data,
) {
}
/**
* @template T of Line
* @param class-string<T> $type
* @return T[]
*/
public function getData(string $type): array
{
$result = [];
foreach ($this->data as $data) {
if ($data instanceof $type) {
$result[] = $data;
}
}
return $result;
}
}

View File

@@ -1,50 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Response;
use BadMethodCallException;
use Gricob\IMAP\Protocol\Response\Line\Line;
use Gricob\IMAP\Protocol\Response\Line\Status\Status;
class ResponseBuilder
{
private ?Status $status = null;
/**
* @var list<Line>
*/
private array $data = [];
public function __construct(private readonly string $statusTag)
{
}
public function addLine(Line $line): void
{
if ($line instanceof Status && $line->tag === $this->statusTag) {
$this->status = $line;
return;
}
$this->data[] = $line;
}
public function hasStatus(): bool
{
return $this->status !== null;
}
public function build(): Response
{
if (null === $this->status) {
throw new BadMethodCallException();
}
return new Response(
$this->status,
$this->data,
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Response;
interface ResponseInterface
{
public function raw(): string;
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Response;
final class TaggedResponse implements ResponseInterface
{
public function __construct(
private readonly string $tag,
private readonly string $status,
private readonly string $text,
private readonly string $raw,
) {}
public function tag(): string
{
return $this->tag;
}
public function status(): string
{
return $this->status;
}
public function text(): string
{
return $this->text;
}
public function isOk(): bool
{
return $this->status === 'OK';
}
public function raw(): string
{
return $this->raw;
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderImap\Client\Protocol\Response;
final class UntaggedResponse implements ResponseInterface
{
public function __construct(
private readonly string $label,
private readonly string $payload,
private readonly string $raw,
) {}
public function label(): string
{
return $this->label;
}
public function payload(): string
{
return $this->payload;
}
/**
* @return list<string>
*/
public function payloadTokens(): array
{
$payload = trim($this->payload);
if ($payload === '') {
return [];
}
return preg_split('/\s+/', $payload) ?: [];
}
public function raw(): string
{
return $this->raw;
}
}