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
@@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use DateTimeInterface;
use Gricob\IMAP\Protocol\Command\Argument\DateTime;
use Gricob\IMAP\Protocol\Command\Argument\QuotedString;
use Gricob\IMAP\Protocol\Command\Argument\SynchronizingLiteral;
use Gricob\IMAP\Protocol\Command\Argument\ParenthesizedList;
final readonly class AppendCommand extends Command implements Continuable
{
/**
* @param list<string>|null $flags
*/
public function __construct(
string $mailboxName,
private string $message,
?array $flags,
?DateTimeInterface $internalDate
) {
parent::__construct(
'APPEND',
...array_filter([
new QuotedString($mailboxName),
ParenthesizedList::tryFrom($flags),
DateTime::tryFrom($internalDate),
new SynchronizingLiteral($this->message),
])
);
}
public function continue(): string
{
return $this->message;
}
}
@@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
interface Argument
{
public function __toString(): string;
}
@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
use DateTimeInterface;
readonly class Date implements Argument
{
public function __construct(private DateTimeInterface $value)
{
}
public static function tryFrom(?DateTimeInterface $value): ?self
{
return is_null($value) ? null : new self($value);
}
public function __toString(): string
{
return $this->value->format('d-M-Y');
}
}
@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
use DateTimeInterface;
readonly class DateTime implements Argument
{
public function __construct(private DateTimeInterface $value)
{
}
public static function tryFrom(?DateTimeInterface $value): ?self
{
return is_null($value) ? null : new self($value);
}
public function __toString(): string
{
return '"'.$this->value->format('d-M-Y H:i:s O').'"';
}
}
@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
final readonly class ParenthesizedList implements Argument
{
/**
* @param list<string> $items
*/
public function __construct(public array $items)
{
}
/**
* @param list<string> $items
*/
public static function tryFrom(?array $items): ?self
{
return empty($items) ? null : new self($items);
}
public function __toString(): string
{
return sprintf('(%s)', implode(' ', $this->items));
}
}
@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
final readonly class QuotedString implements Argument
{
public function __construct(private string $value)
{
}
public function __toString(): string
{
return sprintf('"%s"', $this->value);
}
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
class All implements Criteria
{
public function __toString(): string
{
return 'ALL';
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
use Gricob\IMAP\Protocol\Command\Argument\Date;
readonly class Before extends Date implements Criteria
{
public function __toString(): string
{
return 'BEFORE '.parent::__toString();
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Body implements Criteria
{
public function __construct(private string $value) {}
public function __toString(): string
{
return 'BODY "' . str_replace(['"', '\\'], ['\\"', '\\\\'], $this->value) . '"';
}
}
@@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
use Gricob\IMAP\Protocol\Command\Argument\Argument;
interface Criteria extends Argument
{
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Flagged implements Criteria
{
public function __toString(): string
{
return 'FLAGGED';
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class From implements Criteria
{
public function __construct(private string $value) {}
public function __toString(): string
{
return 'FROM "' . str_replace(['"', '\\'], ['\\"', '\\\\'], $this->value) . '"';
}
}
@@ -1,19 +0,0 @@
<?php
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
use Gricob\IMAP\Protocol\Command\Argument\QuotedString;
class Header implements Criteria
{
public function __construct(
private string $fieldName,
private string $value,
) {
}
public function __toString(): string
{
return sprintf('HEADER %s %s', $this->fieldName, new QuotedString($this->value));
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Larger implements Criteria
{
public function __construct(private int $size) {}
public function __toString(): string
{
return 'LARGER ' . $this->size;
}
}
@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Not implements Criteria
{
public function __construct(private Criteria $criteria)
{
}
public function __toString(): string
{
return 'NOT ('.$this->criteria.')';
}
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Seen implements Criteria
{
public function __toString(): string
{
return 'SEEN';
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
use Gricob\IMAP\Protocol\Command\Argument\Date;
readonly class Since extends Date implements Criteria
{
public function __toString(): string
{
return 'SINCE '.parent::__toString();
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Smaller implements Criteria
{
public function __construct(private int $size) {}
public function __toString(): string
{
return 'SMALLER ' . $this->size;
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Subject implements Criteria
{
public function __construct(private string $value) {}
public function __toString(): string
{
return 'SUBJECT "' . str_replace(['"', '\\'], ['\\"', '\\\\'], $this->value) . '"';
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class To implements Criteria
{
public function __construct(private string $value) {}
public function __toString(): string
{
return 'TO "' . str_replace(['"', '\\'], ['\\"', '\\\\'], $this->value) . '"';
}
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Unflagged implements Criteria
{
public function __toString(): string
{
return 'UNFLAGGED';
}
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Search;
final readonly class Unseen implements Criteria
{
public function __toString(): string
{
return 'UNSEEN';
}
}
@@ -1,86 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
final class SequenceSet implements Argument
{
/**
* @var array<int>
*/
private array $numbers;
private ?string $range;
public function __construct(int ...$numbers)
{
$this->numbers = $numbers;
$this->range = null;
}
public static function range(int $from, int $to): self
{
$set = new self();
$set->range = $from . ':' . $to;
return $set;
}
/**
* Build a SequenceSet that matches every message in the mailbox (1:*).
*/
public static function all(): self
{
$set = new self();
$set->range = '1:*';
return $set;
}
/**
* Build a SequenceSet from a flat array of UIDs, collapsing consecutive
* values into n:m ranges.
*
* Examples:
* [1, 2, 3, 5, 6, 10] → "1:3,5:6,10"
* [42] → "42"
* [7, 3, 4, 5] → "3:5,7"
*
* @param int[] $uids
*/
public static function list(array $uids): self
{
if (empty($uids)) {
return new self();
}
$uids = array_unique($uids);
sort($uids);
$ranges = [];
$start = $end = $uids[0];
for ($i = 1, $count = count($uids); $i <= $count; $i++) {
$current = $uids[$i] ?? null;
if ($current !== null && $current === $end + 1) {
$end = $current;
} else {
$ranges[] = $start === $end ? (string) $start : $start . ':' . $end;
if ($current !== null) {
$start = $end = $current;
}
}
}
$set = new self();
$set->range = implode(',', $ranges);
return $set;
}
public function __toString(): string
{
if ($this->range !== null) {
return $this->range;
}
return implode(',', $this->numbers);
}
}
@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument\Store;
use Gricob\IMAP\Protocol\Command\Argument\Argument;
final readonly class Flags implements Argument
{
/**
* @param list<string> $flags
*/
public function __construct(
private array $flags,
private string $modifier = '',
private bool $silent = true,
) {
}
public function __toString(): string
{
return sprintf(
'%sFLAGS%s (%s)',
$this->modifier,
$this->silent ? '.SILENT' : '',
implode(' ', $this->flags),
);
}
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Argument;
final readonly class SynchronizingLiteral implements Argument
{
public function __construct(private string $value)
{
}
public function __toString(): string
{
return sprintf(
'{%s}',
strlen($this->value)
);
}
}
@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Authenticate;
use Gricob\IMAP\Protocol\Command\Argument\Argument;
use Gricob\IMAP\Protocol\Command\Continuable;
interface SASLMechanism extends Argument, Continuable
{
}
@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command\Authenticate;
final readonly class XOAuth2 implements SASLMechanism
{
public function __construct(
private string $user,
private string $accessToken
) {
}
public function __toString(): string
{
return 'XOAUTH2';
}
public function continue(): string
{
return base64_encode(
sprintf("user=%s\1auth=Bearer %s\1\1", $this->user, $this->accessToken)
);
}
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Authenticate\SASLMechanism;
readonly class AuthenticateCommand extends Command implements Continuable
{
public function __construct(private SASLMechanism $mechanism)
{
parent::__construct('AUTHENTICATE', $mechanism);
}
public function continue(): string
{
return $this->mechanism->continue();
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\Argument;
use Stringable;
abstract readonly class Command implements Stringable
{
private string $command;
/**
* @var Argument[]
*/
private array $arguments;
public function __construct(
string $command,
Argument ...$arguments,
) {
$this->command = $command;
$this->arguments = $arguments;
}
public function command(): string
{
return $this->command;
}
public function __toString(): string
{
return sprintf(
'%s %s',
$this->command,
implode(' ', $this->arguments)
);
}
}
@@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
interface Continuable
{
public function continue(): string;
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\QuotedString;
final readonly class CreateCommand extends Command
{
public function __construct(string $mailboxName)
{
parent::__construct('CREATE', new QuotedString($mailboxName));
}
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
final readonly class ExpungeCommand extends Command
{
public function __construct()
{
parent::__construct('EXPUNGE');
}
}
@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\ParenthesizedList;
use Gricob\IMAP\Protocol\Command\Argument\SequenceSet;
final readonly class FetchCommand extends Command
{
/**
* @param bool $uid
* @param SequenceSet $sequenceSet
* @param list<string> $items
*/
public function __construct(
bool $uid,
SequenceSet $sequenceSet,
array $items,
) {
parent::__construct(
$uid ? 'UID FETCH' : 'FETCH',
$sequenceSet,
new ParenthesizedList($items),
);
}
}
@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\QuotedString;
readonly class ListCommand extends Command
{
public function __construct(string $referenceName, string $pattern)
{
parent::__construct(
'LIST',
new QuotedString($referenceName),
new QuotedString($pattern)
);
}
}
@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\QuotedString;
final readonly class LogInCommand extends Command
{
public function __construct(string $user, string $password)
{
parent::__construct(
'LOGIN',
new QuotedString($user),
new QuotedString($password)
);
}
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\Search\Criteria;
final readonly class SearchCommand extends Command
{
public function __construct(
bool $uid,
Criteria ...$criteria,
) {
parent::__construct(
$uid ? 'UID SEARCH' : 'SEARCH',
...$criteria,
);
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\QuotedString;
readonly class SelectCommand extends Command
{
public function __construct(string $mailbox)
{
parent::__construct('SELECT', new QuotedString($mailbox));
}
}
@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
/**
* STARTTLS command (RFC 3501 §6.2.1) — patched into gricob/imap.
*
* After the server responds OK, upgradeTls() must be called on the underlying
* SocketConnection to complete the TLS handshake.
*/
final readonly class StartTlsCommand extends Command
{
public function __construct()
{
parent::__construct('STARTTLS');
}
}
@@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace Gricob\IMAP\Protocol\Command;
use Gricob\IMAP\Protocol\Command\Argument\SequenceSet;
use Gricob\IMAP\Protocol\Command\Argument\Store\Flags;
final readonly class StoreCommand extends Command
{
public function __construct(
bool $uid,
SequenceSet $sequenceSet,
Flags $dataItem
) {
parent::__construct(
$uid ? 'UID STORE' : 'STORE',
$sequenceSet,
$dataItem,
);
}
}