mirror of
https://github.com/itflow-org/itflow
synced 2026-07-26 18:27:14 +00:00
Rename plugins to libs and update all file references
This commit is contained in:
344
libs/vendor/directorytree/imapengine/src/Connection/ConnectionInterface.php
vendored
Normal file
344
libs/vendor/directorytree/imapengine/src/Connection/ConnectionInterface.php
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use DirectoryTree\ImapEngine\Collections\ResponseCollection;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\TaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier;
|
||||
use DirectoryTree\ImapEngine\Enums\ImapSortKey;
|
||||
use Generator;
|
||||
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Open a new connection.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-state-and-flow-diagram
|
||||
*/
|
||||
public function connect(string $host, ?int $port = null, array $options = []): void;
|
||||
|
||||
/**
|
||||
* Close the current connection.
|
||||
*/
|
||||
public function disconnect(): void;
|
||||
|
||||
/**
|
||||
* Determine if the current session is connected.
|
||||
*/
|
||||
public function connected(): bool;
|
||||
|
||||
/**
|
||||
* Send a "LOGIN" command.
|
||||
*
|
||||
* Login to a new session.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-login-command
|
||||
*/
|
||||
public function login(string $user, string $password): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "LOGOUT" command.
|
||||
*
|
||||
* Logout of the current server session.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-logout-command
|
||||
*/
|
||||
public function logout(): void;
|
||||
|
||||
/**
|
||||
* Send an "AUTHENTICATE" command.
|
||||
*
|
||||
* Authenticate the current session.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-authenticate-command
|
||||
*/
|
||||
public function authenticate(string $user, string $token): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "STARTTLS" command.
|
||||
*
|
||||
* Upgrade the current plaintext connection to a secure TLS-encrypted connection.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-starttls-command
|
||||
*/
|
||||
public function startTls(): void;
|
||||
|
||||
/**
|
||||
* Send an "IDLE" command.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-idle-command
|
||||
*/
|
||||
public function idle(int $timeout): Generator;
|
||||
|
||||
/**
|
||||
* Send a "DONE" command.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.3.13
|
||||
*/
|
||||
public function done(): void;
|
||||
|
||||
/**
|
||||
* Send a "NOOP" command.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-noop-command
|
||||
*/
|
||||
public function noop(): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "EXPUNGE" command.
|
||||
*
|
||||
* Apply session saved changes to the server.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-expunge-command
|
||||
*/
|
||||
public function expunge(): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "CAPABILITY" command.
|
||||
*
|
||||
* Get the mailbox's available capabilities.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-capability-command
|
||||
*/
|
||||
public function capability(): UntaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "SEARCH" command.
|
||||
*
|
||||
* Execute a search request.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-search-command
|
||||
*/
|
||||
public function search(array $params): UntaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "SORT" command.
|
||||
*
|
||||
* Execute a sort request using RFC 5256.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc5256
|
||||
*/
|
||||
public function sort(ImapSortKey $key, string $direction, array $params): UntaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "FETCH" command.
|
||||
*
|
||||
* Exchange identification information.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc2971.
|
||||
*/
|
||||
public function id(?array $ids = null): UntaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "FETCH UID" command.
|
||||
*
|
||||
* Fetch message UIDs using the given message numbers.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-uid-command
|
||||
*/
|
||||
public function uid(int|array $ids, ImapFetchIdentifier $identifier): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "FETCH BODY[TEXT]" command.
|
||||
*
|
||||
* Fetch message text contents.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.9
|
||||
*/
|
||||
public function bodyText(int|array $ids, bool $peek = true): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "FETCH BODY[HEADER]" command.
|
||||
*
|
||||
* Fetch message headers.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.9
|
||||
*/
|
||||
public function bodyHeader(int|array $ids, bool $peek = true): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "FETCH BODYSTRUCTURE" command.
|
||||
*
|
||||
* Fetch message body structure.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.9
|
||||
*/
|
||||
public function bodyStructure(int|array $ids): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "FETCH BODY[i]" command.
|
||||
*
|
||||
* Fetch a specific part of the message BODY, such as BODY[1], BODY[1.2], etc.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.9
|
||||
*/
|
||||
public function bodyPart(string $partIndex, int|array $ids, bool $peek = false): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "FETCH FLAGS" command.
|
||||
*
|
||||
* Fetch a message flags.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.17
|
||||
*/
|
||||
public function flags(int|array $ids): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "FETCH" command.
|
||||
*
|
||||
* Fetch one or more items for one or more messages.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-fetch-command
|
||||
*/
|
||||
public function fetch(array|string $items, array|int $from, mixed $to = null, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "RFC822.SIZE" command.
|
||||
*
|
||||
* Fetch message sizes for one or more messages.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.21
|
||||
*/
|
||||
public function size(int|array $ids): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send an IMAP command.
|
||||
*/
|
||||
public function send(string $name, array $tokens = [], ?string &$tag = null): void;
|
||||
|
||||
/**
|
||||
* Send a "SELECT" command.
|
||||
*
|
||||
* Select the specified folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-select-command
|
||||
*/
|
||||
public function select(string $folder): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "EXAMINE" command.
|
||||
*
|
||||
* Examine a given folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-examine-command
|
||||
*/
|
||||
public function examine(string $folder): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "LIST" command.
|
||||
*
|
||||
* Get a list of available folders.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-list-command
|
||||
*/
|
||||
public function list(string $reference = '', string $folder = '*'): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "STATUS" command.
|
||||
*
|
||||
* Get the status of a given folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-status-command
|
||||
*/
|
||||
public function status(string $folder, array $arguments = ['MESSAGES', 'UNSEEN', 'RECENT', 'UIDNEXT', 'UIDVALIDITY']): UntaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "STORE" command.
|
||||
*
|
||||
* Set message flags.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-store-command
|
||||
*/
|
||||
public function store(array|string $flags, array|int $from, ?int $to = null, ?string $mode = null, bool $silent = true, ?string $item = null): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "APPEND" command.
|
||||
*
|
||||
* Append a new message to given folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-append-command
|
||||
*/
|
||||
public function append(string $folder, string $message, ?array $flags = null): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "UID COPY" command.
|
||||
*
|
||||
* Copy message set from current folder to other folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-copy-command
|
||||
*/
|
||||
public function copy(string $folder, array|int $from, ?int $to = null): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "UID MOVE" command.
|
||||
*
|
||||
* Move a message set from current folder to another folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-move-command
|
||||
*/
|
||||
public function move(string $folder, array|int $from, ?int $to = null): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "CREATE" command.
|
||||
*
|
||||
* Create a new folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-create-command
|
||||
*/
|
||||
public function create(string $folder): ResponseCollection;
|
||||
|
||||
/**
|
||||
* Send a "DELETE" command.
|
||||
*
|
||||
* Delete a folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-delete-command
|
||||
*/
|
||||
public function delete(string $folder): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "RENAME" command.
|
||||
*
|
||||
* Rename an existing folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-rename-command
|
||||
*/
|
||||
public function rename(string $oldPath, string $newPath): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "SUBSCRIBE" command.
|
||||
*
|
||||
* Subscribe to a folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-subscribe-command
|
||||
*/
|
||||
public function subscribe(string $folder): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "UNSUBSCRIBE" command.
|
||||
*
|
||||
* Unsubscribe from a folder.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-unsubscribe-command
|
||||
*/
|
||||
public function unsubscribe(string $folder): TaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "GETQUOTA" command.
|
||||
*
|
||||
* Retrieve quota information about a specific quota root.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9208#name-getquota
|
||||
*/
|
||||
public function quota(string $root): UntaggedResponse;
|
||||
|
||||
/**
|
||||
* Send a "GETQUOTAROOT" command.
|
||||
*
|
||||
* Retrieve quota root information about a mailbox.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9208#name-getquotaroot
|
||||
*/
|
||||
public function quotaRoot(string $mailbox): ResponseCollection;
|
||||
}
|
||||
105
libs/vendor/directorytree/imapengine/src/Connection/ImapCommand.php
vendored
Normal file
105
libs/vendor/directorytree/imapengine/src/Connection/ImapCommand.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use Stringable;
|
||||
|
||||
class ImapCommand implements Stringable
|
||||
{
|
||||
/**
|
||||
* The compiled command lines.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected ?array $compiled = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $tag,
|
||||
protected string $command,
|
||||
protected array $tokens = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the IMAP tag.
|
||||
*/
|
||||
public function tag(): string
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IMAP command.
|
||||
*/
|
||||
public function command(): string
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IMAP tokens.
|
||||
*/
|
||||
public function tokens(): array
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the command into lines for transmission.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function compile(): array
|
||||
{
|
||||
if (is_array($this->compiled)) {
|
||||
return $this->compiled;
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
|
||||
$line = trim("{$this->tag} {$this->command}");
|
||||
|
||||
foreach ($this->tokens as $token) {
|
||||
if (is_array($token)) {
|
||||
// For tokens provided as arrays, the first element is a placeholder
|
||||
// (for example, "{20}") that signals a literal value will follow.
|
||||
// The second element holds the actual literal content.
|
||||
[$placeholder, $literal] = $token;
|
||||
|
||||
$lines[] = "{$line} {$placeholder}";
|
||||
|
||||
$line = $literal;
|
||||
} else {
|
||||
$line .= " {$token}";
|
||||
}
|
||||
}
|
||||
|
||||
$lines[] = $line;
|
||||
|
||||
return $this->compiled = $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a redacted version of the command for safe exposure.
|
||||
*/
|
||||
public function redacted(): ImapCommand
|
||||
{
|
||||
return new static($this->tag, $this->command, array_map(
|
||||
function (mixed $token) {
|
||||
return is_array($token)
|
||||
? array_map(fn () => '[redacted]', $token)
|
||||
: '[redacted]';
|
||||
}, $this->tokens)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command as a string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode("\r\n", $this->compile());
|
||||
}
|
||||
}
|
||||
815
libs/vendor/directorytree/imapengine/src/Connection/ImapConnection.php
vendored
Normal file
815
libs/vendor/directorytree/imapengine/src/Connection/ImapConnection.php
vendored
Normal file
@@ -0,0 +1,815 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use DirectoryTree\ImapEngine\Collections\ResponseCollection;
|
||||
use DirectoryTree\ImapEngine\Connection\Loggers\LoggerInterface;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\ContinuationResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\Data;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\ListData;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Response;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\TaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Streams\FakeStream;
|
||||
use DirectoryTree\ImapEngine\Connection\Streams\StreamInterface;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier;
|
||||
use DirectoryTree\ImapEngine\Enums\ImapSortKey;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapCommandException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionClosedException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionFailedException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionTimedOutException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapResponseException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapStreamException;
|
||||
use DirectoryTree\ImapEngine\Support\Str;
|
||||
use Exception;
|
||||
use Generator;
|
||||
use LogicException;
|
||||
use Throwable;
|
||||
|
||||
class ImapConnection implements ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Sequence number used to generate unique command tags.
|
||||
*/
|
||||
protected int $sequence = 0;
|
||||
|
||||
/**
|
||||
* The result instance.
|
||||
*/
|
||||
protected ?Result $result = null;
|
||||
|
||||
/**
|
||||
* The parser instance.
|
||||
*/
|
||||
protected ?ImapParser $parser = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected StreamInterface $stream,
|
||||
protected ?LoggerInterface $logger = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a new connection with a fake stream.
|
||||
*/
|
||||
public static function fake(array $responses = []): static
|
||||
{
|
||||
$stream = new FakeStream;
|
||||
|
||||
$stream->open();
|
||||
|
||||
$stream->feed($responses);
|
||||
|
||||
return new static($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the connection.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (! $this->connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@$this->logout();
|
||||
} catch (Exception $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function connect(string $host, ?int $port = null, array $options = []): void
|
||||
{
|
||||
$transport = strtolower($options['encryption'] ?? '') ?: 'tcp';
|
||||
|
||||
if (in_array($transport, ['ssl', 'tls'])) {
|
||||
$port ??= 993;
|
||||
} else {
|
||||
$port ??= 143;
|
||||
}
|
||||
|
||||
$this->setParser(
|
||||
$this->newParser($this->stream)
|
||||
);
|
||||
|
||||
$this->stream->open(
|
||||
$transport === 'starttls' ? 'tcp' : $transport,
|
||||
$host,
|
||||
$port,
|
||||
$options['timeout'] ?? 30,
|
||||
$this->getDefaultSocketOptions(
|
||||
$transport,
|
||||
$options['proxy'] ?? [],
|
||||
$options['validate_cert'] ?? true
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertNextResponse(
|
||||
fn (Response $response) => $response instanceof UntaggedResponse,
|
||||
fn (UntaggedResponse $response) => $response->type()->is('OK'),
|
||||
fn () => new ImapConnectionFailedException("Connection to $host:$port failed")
|
||||
);
|
||||
|
||||
if ($transport === 'starttls') {
|
||||
$this->startTls();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default socket options for the given transport.
|
||||
*
|
||||
* @param 'ssl'|'tls'|'starttls'|'tcp' $transport
|
||||
*/
|
||||
protected function getDefaultSocketOptions(string $transport, array $proxy = [], bool $validateCert = true): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
$key = match ($transport) {
|
||||
'ssl', 'tls' => 'ssl',
|
||||
'starttls', 'tcp' => 'tcp',
|
||||
};
|
||||
|
||||
if (in_array($transport, ['ssl', 'tls'])) {
|
||||
$options[$key] = [
|
||||
'verify_peer' => $validateCert,
|
||||
'verify_peer_name' => $validateCert,
|
||||
];
|
||||
}
|
||||
|
||||
if (! isset($proxy['socket'])) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
$options[$key]['proxy'] = $proxy['socket'];
|
||||
$options[$key]['request_fulluri'] = $proxy['request_fulluri'] ?? false;
|
||||
|
||||
if (isset($proxy['username'])) {
|
||||
$auth = base64_encode($proxy['username'].':'.$proxy['password']);
|
||||
|
||||
$options[$key]['header'] = ["Proxy-Authorization: Basic $auth"];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function disconnect(): void
|
||||
{
|
||||
$this->stream->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function connected(): bool
|
||||
{
|
||||
return $this->stream->opened();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function login(string $user, string $password): TaggedResponse
|
||||
{
|
||||
$this->send('LOGIN', Str::literal([$user, $password]), $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag, fn (TaggedResponse $response) => (
|
||||
ImapCommandException::make($this->result->command()->redacted(), $response)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$this->send('LOGOUT', tag: $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function authenticate(string $user, string $token): TaggedResponse
|
||||
{
|
||||
$this->send('AUTHENTICATE', ['XOAUTH2', Str::credentials($user, $token)], $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag, fn (TaggedResponse $response) => (
|
||||
ImapCommandException::make($this->result->command()->redacted(), $response)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function startTls(): void
|
||||
{
|
||||
$this->send('STARTTLS', tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
$this->stream->setSocketSetCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function select(string $folder = 'INBOX'): ResponseCollection
|
||||
{
|
||||
return $this->examineOrSelect('SELECT', $folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function examine(string $folder = 'INBOX'): ResponseCollection
|
||||
{
|
||||
return $this->examineOrSelect('EXAMINE', $folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine and select have the same response.
|
||||
*/
|
||||
protected function examineOrSelect(string $command = 'EXAMINE', string $folder = 'INBOX'): ResponseCollection
|
||||
{
|
||||
$this->send($command, [Str::literal($folder)], $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function status(string $folder = 'INBOX', array $arguments = ['MESSAGES', 'UNSEEN', 'RECENT', 'UIDNEXT', 'UIDVALIDITY']): UntaggedResponse
|
||||
{
|
||||
$this->send('STATUS', [
|
||||
Str::literal($folder),
|
||||
Str::list($arguments),
|
||||
], $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->firstWhere(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('STATUS')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function create(string $folder): ResponseCollection
|
||||
{
|
||||
$this->send('CREATE', [Str::literal($folder)], $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->filter(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('LIST')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $folder): TaggedResponse
|
||||
{
|
||||
$this->send('DELETE', [Str::literal($folder)], tag: $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function rename(string $oldPath, string $newPath): TaggedResponse
|
||||
{
|
||||
$this->send('RENAME', Str::literal([$oldPath, $newPath]), tag: $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function subscribe(string $folder): TaggedResponse
|
||||
{
|
||||
$this->send('SUBSCRIBE', [Str::literal($folder)], tag: $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function unsubscribe(string $folder): TaggedResponse
|
||||
{
|
||||
$this->send('UNSUBSCRIBE', [Str::literal($folder)], tag: $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function quota(string $root): UntaggedResponse
|
||||
{
|
||||
$this->send('GETQUOTA', [Str::literal($root)], tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->firstOrFail(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('QUOTA')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function quotaRoot(string $mailbox): ResponseCollection
|
||||
{
|
||||
$this->send('GETQUOTAROOT', [Str::literal($mailbox)], tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->filter(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('QUOTA')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function list(string $reference = '', string $folder = '*'): ResponseCollection
|
||||
{
|
||||
$this->send('LIST', Str::literal([$reference, $folder]), $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->filter(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('LIST')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function append(string $folder, string $message, ?array $flags = null): TaggedResponse
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
$tokens[] = Str::literal($folder);
|
||||
|
||||
if ($flags) {
|
||||
$tokens[] = Str::list($flags);
|
||||
}
|
||||
|
||||
$tokens[] = Str::literal($message);
|
||||
|
||||
$this->send('APPEND', $tokens, tag: $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function copy(string $folder, array|int $from, ?int $to = null): TaggedResponse
|
||||
{
|
||||
$this->send('UID COPY', [
|
||||
Str::set($from, $to),
|
||||
Str::literal($folder),
|
||||
], $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function move(string $folder, array|int $from, ?int $to = null): TaggedResponse
|
||||
{
|
||||
$this->send('UID MOVE', [
|
||||
Str::set($from, $to),
|
||||
Str::literal($folder),
|
||||
], $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function store(array|string $flags, array|int $from, ?int $to = null, ?string $mode = null, bool $silent = true, ?string $item = null): ResponseCollection
|
||||
{
|
||||
$set = Str::set($from, $to);
|
||||
|
||||
$flags = Str::list((array) $flags);
|
||||
|
||||
$item = ($mode == '-' ? '-' : '+').(is_null($item) ? 'FLAGS' : $item).($silent ? '.SILENT' : '');
|
||||
|
||||
$this->send('UID STORE', [$set, $item, $flags], tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $silent ? new ResponseCollection : $this->result->responses()->untagged()->filter(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('FETCH')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function uid(int|array $ids, ImapFetchIdentifier $identifier): ResponseCollection
|
||||
{
|
||||
return $this->fetch(['UID'], (array) $ids, null, $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bodyText(int|array $ids, bool $peek = true): ResponseCollection
|
||||
{
|
||||
return $this->fetch([$peek ? 'BODY.PEEK[TEXT]' : 'BODY[TEXT]'], (array) $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bodyHeader(int|array $ids, bool $peek = true): ResponseCollection
|
||||
{
|
||||
return $this->fetch([$peek ? 'BODY.PEEK[HEADER]' : 'BODY[HEADER]'], (array) $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the BODYSTRUCTURE for the given message(s).
|
||||
*/
|
||||
public function bodyStructure(int|array $ids): ResponseCollection
|
||||
{
|
||||
return $this->fetch(['BODYSTRUCTURE'], (array) $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a specific part of the message BODY, such as BODY[1], BODY[1.2], etc.
|
||||
*/
|
||||
public function bodyPart(string $partIndex, int|array $ids, bool $peek = false): ResponseCollection
|
||||
{
|
||||
$part = $peek ? "BODY.PEEK[$partIndex]" : "BODY[$partIndex]";
|
||||
|
||||
return $this->fetch([$part], (array) $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function flags(int|array $ids): ResponseCollection
|
||||
{
|
||||
return $this->fetch(['FLAGS'], (array) $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function size(int|array $ids): ResponseCollection
|
||||
{
|
||||
return $this->fetch(['RFC822.SIZE'], (array) $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function search(array $params): UntaggedResponse
|
||||
{
|
||||
$this->send('UID SEARCH', $params, tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->firstOrFail(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('SEARCH')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function sort(ImapSortKey $key, string $direction, array $params): UntaggedResponse
|
||||
{
|
||||
$sortCriteria = $direction === 'desc' ? "REVERSE {$key->value}" : $key->value;
|
||||
|
||||
$this->send('UID SORT', ["({$sortCriteria})", 'UTF-8', ...$params], tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->firstOrFail(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('SORT')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function capability(): UntaggedResponse
|
||||
{
|
||||
$this->send('CAPABILITY', tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->firstOrFail(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('CAPABILITY')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function id(?array $ids = null): UntaggedResponse
|
||||
{
|
||||
$token = 'NIL';
|
||||
|
||||
if (is_array($ids) && ! empty($ids)) {
|
||||
$token = '(';
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$token .= '"'.Str::escape($id).'" ';
|
||||
}
|
||||
|
||||
$token = rtrim($token).')';
|
||||
}
|
||||
|
||||
$this->send('ID', [$token], tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged()->firstOrFail(
|
||||
fn (UntaggedResponse $response) => $response->type()->is('ID')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function expunge(): ResponseCollection
|
||||
{
|
||||
$this->send('EXPUNGE', tag: $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
return $this->result->responses()->untagged();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function noop(): TaggedResponse
|
||||
{
|
||||
$this->send('NOOP', tag: $tag);
|
||||
|
||||
return $this->assertTaggedResponse($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function idle(int $timeout): Generator
|
||||
{
|
||||
$this->stream->setTimeout($timeout);
|
||||
|
||||
$this->send('IDLE', tag: $tag);
|
||||
|
||||
$this->assertNextResponse(
|
||||
fn (Response $response) => $response instanceof ContinuationResponse,
|
||||
fn (ContinuationResponse $response) => true,
|
||||
fn (ContinuationResponse $response) => ImapCommandException::make(new ImapCommand('', 'IDLE'), $response),
|
||||
);
|
||||
|
||||
while ($response = $this->nextReply()) {
|
||||
yield $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function done(): void
|
||||
{
|
||||
$this->write('DONE');
|
||||
|
||||
// After issuing a "DONE" command, the server must eventually respond with a
|
||||
// tagged response to indicate that the IDLE command has been successfully
|
||||
// terminated and the server is ready to accept further commands.
|
||||
$this->assertNextResponse(
|
||||
fn (Response $response) => $response instanceof TaggedResponse,
|
||||
fn (TaggedResponse $response) => $response->successful(),
|
||||
fn (TaggedResponse $response) => ImapCommandException::make(new ImapCommand('', 'DONE'), $response),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an IMAP command.
|
||||
*
|
||||
* @param-out string $tag
|
||||
*/
|
||||
public function send(string $name, array $tokens = [], ?string &$tag = null): void
|
||||
{
|
||||
if (! $tag) {
|
||||
$this->sequence++;
|
||||
$tag = 'TAG'.$this->sequence;
|
||||
}
|
||||
|
||||
$command = new ImapCommand($tag, $name, $tokens);
|
||||
|
||||
// After every command, we'll overwrite any previous result
|
||||
// with the new command and its responses, so that we can
|
||||
// easily access the commands responses for assertion.
|
||||
$this->setResult(new Result($command));
|
||||
|
||||
foreach ($command->compile() as $line) {
|
||||
$this->write($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the connected stream.
|
||||
*/
|
||||
protected function write(string $data): void
|
||||
{
|
||||
if ($this->stream->fwrite($data."\r\n") === false) {
|
||||
throw new ImapStreamException('Failed to write data to stream');
|
||||
}
|
||||
|
||||
$this->logger?->sent($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch one or more items for one or more messages.
|
||||
*/
|
||||
public function fetch(array|string $items, array|int $from, mixed $to = null, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid): ResponseCollection
|
||||
{
|
||||
$prefix = ($identifier === ImapFetchIdentifier::Uid) ? 'UID' : '';
|
||||
|
||||
$this->send(trim($prefix.' FETCH'), [
|
||||
Str::set($from, $to),
|
||||
Str::list((array) $items),
|
||||
], $tag);
|
||||
|
||||
$this->assertTaggedResponse($tag);
|
||||
|
||||
// Some IMAP servers can send unsolicited untagged responses along with fetch
|
||||
// requests. We'll need to filter these out so that we can return only the
|
||||
// responses that are relevant to the fetch command. For example:
|
||||
// >> TAG123 FETCH (UID 456 BODY[TEXT])
|
||||
// << * 123 FETCH (UID 456 BODY[TEXT] {14}\nHello, World!)
|
||||
// << * 123 FETCH (FLAGS (\Seen)) <-- Unsolicited response
|
||||
return $this->result->responses()->untagged()->filter(function (UntaggedResponse $response) use ($items, $identifier) {
|
||||
// Skip over any untagged responses that are not FETCH responses.
|
||||
// The third token should always be the list of data items.
|
||||
if (! ($data = $response->tokenAt(3)) instanceof ListData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return match ($identifier) {
|
||||
// If we're fetching UIDs, we can check if a UID token is contained in the list.
|
||||
ImapFetchIdentifier::Uid => $data->contains('UID'),
|
||||
|
||||
// If we're fetching message numbers, we can check if the requested items are all contained in the list.
|
||||
ImapFetchIdentifier::MessageNumber => $data->contains($items),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current result instance.
|
||||
*/
|
||||
protected function setResult(Result $result): void
|
||||
{
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current parser instance.
|
||||
*/
|
||||
protected function setParser(ImapParser $parser): void
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new parser instance.
|
||||
*/
|
||||
protected function newParser(StreamInterface $stream): ImapParser
|
||||
{
|
||||
return new ImapParser($this->newTokenizer($stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new tokenizer instance.
|
||||
*/
|
||||
protected function newTokenizer(StreamInterface $stream): ImapTokenizer
|
||||
{
|
||||
return new ImapTokenizer($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the next response is a successful tagged response.
|
||||
*/
|
||||
protected function assertTaggedResponse(string $tag, ?callable $exception = null): TaggedResponse
|
||||
{
|
||||
/** @var TaggedResponse $response */
|
||||
$response = $this->assertNextResponse(
|
||||
fn (Response $response) => (
|
||||
$response instanceof TaggedResponse && $response->tag()->is($tag)
|
||||
),
|
||||
fn (TaggedResponse $response) => (
|
||||
$response->successful()
|
||||
),
|
||||
$exception ?? fn (TaggedResponse $response) => (
|
||||
ImapCommandException::make($this->result->command(), $response)
|
||||
),
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the next response matches the given filter and assertion.
|
||||
*
|
||||
* @template T of Response
|
||||
*
|
||||
* @param callable(Response): bool $filter
|
||||
* @param callable(T): bool $assertion
|
||||
* @param callable(T): Throwable $exception
|
||||
* @return T
|
||||
*
|
||||
* @throws ImapResponseException
|
||||
*/
|
||||
protected function assertNextResponse(callable $filter, callable $assertion, callable $exception): Response
|
||||
{
|
||||
while ($response = $this->nextResponse($filter)) {
|
||||
if ($assertion($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
throw $exception($response);
|
||||
}
|
||||
|
||||
throw new ImapResponseException('No matching response found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next response matching the given filter.
|
||||
*
|
||||
* @template T of Response
|
||||
*
|
||||
* @param callable(T): bool $filter
|
||||
* @return T|null
|
||||
*/
|
||||
protected function nextResponse(callable $filter): ?Response
|
||||
{
|
||||
if (! $this->parser) {
|
||||
throw new LogicException('No parser instance set');
|
||||
}
|
||||
|
||||
while ($response = $this->nextReply()) {
|
||||
if (! $response instanceof Response) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->result?->addResponse($response);
|
||||
|
||||
if ($filter($response)) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next reply from the stream.
|
||||
*/
|
||||
protected function nextReply(): Data|Token|Response|null
|
||||
{
|
||||
if (! $reply = $this->parser->next()) {
|
||||
$meta = $this->stream->meta();
|
||||
|
||||
throw match (true) {
|
||||
$meta['timed_out'] ?? false => new ImapConnectionTimedOutException('Stream timed out, no response'),
|
||||
$meta['eof'] ?? false => new ImapConnectionClosedException('Server closed the connection (EOF)'),
|
||||
default => new ImapConnectionFailedException('Unknown stream error. Metadata: '.json_encode($meta)),
|
||||
};
|
||||
}
|
||||
|
||||
$this->logger?->received($reply);
|
||||
|
||||
return $reply;
|
||||
}
|
||||
}
|
||||
270
libs/vendor/directorytree/imapengine/src/Connection/ImapParser.php
vendored
Normal file
270
libs/vendor/directorytree/imapengine/src/Connection/ImapParser.php
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\ContinuationResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\Data;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\ListData;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\ResponseCodeData;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Response;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\TaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Crlf;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ListClose;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ListOpen;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Number;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ResponseCodeClose;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ResponseCodeOpen;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapParserException;
|
||||
|
||||
class ImapParser
|
||||
{
|
||||
/**
|
||||
* The current token being parsed.
|
||||
*
|
||||
* Expected to be an associative array with keys like "type" and "value".
|
||||
*/
|
||||
protected ?Token $currentToken = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected ImapTokenizer $tokenizer
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the next response from the tokenizer.
|
||||
*/
|
||||
public function next(): Data|Token|Response|null
|
||||
{
|
||||
// Attempt to load the first token.
|
||||
if (! $this->currentToken) {
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
// No token was found, return null.
|
||||
if (! $this->currentToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the token indicates the beginning of a list, parse it.
|
||||
if ($this->currentToken instanceof ListOpen) {
|
||||
return $this->parseList();
|
||||
}
|
||||
|
||||
// If the token is an Atom or Number, check its value for special markers.
|
||||
if ($this->currentToken instanceof Atom || $this->currentToken instanceof Number) {
|
||||
// '*' marks an untagged response.
|
||||
if ($this->currentToken->value === '*') {
|
||||
return $this->parseUntaggedResponse();
|
||||
}
|
||||
|
||||
// '+' marks a continuation response.
|
||||
if ($this->currentToken->value === '+') {
|
||||
return $this->parseContinuationResponse();
|
||||
}
|
||||
|
||||
// If it's an ATOM and not '*' or '+', it's likely a tagged response.
|
||||
return $this->parseTaggedResponse();
|
||||
}
|
||||
|
||||
return $this->parseElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an untagged response.
|
||||
*
|
||||
* An untagged response begins with the '*' token. It may contain
|
||||
* multiple elements, including lists and response codes.
|
||||
*/
|
||||
protected function parseUntaggedResponse(): UntaggedResponse
|
||||
{
|
||||
// Capture the initial '*' token.
|
||||
$elements[] = clone $this->currentToken;
|
||||
|
||||
$this->advance();
|
||||
|
||||
// Collect all tokens until the end-of-response marker.
|
||||
while ($this->currentToken && ! $this->currentToken instanceof Crlf) {
|
||||
$elements[] = $this->parseElement();
|
||||
}
|
||||
|
||||
// If the end-of-response marker (CRLF) is present, consume it.
|
||||
if ($this->currentToken && $this->currentToken instanceof Crlf) {
|
||||
$this->currentToken = null;
|
||||
} else {
|
||||
throw new ImapParserException('Unterminated untagged response');
|
||||
}
|
||||
|
||||
return new UntaggedResponse($elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a continuation response.
|
||||
*
|
||||
* A continuation response starts with a '+' token, indicating
|
||||
* that the server expects additional data from the client.
|
||||
*/
|
||||
protected function parseContinuationResponse(): ContinuationResponse
|
||||
{
|
||||
// Capture the initial '+' token.
|
||||
$elements[] = clone $this->currentToken;
|
||||
|
||||
$this->advance();
|
||||
|
||||
// Collect all tokens until the CRLF marker.
|
||||
while ($this->currentToken && ! $this->currentToken instanceof Crlf) {
|
||||
$elements[] = $this->parseElement();
|
||||
}
|
||||
|
||||
// Consume the CRLF marker if present.
|
||||
if ($this->currentToken && $this->currentToken instanceof Crlf) {
|
||||
$this->currentToken = null;
|
||||
} else {
|
||||
throw new ImapParserException('Unterminated continuation response');
|
||||
}
|
||||
|
||||
return new ContinuationResponse($elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a tagged response.
|
||||
*
|
||||
* A tagged response begins with a tag (which is not '*' or '+')
|
||||
* and is followed by a status and optional data.
|
||||
*/
|
||||
protected function parseTaggedResponse(): TaggedResponse
|
||||
{
|
||||
// Capture the initial TAG token.
|
||||
$tokens[] = clone $this->currentToken;
|
||||
|
||||
$this->advance();
|
||||
|
||||
// Collect tokens until the end-of-response marker is reached.
|
||||
while ($this->currentToken && ! $this->currentToken instanceof Crlf) {
|
||||
$tokens[] = $this->parseElement();
|
||||
}
|
||||
|
||||
// Consume the CRLF marker if present.
|
||||
if ($this->currentToken && $this->currentToken instanceof Crlf) {
|
||||
$this->currentToken = null;
|
||||
} else {
|
||||
throw new ImapParserException('Unterminated tagged response');
|
||||
}
|
||||
|
||||
return new TaggedResponse($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a bracket group of elements delimited by '[' and ']'.
|
||||
*
|
||||
* Bracket groups are used to represent response codes.
|
||||
*/
|
||||
protected function parseBracketGroup(): ResponseCodeData
|
||||
{
|
||||
// Consume the opening '[' token.
|
||||
$this->advance();
|
||||
|
||||
$elements = [];
|
||||
|
||||
while (
|
||||
$this->currentToken
|
||||
&& ! $this->currentToken instanceof ResponseCodeClose
|
||||
) {
|
||||
// Skip CRLF tokens that may appear inside bracket groups.
|
||||
if ($this->currentToken instanceof Crlf) {
|
||||
$this->advance();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$elements[] = $this->parseElement();
|
||||
}
|
||||
|
||||
if ($this->currentToken === null) {
|
||||
throw new ImapParserException('Unterminated bracket group in response');
|
||||
}
|
||||
|
||||
// Consume the closing ']' token.
|
||||
$this->advance();
|
||||
|
||||
return new ResponseCodeData($elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of elements delimited by '(' and ')'.
|
||||
*
|
||||
* Lists are handled recursively, as a list may contain nested lists.
|
||||
*/
|
||||
protected function parseList(): ListData
|
||||
{
|
||||
// Consume the opening '(' token.
|
||||
$this->advance();
|
||||
|
||||
$elements = [];
|
||||
|
||||
// Continue to parse elements until we find the corresponding ')'.
|
||||
while (
|
||||
$this->currentToken
|
||||
&& ! $this->currentToken instanceof ListClose
|
||||
) {
|
||||
// Skip CRLF tokens that appear inside lists (after literals).
|
||||
if ($this->currentToken instanceof Crlf) {
|
||||
$this->advance();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$elements[] = $this->parseElement();
|
||||
}
|
||||
|
||||
// If we reached the end without finding a closing ')', throw an exception.
|
||||
if ($this->currentToken === null) {
|
||||
throw new ImapParserException('Unterminated list in response');
|
||||
}
|
||||
|
||||
// Consume the closing ')' token.
|
||||
$this->advance();
|
||||
|
||||
return new ListData($elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single element, which might be a list or a simple token.
|
||||
*/
|
||||
protected function parseElement(): Data|Token|null
|
||||
{
|
||||
// If there is no current token, return null.
|
||||
if ($this->currentToken === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the token indicates the start of a list, parse it as a list.
|
||||
if ($this->currentToken instanceof ListOpen) {
|
||||
return $this->parseList();
|
||||
}
|
||||
|
||||
// If the token indicates the start of a group, parse it as a group.
|
||||
if ($this->currentToken instanceof ResponseCodeOpen) {
|
||||
return $this->parseBracketGroup();
|
||||
}
|
||||
|
||||
// Otherwise, capture the current token.
|
||||
$token = clone $this->currentToken;
|
||||
|
||||
$this->advance();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance to the next token from the tokenizer.
|
||||
*/
|
||||
protected function advance(): void
|
||||
{
|
||||
$this->currentToken = $this->tokenizer->nextToken();
|
||||
}
|
||||
}
|
||||
527
libs/vendor/directorytree/imapengine/src/Connection/ImapQueryBuilder.php
vendored
Normal file
527
libs/vendor/directorytree/imapengine/src/Connection/ImapQueryBuilder.php
vendored
Normal file
@@ -0,0 +1,527 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use DateTimeInterface;
|
||||
use DirectoryTree\ImapEngine\Enums\ImapSearchKey;
|
||||
use DirectoryTree\ImapEngine\Support\Str;
|
||||
|
||||
class ImapQueryBuilder
|
||||
{
|
||||
/**
|
||||
* The largest UID value allowed by IMAP.
|
||||
*/
|
||||
protected const MAX_UID = '4294967295';
|
||||
|
||||
/**
|
||||
* The where conditions for the query.
|
||||
*/
|
||||
protected array $wheres = [];
|
||||
|
||||
/**
|
||||
* The date format to use for date based queries.
|
||||
*/
|
||||
protected string $dateFormat = 'd-M-Y';
|
||||
|
||||
/**
|
||||
* Add a where "ALL" clause to the query.
|
||||
*/
|
||||
public function all(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::All);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "NEW" clause to the query.
|
||||
*/
|
||||
public function new(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::New);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "OLD" clause to the query.
|
||||
*/
|
||||
public function old(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Old);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SEEN" clause to the query.
|
||||
*/
|
||||
public function seen(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Seen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "DRAFT" clause to the query.
|
||||
*/
|
||||
public function draft(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Draft);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "RECENT" clause to the query.
|
||||
*/
|
||||
public function recent(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Recent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "UNSEEN" clause to the query.
|
||||
*/
|
||||
public function unseen(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Unseen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "FLAGGED" clause to the query.
|
||||
*/
|
||||
public function flagged(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Flagged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "DELETED" clause to the query.
|
||||
*/
|
||||
public function deleted(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "ANSWERED" clause to the query.
|
||||
*/
|
||||
public function answered(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Answered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "UNDELETED" clause to the query.
|
||||
*/
|
||||
public function undeleted(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Undeleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "UNFLAGGED" clause to the query.
|
||||
*/
|
||||
public function unflagged(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Unflagged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "UNANSWERED" clause to the query.
|
||||
*/
|
||||
public function unanswered(): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Unanswered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "FROM" clause to the query.
|
||||
*/
|
||||
public function from(string $email): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::From, $email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "TO" clause to the query.
|
||||
*/
|
||||
public function to(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::To, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "CC" clause to the query.
|
||||
*/
|
||||
public function cc(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Cc, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "BCC" clause to the query.
|
||||
*/
|
||||
public function bcc(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Bcc, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "BODY" clause to the query.
|
||||
*/
|
||||
public function body(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Body, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "KEYWORD" clause to the query.
|
||||
*/
|
||||
public function keyword(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Keyword, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "UNKEYWORD" clause to the query.
|
||||
*/
|
||||
public function unkeyword(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Unkeyword, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "ON" clause to the query.
|
||||
*/
|
||||
public function on(mixed $date): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::On, new RawQueryValue(
|
||||
$this->parseDate($date)->format($this->dateFormat)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SINCE" clause to the query.
|
||||
*/
|
||||
public function since(mixed $date): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Since, new RawQueryValue(
|
||||
$this->parseDate($date)->format($this->dateFormat)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "BEFORE" clause to the query.
|
||||
*/
|
||||
public function before(mixed $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Before, new RawQueryValue(
|
||||
$this->parseDate($value)->format($this->dateFormat)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SENTON" clause to the query.
|
||||
*/
|
||||
public function sentOn(mixed $date): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::SentOn, new RawQueryValue(
|
||||
$this->parseDate($date)->format($this->dateFormat)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SENTSINCE" clause to the query.
|
||||
*/
|
||||
public function sentSince(mixed $date): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::SentSince, new RawQueryValue(
|
||||
$this->parseDate($date)->format($this->dateFormat)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SENTBEFORE" clause to the query.
|
||||
*/
|
||||
public function sentBefore(mixed $date): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::SentBefore, new RawQueryValue(
|
||||
$this->parseDate($date)->format($this->dateFormat)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SUBJECT" clause to the query.
|
||||
*/
|
||||
public function subject(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Subject, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "TEXT" clause to the query.
|
||||
*/
|
||||
public function text(string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Text, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "HEADER" clause to the query.
|
||||
*/
|
||||
public function header(string $header, string $value): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Header->value." $header", $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "HEADER Message-ID" clause to the query.
|
||||
*/
|
||||
public function messageId(string $messageId): static
|
||||
{
|
||||
return $this->header('Message-ID', trim(trim($messageId), '<>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "UID" clause to the query.
|
||||
*/
|
||||
public function uid(int|string|array $from, int|float|null $to = null): static
|
||||
{
|
||||
if ($to === INF) {
|
||||
$to = self::MAX_UID;
|
||||
}
|
||||
|
||||
return $this->where(ImapSearchKey::Uid, new RawQueryValue(Str::set($from, $to)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "LARGER" clause to the query.
|
||||
*/
|
||||
public function larger(int $bytes): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Larger, new RawQueryValue($bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where "SMALLER" clause to the query.
|
||||
*/
|
||||
public function smaller(int $bytes): static
|
||||
{
|
||||
return $this->where(ImapSearchKey::Smaller, new RawQueryValue($bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "where" condition.
|
||||
*/
|
||||
public function where(mixed $column, mixed $value = null): static
|
||||
{
|
||||
if (is_callable($column)) {
|
||||
$this->addNestedCondition('AND', $column);
|
||||
} else {
|
||||
$this->addBasicCondition('AND', $column, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "or where" condition.
|
||||
*/
|
||||
public function orWhere(mixed $column, mixed $value = null): static
|
||||
{
|
||||
if (is_callable($column)) {
|
||||
$this->addNestedCondition('OR', $column);
|
||||
} else {
|
||||
$this->addBasicCondition('OR', $column, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "where not" condition.
|
||||
*/
|
||||
public function whereNot(mixed $column, mixed $value = null): static
|
||||
{
|
||||
$this->addBasicCondition('AND', $column, $value, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the query has any where conditions.
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->wheres);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the instance into an IMAP-compatible query string.
|
||||
*/
|
||||
public function toImap(): string
|
||||
{
|
||||
return $this->compileWheres($this->wheres);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new query instance (like Eloquent's newQuery).
|
||||
*/
|
||||
protected function newQuery(): static
|
||||
{
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic condition to the query.
|
||||
*/
|
||||
protected function addBasicCondition(string $boolean, mixed $column, mixed $value, bool $not = false): void
|
||||
{
|
||||
$value = $this->prepareWhereValue($value);
|
||||
|
||||
$column = Str::enum($column);
|
||||
|
||||
$this->wheres[] = [
|
||||
'type' => 'basic',
|
||||
'not' => $not,
|
||||
'key' => $column,
|
||||
'value' => $value,
|
||||
'boolean' => $boolean,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the where value, escaping it as needed.
|
||||
*/
|
||||
protected function prepareWhereValue(mixed $value): RawQueryValue|string|null
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof RawQueryValue) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
$value = $value->value;
|
||||
}
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
$value = Carbon::instance($value);
|
||||
}
|
||||
|
||||
if ($value instanceof CarbonInterface) {
|
||||
$value = $value->format($this->dateFormat);
|
||||
}
|
||||
|
||||
return Str::escape($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a nested condition group to the query.
|
||||
*/
|
||||
protected function addNestedCondition(string $boolean, callable $callback): void
|
||||
{
|
||||
$nested = $this->newQuery();
|
||||
|
||||
$callback($nested);
|
||||
|
||||
$this->wheres[] = [
|
||||
'type' => 'nested',
|
||||
'query' => $nested,
|
||||
'boolean' => $boolean,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a date string into a Carbon instance.
|
||||
*/
|
||||
protected function parseDate(mixed $date): CarbonInterface
|
||||
{
|
||||
if ($date instanceof CarbonInterface) {
|
||||
return $date;
|
||||
}
|
||||
|
||||
return Carbon::parse($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a single expression node from a basic or nested where.
|
||||
*
|
||||
* @param array{type: 'basic'|'nested', boolean: 'AND'|'OR', query: ImapQueryBuilder} $where
|
||||
*/
|
||||
protected function makeExpressionNode(array $where): array
|
||||
{
|
||||
return match ($where['type']) {
|
||||
'basic' => [
|
||||
'expr' => $this->compileBasic($where),
|
||||
'boolean' => $where['boolean'],
|
||||
],
|
||||
|
||||
'nested' => [
|
||||
'expr' => $where['query']->toImap(),
|
||||
'boolean' => $where['boolean'],
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the existing expression with the next expression, respecting the boolean operator.
|
||||
*
|
||||
* @param 'AND'|'OR' $boolean
|
||||
*/
|
||||
protected function mergeExpressions(string $existing, string $next, string $boolean): string
|
||||
{
|
||||
return match ($boolean) {
|
||||
// AND is implicit – just append.
|
||||
'AND' => $existing.' '.$next,
|
||||
|
||||
// IMAP's OR is binary; nest accordingly.
|
||||
'OR' => 'OR ('.$existing.') ('.$next.')',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively compile the wheres array into an IMAP-compatible string.
|
||||
*/
|
||||
protected function compileWheres(array $wheres): string
|
||||
{
|
||||
if (empty($wheres)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convert each "where" into a node for later merging.
|
||||
$exprNodes = array_map(fn (array $where) => (
|
||||
$this->makeExpressionNode($where)
|
||||
), $wheres);
|
||||
|
||||
// Start with the first expression.
|
||||
$combined = array_shift($exprNodes)['expr'];
|
||||
|
||||
// Merge the rest of the expressions.
|
||||
foreach ($exprNodes as $node) {
|
||||
$combined = $this->mergeExpressions(
|
||||
$combined, $node['expr'], $node['boolean']
|
||||
);
|
||||
}
|
||||
|
||||
return trim($combined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a basic where condition into an IMAP-compatible string.
|
||||
*/
|
||||
protected function compileBasic(array $where): string
|
||||
{
|
||||
$part = strtoupper($where['key']);
|
||||
|
||||
if ($where['value'] instanceof RawQueryValue) {
|
||||
$part .= ' '.$where['value']->value;
|
||||
} elseif ($where['value']) {
|
||||
$part .= ' "'.Str::toImapUtf7($where['value']).'"';
|
||||
}
|
||||
|
||||
if ($where['not']) {
|
||||
$part = 'NOT '.$part;
|
||||
}
|
||||
|
||||
return $part;
|
||||
}
|
||||
}
|
||||
511
libs/vendor/directorytree/imapengine/src/Connection/ImapTokenizer.php
vendored
Normal file
511
libs/vendor/directorytree/imapengine/src/Connection/ImapTokenizer.php
vendored
Normal file
@@ -0,0 +1,511 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Streams\StreamInterface;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Crlf;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\EmailAddress;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ListClose;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ListOpen;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Literal;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Nil;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Number;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\QuotedString;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ResponseCodeClose;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\ResponseCodeOpen;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapParserException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapStreamException;
|
||||
|
||||
class ImapTokenizer
|
||||
{
|
||||
/**
|
||||
* The current position in the buffer.
|
||||
*/
|
||||
protected int $position = 0;
|
||||
|
||||
/**
|
||||
* The buffer of characters read from the stream.
|
||||
*/
|
||||
protected string $buffer = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected StreamInterface $stream
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns the next token from the stream.
|
||||
*/
|
||||
public function nextToken(): ?Token
|
||||
{
|
||||
$this->skipWhitespace();
|
||||
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
if ($char === null || $char === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for line feed.
|
||||
if ($char === "\n") {
|
||||
// With a valid IMAP response, we should never reach this point,
|
||||
// but in case we receive a malformed response, we will flush
|
||||
// the buffer and return null to prevent an infinite loop.
|
||||
$this->flushBuffer();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for carriage return. (\r\n)
|
||||
if ($char === "\r") {
|
||||
$this->advance(); // Consume CR
|
||||
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
if ($this->currentChar() !== "\n") {
|
||||
throw new ImapParserException('Expected LF after CR');
|
||||
}
|
||||
|
||||
$this->advance(); // Consume LF (\n)
|
||||
|
||||
return new Crlf("\r\n");
|
||||
}
|
||||
|
||||
// Check for parameter list opening.
|
||||
if ($char === '(') {
|
||||
$this->advance();
|
||||
|
||||
return new ListOpen('(');
|
||||
}
|
||||
|
||||
// Check for a parameter list closing.
|
||||
if ($char === ')') {
|
||||
$this->advance();
|
||||
|
||||
return new ListClose(')');
|
||||
}
|
||||
|
||||
// Check for a response group open.
|
||||
if ($char === '[') {
|
||||
$this->advance();
|
||||
|
||||
return new ResponseCodeOpen('[');
|
||||
}
|
||||
|
||||
// Check for response group close.
|
||||
if ($char === ']') {
|
||||
$this->advance();
|
||||
|
||||
return new ResponseCodeClose(']');
|
||||
}
|
||||
|
||||
// Check for angle bracket open (email addresses).
|
||||
if ($char === '<') {
|
||||
$this->advance();
|
||||
|
||||
return $this->readEmailAddress();
|
||||
}
|
||||
|
||||
// Check for quoted string.
|
||||
if ($char === '"') {
|
||||
return $this->readQuotedString();
|
||||
}
|
||||
|
||||
// Check for literal block open.
|
||||
if ($char === '{') {
|
||||
return $this->readLiteral();
|
||||
}
|
||||
|
||||
// Otherwise, parse a number or atom.
|
||||
return $this->readNumberOrAtom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips whitespace characters (spaces and tabs only, preserving CRLF).
|
||||
*/
|
||||
protected function skipWhitespace(): void
|
||||
{
|
||||
while (true) {
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
// Break on EOF.
|
||||
if ($char === null || $char === '') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Break on CRLF.
|
||||
if ($char === "\r" || $char === "\n") {
|
||||
break;
|
||||
}
|
||||
|
||||
// Break on non-whitespace.
|
||||
if ($char !== ' ' && $char !== "\t") {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a quoted string token.
|
||||
*
|
||||
* Quoted strings are enclosed in double quotes and may contain escaped characters.
|
||||
*/
|
||||
protected function readQuotedString(): QuotedString
|
||||
{
|
||||
// Skip the opening quote.
|
||||
$this->advance();
|
||||
|
||||
$value = '';
|
||||
|
||||
while (true) {
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
if ($char === null) {
|
||||
throw new ImapParserException(sprintf(
|
||||
'Unterminated quoted string at buffer offset %d. Buffer: "%s"',
|
||||
$this->position,
|
||||
substr($this->buffer, max(0, $this->position - 10), 20)
|
||||
));
|
||||
}
|
||||
|
||||
if ($char === '\\') {
|
||||
$this->advance(); // Skip the backslash.
|
||||
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$escapedChar = $this->currentChar();
|
||||
|
||||
if ($escapedChar === null) {
|
||||
throw new ImapParserException('Unterminated escape sequence in quoted string');
|
||||
}
|
||||
|
||||
$value .= $escapedChar;
|
||||
|
||||
$this->advance();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === '"') {
|
||||
$this->advance(); // Skip the closing quote.
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$value .= $char;
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
return new QuotedString($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a literal token.
|
||||
*
|
||||
* Literal blocks in IMAP have the form {<length>}\r\n<data>.
|
||||
*/
|
||||
protected function readLiteral(): Literal
|
||||
{
|
||||
// Skip the opening '{'.
|
||||
$this->advance();
|
||||
|
||||
// This will contain the size of the literal block in a sequence of digits.
|
||||
// {<size>}\r\n<data>
|
||||
$numStr = '';
|
||||
|
||||
while (true) {
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
if ($char === null) {
|
||||
throw new ImapParserException('Unterminated literal specifier');
|
||||
}
|
||||
|
||||
if ($char === '}') {
|
||||
$this->advance(); // Skip the '}'.
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$numStr .= $char;
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
// Expect carriage return after the literal specifier.
|
||||
$this->ensureBuffer(2);
|
||||
|
||||
// Get the carriage return.
|
||||
$crlf = substr($this->buffer, $this->position, 2);
|
||||
|
||||
if ($crlf !== "\r\n") {
|
||||
throw new ImapParserException('Expected CRLF after literal specifier');
|
||||
}
|
||||
|
||||
// Skip the CRLF.
|
||||
$this->advance(2);
|
||||
|
||||
$length = (int) $numStr;
|
||||
|
||||
// Use any data that is already in our buffer.
|
||||
$available = strlen($this->buffer) - $this->position;
|
||||
|
||||
if ($available >= $length) {
|
||||
$literal = substr($this->buffer, $this->position, $length);
|
||||
|
||||
$this->advance($length);
|
||||
} else {
|
||||
// Consume whatever is available without flushing the whole buffer.
|
||||
$literal = substr($this->buffer, $this->position);
|
||||
|
||||
$consumed = strlen($literal);
|
||||
|
||||
// Advance the pointer by the number of bytes we took.
|
||||
$this->advance($consumed);
|
||||
|
||||
// Calculate how many bytes are still needed.
|
||||
$remaining = $length - $consumed;
|
||||
|
||||
// Read the missing bytes from the stream.
|
||||
$data = $this->stream->read($remaining);
|
||||
|
||||
if ($data === false || strlen($data) !== $remaining) {
|
||||
throw new ImapStreamException('Unexpected end of stream while trying to fill the buffer');
|
||||
}
|
||||
|
||||
$literal .= $data;
|
||||
}
|
||||
|
||||
// Verify that the literal length matches the expected length.
|
||||
if (strlen($literal) !== $length) {
|
||||
throw new ImapParserException(sprintf(
|
||||
'Literal length mismatch: expected %d, got %d',
|
||||
$length,
|
||||
strlen($literal)
|
||||
));
|
||||
}
|
||||
|
||||
return new Literal($literal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a number or atom token.
|
||||
*/
|
||||
protected function readNumberOrAtom(): Token
|
||||
{
|
||||
$position = $this->position;
|
||||
|
||||
// First char must be a digit to even consider a number.
|
||||
if (! ctype_digit($this->buffer[$position] ?? '')) {
|
||||
return $this->readAtom();
|
||||
}
|
||||
|
||||
// Walk forward to find the end of the digit run.
|
||||
while (ctype_digit($this->buffer[$position] ?? '')) {
|
||||
$position++;
|
||||
|
||||
$this->ensureBuffer($position - $this->position + 1);
|
||||
}
|
||||
|
||||
$next = $this->buffer[$position] ?? null;
|
||||
|
||||
// If next is EOF or a delimiter, it's a Number.
|
||||
if ($next === null || $this->isDelimiter($next)) {
|
||||
return $this->readNumber();
|
||||
}
|
||||
|
||||
// Otherwise it's an Atom.
|
||||
return $this->readAtom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a number token.
|
||||
*
|
||||
* A number consists of one or more digit characters and represents a numeric value.
|
||||
*/
|
||||
protected function readNumber(): Number
|
||||
{
|
||||
$start = $this->position;
|
||||
|
||||
while (true) {
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
if ($char === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! ctype_digit($char)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
return new Number(substr($this->buffer, $start, $this->position - $start));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an atom token.
|
||||
*
|
||||
* ATOMs are sequences of printable ASCII characters that do not contain delimiters.
|
||||
*/
|
||||
protected function readAtom(): Atom
|
||||
{
|
||||
$value = '';
|
||||
|
||||
while (true) {
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
if ($char === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $this->isValidAtomCharacter($char)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$value .= $char;
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
if (strcasecmp($value, 'NIL') === 0) {
|
||||
return new Nil($value);
|
||||
}
|
||||
|
||||
return new Atom($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an email address token enclosed in angle brackets.
|
||||
*
|
||||
* Email addresses are enclosed in angle brackets ("<" and ">").
|
||||
*
|
||||
* For example "<johndoe@email.com>"
|
||||
*/
|
||||
protected function readEmailAddress(): ?EmailAddress
|
||||
{
|
||||
$value = '';
|
||||
|
||||
while (true) {
|
||||
$this->ensureBuffer(1);
|
||||
|
||||
$char = $this->currentChar();
|
||||
|
||||
if ($char === null) {
|
||||
throw new ImapParserException('Unterminated email address, expected ">"');
|
||||
}
|
||||
|
||||
if ($char === '>') {
|
||||
$this->advance(); // Skip the closing '>'.
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$value .= $char;
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
return new EmailAddress($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that at least the given length in characters are available in the buffer.
|
||||
*/
|
||||
protected function ensureBuffer(int $length): void
|
||||
{
|
||||
// If we have enough data in the buffer, return early.
|
||||
while ((strlen($this->buffer) - $this->position) < $length) {
|
||||
$data = $this->stream->fgets();
|
||||
|
||||
if ($data === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer .= $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current character in the buffer.
|
||||
*/
|
||||
protected function currentChar(): ?string
|
||||
{
|
||||
return $this->buffer[$this->position] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the internal pointer by $n characters.
|
||||
*/
|
||||
protected function advance(int $n = 1): void
|
||||
{
|
||||
$this->position += $n;
|
||||
|
||||
// If we have consumed the entire buffer, reset it.
|
||||
if ($this->position >= strlen($this->buffer)) {
|
||||
$this->flushBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the buffer and reset the position.
|
||||
*/
|
||||
protected function flushBuffer(): void
|
||||
{
|
||||
$this->buffer = '';
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given character is a valid atom character.
|
||||
*/
|
||||
protected function isValidAtomCharacter(string $char): bool
|
||||
{
|
||||
// Get the ASCII code.
|
||||
$code = ord($char);
|
||||
|
||||
// Allow only printable ASCII (32-126).
|
||||
if ($code < 32 || $code > 126) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delimiters are not allowed inside ATOMs.
|
||||
if ($this->isDelimiter($char)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given character is a delimiter for tokenizing responses.
|
||||
*/
|
||||
protected function isDelimiter(string $char): bool
|
||||
{
|
||||
// This delimiter list includes additional characters (such as square
|
||||
// brackets, curly braces, and angle brackets) to ensure that tokens
|
||||
// like the response code group brackets are split out. This is fine
|
||||
// for tokenizing responses, even though it’s more restrictive
|
||||
// than the IMAP atom definition in RFC 3501 (section 9).
|
||||
return in_array($char, [' ', '(', ')', '[', ']', '{', '}', '<', '>'], true);
|
||||
}
|
||||
}
|
||||
14
libs/vendor/directorytree/imapengine/src/Connection/Loggers/EchoLogger.php
vendored
Normal file
14
libs/vendor/directorytree/imapengine/src/Connection/Loggers/EchoLogger.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Loggers;
|
||||
|
||||
class EchoLogger extends Logger
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function write(string $message): void
|
||||
{
|
||||
echo $message;
|
||||
}
|
||||
}
|
||||
21
libs/vendor/directorytree/imapengine/src/Connection/Loggers/FileLogger.php
vendored
Normal file
21
libs/vendor/directorytree/imapengine/src/Connection/Loggers/FileLogger.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Loggers;
|
||||
|
||||
class FileLogger extends Logger
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $path
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function write(string $message): void
|
||||
{
|
||||
file_put_contents($this->path, $message, FILE_APPEND);
|
||||
}
|
||||
}
|
||||
35
libs/vendor/directorytree/imapengine/src/Connection/Loggers/Logger.php
vendored
Normal file
35
libs/vendor/directorytree/imapengine/src/Connection/Loggers/Logger.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Loggers;
|
||||
|
||||
abstract class Logger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* Write a message to the log.
|
||||
*/
|
||||
abstract protected function write(string $message): void;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function sent(string $message): void
|
||||
{
|
||||
$this->write(sprintf('%s: >> %s', $this->date(), $message).PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function received(string $message): void
|
||||
{
|
||||
$this->write(sprintf('%s: << %s', $this->date(), $message).PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date and time.
|
||||
*/
|
||||
protected function date(): string
|
||||
{
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
16
libs/vendor/directorytree/imapengine/src/Connection/Loggers/LoggerInterface.php
vendored
Normal file
16
libs/vendor/directorytree/imapengine/src/Connection/Loggers/LoggerInterface.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Loggers;
|
||||
|
||||
interface LoggerInterface
|
||||
{
|
||||
/**
|
||||
* Log when a message is sent.
|
||||
*/
|
||||
public function sent(string $message): void;
|
||||
|
||||
/**
|
||||
* Log when a message is received.
|
||||
*/
|
||||
public function received(string $message): void;
|
||||
}
|
||||
14
libs/vendor/directorytree/imapengine/src/Connection/Loggers/RayLogger.php
vendored
Normal file
14
libs/vendor/directorytree/imapengine/src/Connection/Loggers/RayLogger.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Loggers;
|
||||
|
||||
class RayLogger extends Logger
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function write(string $message): void
|
||||
{
|
||||
ray($message);
|
||||
}
|
||||
}
|
||||
15
libs/vendor/directorytree/imapengine/src/Connection/RawQueryValue.php
vendored
Normal file
15
libs/vendor/directorytree/imapengine/src/Connection/RawQueryValue.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use Stringable;
|
||||
|
||||
class RawQueryValue
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Stringable|string $value
|
||||
) {}
|
||||
}
|
||||
18
libs/vendor/directorytree/imapengine/src/Connection/Responses/ContinuationResponse.php
vendored
Normal file
18
libs/vendor/directorytree/imapengine/src/Connection/Responses/ContinuationResponse.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
|
||||
class ContinuationResponse extends Response
|
||||
{
|
||||
/**
|
||||
* Get the data tokens.
|
||||
*
|
||||
* @return Token[]
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return array_slice($this->tokens, 1);
|
||||
}
|
||||
}
|
||||
73
libs/vendor/directorytree/imapengine/src/Connection/Responses/Data/Data.php
vendored
Normal file
73
libs/vendor/directorytree/imapengine/src/Connection/Responses/Data/Data.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses\Data;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\HasTokens;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
use Stringable;
|
||||
|
||||
abstract class Data implements Stringable
|
||||
{
|
||||
use HasTokens;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $tokens
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the tokens.
|
||||
*
|
||||
* @return Token[]|Data[]
|
||||
*/
|
||||
public function tokens(): array
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first token.
|
||||
*/
|
||||
public function first(): Token|Data|null
|
||||
{
|
||||
return $this->tokens[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last token.
|
||||
*/
|
||||
public function last(): Token|Data|null
|
||||
{
|
||||
return $this->tokens[count($this->tokens) - 1] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the data contains a specific value.
|
||||
*/
|
||||
public function contains(array|string $needles): bool
|
||||
{
|
||||
$haystack = $this->values();
|
||||
|
||||
foreach ((array) $needles as $needle) {
|
||||
if (! in_array($needle, $haystack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the token's values.
|
||||
*/
|
||||
public function values(): array
|
||||
{
|
||||
return array_map(function (Token|Data $token) {
|
||||
return $token instanceof Data
|
||||
? $token->values()
|
||||
: $token->value;
|
||||
}, $this->tokens);
|
||||
}
|
||||
}
|
||||
48
libs/vendor/directorytree/imapengine/src/Connection/Responses/Data/ListData.php
vendored
Normal file
48
libs/vendor/directorytree/imapengine/src/Connection/Responses/Data/ListData.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses\Data;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
|
||||
class ListData extends Data
|
||||
{
|
||||
/**
|
||||
* Find the immediate successor token of the given field in the list.
|
||||
*/
|
||||
public function lookup(string $field): Data|Token|null
|
||||
{
|
||||
foreach ($this->tokens as $index => $token) {
|
||||
if ((string) $token === $field) {
|
||||
return $this->tokenAt(++$index);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert alternating key/value tokens to an associative array.
|
||||
*/
|
||||
public function toKeyValuePairs(): array
|
||||
{
|
||||
$pairs = [];
|
||||
|
||||
for ($i = 0; $i < count($this->tokens) - 1; $i += 2) {
|
||||
$key = strtolower($this->tokens[$i]->value);
|
||||
|
||||
$pairs[$key] = $this->tokens[$i + 1]->value;
|
||||
}
|
||||
|
||||
return $pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list as a string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('(%s)', implode(
|
||||
' ', array_map('strval', $this->tokens)
|
||||
));
|
||||
}
|
||||
}
|
||||
16
libs/vendor/directorytree/imapengine/src/Connection/Responses/Data/ResponseCodeData.php
vendored
Normal file
16
libs/vendor/directorytree/imapengine/src/Connection/Responses/Data/ResponseCodeData.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses\Data;
|
||||
|
||||
class ResponseCodeData extends Data
|
||||
{
|
||||
/**
|
||||
* Get the group as a string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('[%s]', implode(
|
||||
' ', array_map('strval', $this->tokens)
|
||||
));
|
||||
}
|
||||
}
|
||||
32
libs/vendor/directorytree/imapengine/src/Connection/Responses/HasTokens.php
vendored
Normal file
32
libs/vendor/directorytree/imapengine/src/Connection/Responses/HasTokens.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\Data;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
|
||||
trait HasTokens
|
||||
{
|
||||
/**
|
||||
* Get the response tokens.
|
||||
*
|
||||
* @return Token[]|Data[]
|
||||
*/
|
||||
abstract public function tokens(): array;
|
||||
|
||||
/**
|
||||
* Get the response token at the given index.
|
||||
*/
|
||||
public function tokenAt(int $index): Token|Data|null
|
||||
{
|
||||
return $this->tokens()[$index] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response tokens after the given index.
|
||||
*/
|
||||
public function tokensAfter(int $index): array
|
||||
{
|
||||
return array_slice($this->tokens(), $index);
|
||||
}
|
||||
}
|
||||
28
libs/vendor/directorytree/imapengine/src/Connection/Responses/MessageResponseParser.php
vendored
Normal file
28
libs/vendor/directorytree/imapengine/src/Connection/Responses/MessageResponseParser.php
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\ResponseCodeData;
|
||||
|
||||
class MessageResponseParser
|
||||
{
|
||||
/**
|
||||
* Get the UID from a tagged move or copy response.
|
||||
*/
|
||||
public static function getUidFromCopy(TaggedResponse $response): ?int
|
||||
{
|
||||
if (! $data = $response->tokenAt(2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $data instanceof ResponseCodeData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $value = $data->tokenAt(3)?->value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
50
libs/vendor/directorytree/imapengine/src/Connection/Responses/Response.php
vendored
Normal file
50
libs/vendor/directorytree/imapengine/src/Connection/Responses/Response.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Data\Data;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Stringable;
|
||||
|
||||
class Response implements Arrayable, Stringable
|
||||
{
|
||||
use HasTokens;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $tokens
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the response tokens.
|
||||
*
|
||||
* @return Token[]|Data[]
|
||||
*/
|
||||
public function tokens(): array
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_map(function (Token|Data $token) {
|
||||
return $token instanceof Data
|
||||
? $token->values()
|
||||
: $token->value;
|
||||
}, $this->tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the response tokens.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode(' ', $this->tokens);
|
||||
}
|
||||
}
|
||||
52
libs/vendor/directorytree/imapengine/src/Connection/Responses/TaggedResponse.php
vendored
Normal file
52
libs/vendor/directorytree/imapengine/src/Connection/Responses/TaggedResponse.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Number;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Token;
|
||||
|
||||
class TaggedResponse extends Response
|
||||
{
|
||||
/**
|
||||
* Get the response tag.
|
||||
*/
|
||||
public function tag(): Atom|Number
|
||||
{
|
||||
return $this->tokens[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response status token.
|
||||
*/
|
||||
public function status(): Atom
|
||||
{
|
||||
return $this->tokens[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data tokens.
|
||||
*
|
||||
* @return Token[]
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return array_slice($this->tokens, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the response was successful.
|
||||
*/
|
||||
public function successful(): bool
|
||||
{
|
||||
return strtoupper($this->status()->value) === 'OK';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the response failed.
|
||||
*/
|
||||
public function failed(): bool
|
||||
{
|
||||
return in_array(strtoupper($this->status()->value), ['NO', 'BAD']);
|
||||
}
|
||||
}
|
||||
27
libs/vendor/directorytree/imapengine/src/Connection/Responses/UntaggedResponse.php
vendored
Normal file
27
libs/vendor/directorytree/imapengine/src/Connection/Responses/UntaggedResponse.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Responses;
|
||||
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Number;
|
||||
|
||||
class UntaggedResponse extends Response
|
||||
{
|
||||
/**
|
||||
* Get the response type token.
|
||||
*/
|
||||
public function type(): Atom|Number
|
||||
{
|
||||
return $this->tokens[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data tokens.
|
||||
*
|
||||
* @return Atom[]
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return array_slice($this->tokens, 2);
|
||||
}
|
||||
}
|
||||
41
libs/vendor/directorytree/imapengine/src/Connection/Result.php
vendored
Normal file
41
libs/vendor/directorytree/imapengine/src/Connection/Result.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection;
|
||||
|
||||
use DirectoryTree\ImapEngine\Collections\ResponseCollection;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\Response;
|
||||
|
||||
class Result
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected ImapCommand $command,
|
||||
protected array $responses = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the executed command.
|
||||
*/
|
||||
public function command(): ImapCommand
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a response to the result.
|
||||
*/
|
||||
public function addResponse(Response $response): void
|
||||
{
|
||||
$this->responses[] = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the recently received responses.
|
||||
*/
|
||||
public function responses(): ResponseCollection
|
||||
{
|
||||
return new ResponseCollection($this->responses);
|
||||
}
|
||||
}
|
||||
232
libs/vendor/directorytree/imapengine/src/Connection/Streams/FakeStream.php
vendored
Normal file
232
libs/vendor/directorytree/imapengine/src/Connection/Streams/FakeStream.php
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Streams;
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use RuntimeException;
|
||||
|
||||
class FakeStream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* Lines queued for testing; each call to fgets() pops the next line.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $buffer = [];
|
||||
|
||||
/**
|
||||
* Data that has been "written" to this fake stream (for assertion).
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $written = [];
|
||||
|
||||
/**
|
||||
* The connection info.
|
||||
*/
|
||||
protected ?array $connection = null;
|
||||
|
||||
/**
|
||||
* The mock meta info.
|
||||
*/
|
||||
protected array $meta = [
|
||||
'crypto' => [
|
||||
'protocol' => '',
|
||||
'cipher_name' => '',
|
||||
'cipher_bits' => 0,
|
||||
'cipher_version' => '',
|
||||
],
|
||||
'mode' => 'c',
|
||||
'eof' => false,
|
||||
'blocked' => false,
|
||||
'timed_out' => false,
|
||||
'seekable' => false,
|
||||
'unread_bytes' => 0,
|
||||
'stream_type' => 'tcp_socket/unknown',
|
||||
];
|
||||
|
||||
/**
|
||||
* Feed a line to the stream buffer with a newline character.
|
||||
*/
|
||||
public function feed(array|string $lines): self
|
||||
{
|
||||
// We'll ensure that each line ends with a CRLF,
|
||||
// as this is the expected behavior of every
|
||||
// reply that comes from an IMAP server.
|
||||
$lines = array_map(fn (string $line) => (
|
||||
rtrim($line, "\r\n")."\r\n"
|
||||
), (array) $lines);
|
||||
|
||||
array_push($this->buffer, ...$lines);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed a raw line to the stream buffer.
|
||||
*/
|
||||
public function feedRaw(array|string $lines): self
|
||||
{
|
||||
array_push($this->buffer, ...(array) $lines);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timed out status.
|
||||
*/
|
||||
public function setMeta(string $attribute, mixed $value): self
|
||||
{
|
||||
if (! isset($this->meta[$attribute])) {
|
||||
throw new RuntimeException(
|
||||
"Unknown metadata attribute: {$attribute}"
|
||||
);
|
||||
}
|
||||
|
||||
if (gettype($this->meta[$attribute]) !== gettype($value)) {
|
||||
throw new RuntimeException(
|
||||
"Metadata attribute {$attribute} must be of type ".gettype($this->meta[$attribute])
|
||||
);
|
||||
}
|
||||
|
||||
$this->meta[$attribute] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function open(?string $transport = null, ?string $host = null, ?int $port = null, ?int $timeout = null, array $options = []): bool
|
||||
{
|
||||
$this->connection = compact('transport', 'host', 'port', 'timeout', 'options');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->buffer = [];
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function read(int $length): string|false
|
||||
{
|
||||
if (! $this->opened()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->meta['eof'] && empty($this->buffer)) {
|
||||
return false; // EOF and no data left. Indicate end of stream.
|
||||
}
|
||||
|
||||
$data = implode('', $this->buffer);
|
||||
|
||||
$availableLength = strlen($data);
|
||||
|
||||
if ($availableLength === 0) {
|
||||
// No data available right now (but not EOF).
|
||||
// Simulate non-blocking behavior.
|
||||
return '';
|
||||
}
|
||||
|
||||
$bytesToRead = min($length, $availableLength);
|
||||
|
||||
$result = substr($data, 0, $bytesToRead);
|
||||
|
||||
$remainingData = substr($data, $bytesToRead);
|
||||
|
||||
$this->buffer = $remainingData !== '' ? [$remainingData] : [];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fgets(): string|false
|
||||
{
|
||||
if (! $this->opened()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simulate timeout/eof checks.
|
||||
if ($this->meta['timed_out'] || $this->meta['eof']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_shift($this->buffer) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fwrite(string $data): int|false
|
||||
{
|
||||
if (! $this->opened()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->written[] = $data;
|
||||
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function meta(): array
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function opened(): bool
|
||||
{
|
||||
return (bool) $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setTimeout(int $seconds): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setSocketSetCrypto(bool $enabled, ?int $method): bool|int
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given data was written to the stream.
|
||||
*/
|
||||
public function assertWritten(string $string): void
|
||||
{
|
||||
$found = false;
|
||||
|
||||
foreach ($this->written as $index => $written) {
|
||||
if (str_contains($written, $string)) {
|
||||
unset($this->written[$index]);
|
||||
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert::assertTrue($found, "Failed asserting that the string '{$string}' was written to the stream.");
|
||||
}
|
||||
}
|
||||
120
libs/vendor/directorytree/imapengine/src/Connection/Streams/ImapStream.php
vendored
Normal file
120
libs/vendor/directorytree/imapengine/src/Connection/Streams/ImapStream.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Streams;
|
||||
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionFailedException;
|
||||
|
||||
class ImapStream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* The underlying PHP stream resource.
|
||||
*
|
||||
* @var resource|null
|
||||
*/
|
||||
protected mixed $stream = null;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function open(string $transport, string $host, int $port, int $timeout, array $options = []): bool
|
||||
{
|
||||
$this->stream = @stream_socket_client(
|
||||
$address = "{$transport}://{$host}:{$port}",
|
||||
$errno,
|
||||
$errstr,
|
||||
$timeout,
|
||||
STREAM_CLIENT_CONNECT,
|
||||
stream_context_create($options)
|
||||
);
|
||||
|
||||
if (! $this->stream) {
|
||||
throw new ImapConnectionFailedException("Unable to connect to {$address} ({$errstr})", $errno);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if ($this->opened()) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
|
||||
$this->stream = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function read(int $length): string|false
|
||||
{
|
||||
if (! $this->opened()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
|
||||
while (strlen($data) < $length && ! feof($this->stream)) {
|
||||
$chunk = fread($this->stream, $length - strlen($data));
|
||||
|
||||
if ($chunk === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data .= $chunk;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fgets(): string|false
|
||||
{
|
||||
return $this->opened() ? fgets($this->stream) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fwrite(string $data): int|false
|
||||
{
|
||||
return $this->opened() ? fwrite($this->stream, $data) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function meta(): array
|
||||
{
|
||||
return $this->opened() ? stream_get_meta_data($this->stream) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function opened(): bool
|
||||
{
|
||||
return is_resource($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setTimeout(int $seconds): bool
|
||||
{
|
||||
return stream_set_timeout($this->stream, $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setSocketSetCrypto(bool $enabled, ?int $method): bool|int
|
||||
{
|
||||
return stream_socket_enable_crypto($this->stream, $enabled, $method);
|
||||
}
|
||||
}
|
||||
51
libs/vendor/directorytree/imapengine/src/Connection/Streams/StreamInterface.php
vendored
Normal file
51
libs/vendor/directorytree/imapengine/src/Connection/Streams/StreamInterface.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Streams;
|
||||
|
||||
interface StreamInterface
|
||||
{
|
||||
/**
|
||||
* Open the underlying stream.
|
||||
*/
|
||||
public function open(string $transport, string $host, int $port, int $timeout, array $options = []): bool;
|
||||
|
||||
/**
|
||||
* Close the underlying stream.
|
||||
*/
|
||||
public function close(): void;
|
||||
|
||||
/**
|
||||
* Read data from the stream.
|
||||
*/
|
||||
public function read(int $length): string|false;
|
||||
|
||||
/**
|
||||
* Read a single line from the stream.
|
||||
*/
|
||||
public function fgets(): string|false;
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*/
|
||||
public function fwrite(string $data): int|false;
|
||||
|
||||
/**
|
||||
* Return meta info (like stream_get_meta_data).
|
||||
*/
|
||||
public function meta(): array;
|
||||
|
||||
/**
|
||||
* Determine if the stream is open.
|
||||
*/
|
||||
public function opened(): bool;
|
||||
|
||||
/**
|
||||
* Set the timeout on the stream.
|
||||
*/
|
||||
public function setTimeout(int $seconds): bool;
|
||||
|
||||
/**
|
||||
* Set encryption state on an already connected socked.
|
||||
*/
|
||||
public function setSocketSetCrypto(bool $enabled, ?int $method): bool|int;
|
||||
}
|
||||
8
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Atom.php
vendored
Normal file
8
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Atom.php
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
/**
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-atom
|
||||
*/
|
||||
class Atom extends Token {}
|
||||
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Crlf.php
vendored
Normal file
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Crlf.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class Crlf extends Token {}
|
||||
14
libs/vendor/directorytree/imapengine/src/Connection/Tokens/EmailAddress.php
vendored
Normal file
14
libs/vendor/directorytree/imapengine/src/Connection/Tokens/EmailAddress.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class EmailAddress extends Token
|
||||
{
|
||||
/**
|
||||
* Get the token's value.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return '<'.$this->value.'>';
|
||||
}
|
||||
}
|
||||
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ListClose.php
vendored
Normal file
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ListClose.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class ListClose extends Token {}
|
||||
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ListOpen.php
vendored
Normal file
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ListOpen.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class ListOpen extends Token {}
|
||||
14
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Literal.php
vendored
Normal file
14
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Literal.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class Literal extends Token
|
||||
{
|
||||
/**
|
||||
* Get the token's value.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf("{%d}\r\n%s", strlen($this->value), $this->value);
|
||||
}
|
||||
}
|
||||
8
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Nil.php
vendored
Normal file
8
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Nil.php
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
/**
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-nil
|
||||
*/
|
||||
class Nil extends Atom {}
|
||||
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Number.php
vendored
Normal file
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Number.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class Number extends Token {}
|
||||
14
libs/vendor/directorytree/imapengine/src/Connection/Tokens/QuotedString.php
vendored
Normal file
14
libs/vendor/directorytree/imapengine/src/Connection/Tokens/QuotedString.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class QuotedString extends Token
|
||||
{
|
||||
/**
|
||||
* Get the token's value.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return '"'.$this->value.'"';
|
||||
}
|
||||
}
|
||||
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ResponseCodeClose.php
vendored
Normal file
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ResponseCodeClose.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class ResponseCodeClose extends Token {}
|
||||
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ResponseCodeOpen.php
vendored
Normal file
5
libs/vendor/directorytree/imapengine/src/Connection/Tokens/ResponseCodeOpen.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
class ResponseCodeOpen extends Token {}
|
||||
39
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Token.php
vendored
Normal file
39
libs/vendor/directorytree/imapengine/src/Connection/Tokens/Token.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Connection\Tokens;
|
||||
|
||||
use Stringable;
|
||||
|
||||
abstract class Token implements Stringable
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Determine if the token is the given value.
|
||||
*/
|
||||
public function is(string $value): bool
|
||||
{
|
||||
return $this->value === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the token is not the given value.
|
||||
*/
|
||||
public function isNot(string $value): bool
|
||||
{
|
||||
return ! $this->is($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the token's value.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user