Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4364fad
Add incremental mailbox synchronization and extensible fetched data
stevebauman Sep 2, 2026
61c0b43
Support incremental synchronization through fetch modifiers
stevebauman Sep 2, 2026
95a67d8
Remove redundant connection FETCH shortcuts
stevebauman Sep 2, 2026
e5a2034
Unify IMAP operation identifiers and STORE modifiers
stevebauman Sep 2, 2026
b9f3499
Align connection parameters with IMAP command syntax
stevebauman Sep 2, 2026
67b5640
Send SORT charsets unquoted for server compatibility
stevebauman Sep 2, 2026
b9a7101
Adjust formatting
stevebauman Sep 2, 2026
4fd92fb
Spacing
stevebauman Sep 2, 2026
ba8ce90
Address incremental synchronization review feedback
stevebauman Sep 3, 2026
a4c13d0
Adjust param order
stevebauman Sep 3, 2026
ab63e17
Rename sequence set parser to fromSequenceSet
stevebauman Sep 3, 2026
0844b7b
Harden synchronization results and mailbox reconnects
stevebauman Sep 3, 2026
b5dda15
Extract SASL authentication exchange
stevebauman Sep 5, 2026
68e6ab6
Add fake logger assertions
stevebauman Sep 5, 2026
007487b
Refine capability and selection APIs
stevebauman Sep 5, 2026
cea1083
Fix code style
stevebauman Sep 5, 2026
cba52f6
Make mailbox capabilities immutable snapshots
stevebauman Sep 20, 2026
9494048
Handle selection capability and state edge cases
stevebauman Sep 20, 2026
fdafa28
Improve tokenizer method and enablement interface naming
stevebauman Sep 20, 2026
81822a9
Handle authentication replies within the exchange
stevebauman Sep 20, 2026
5889554
Sort cases
stevebauman Sep 20, 2026
8dcaf34
Adjust comments and spacing
stevebauman Sep 20, 2026
7fc93bf
Construct fake mailboxes through a typed factory
stevebauman Sep 20, 2026
58a3e0e
Validate IMAP arguments and centralize mailbox reset
stevebauman Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}
],
"require": {
"php": "^8.1",
"php": "^8.2",
"symfony/mime": ">=6.0",
"nesbot/carbon": ">=2.0",
"illuminate/collections": ">=9.0",
Expand Down
54 changes: 54 additions & 0 deletions src/Authentication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace DirectoryTree\ImapEngine;

use DirectoryTree\ImapEngine\Connection\ConnectionInterface;
use DirectoryTree\ImapEngine\Connection\Responses\TaggedResponse;
use Throwable;

class Authentication
{
/**
* Constructor.
*/
public function __construct(
protected ConnectionInterface $connection,
protected AuthenticatorInterface $authenticator,
) {}

/**
* Authenticate the connection.
*/
public function authenticate(bool $initial = false): TaggedResponse
{
$response = $this->authenticator->initial();

$sent = $initial && $response !== null;

$exchange = $this->connection->authenticate(
$this->authenticator->mechanism(),
$sent ? $response : null,
);

while ($exchange->valid()) {
$challenge = $exchange->current();

try {
if (! $sent && $response !== null) {
$answer = $response;
$sent = true;
} else {
$answer = $this->authenticator->respond($challenge);
}
} catch (Throwable $e) {
$this->connection->disconnect();

throw $e;
}

$exchange->send($answer);
}

return $exchange->getReturn();
}
}
40 changes: 40 additions & 0 deletions src/Authentication/XOAuth2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace DirectoryTree\ImapEngine\Authentication;

use DirectoryTree\ImapEngine\AuthenticatorInterface;

class XOAuth2 implements AuthenticatorInterface
{
/**
* Constructor.
*/
public function __construct(
protected string $user,
protected string $token,
) {}

/**
* {@inheritDoc}
*/
public function mechanism(): string
{
return 'XOAUTH2';
}

/**
* {@inheritDoc}
*/
public function initial(): string
{
return "user=$this->user\1auth=Bearer $this->token\1\1";
}

/**
* {@inheritDoc}
*/
public function respond(string $challenge): string
{
return '';
}
}
21 changes: 21 additions & 0 deletions src/AuthenticatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace DirectoryTree\ImapEngine;

interface AuthenticatorInterface
{
/**
* Get the SASL mechanism name.
*/
public function mechanism(): string;

/**
* Get the unencoded initial data, or null to await a challenge.
*/
public function initial(): ?string;

/**
* Respond to a decoded challenge. Return null to cancel authentication.
*/
public function respond(string $challenge): ?string;
}
93 changes: 93 additions & 0 deletions src/Capabilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace DirectoryTree\ImapEngine;

readonly class Capabilities
{
/**
* The mailbox capabilities.
*
* @var array<string, Capability>
*/
protected array $items;

/**
* Constructor.
*
* @param array<string, Capability> $items
*/
protected function __construct(array $items)
{
$this->items = $items;
}

/**
* Create a capability collection from the given items.
*/
public static function from(Capability ...$capabilities): static
{
$items = [];

foreach ($capabilities as $capability) {
$items[$capability->name()] = $capability;
}

return new static($items);
}

/**
* Get the capability items.
*
* @return array<string, Capability>
*/
public function items(): array
{
return $this->items;
}

/**
* Get all supported capabilities.
*/
public function all(): array
{
return array_keys($this->items);
}

/**
* Determine if the exact capability exists.
*/
public function has(string $capability): bool
{
return isset($this->items[strtoupper($capability)]);
}

/**
* Determine if the capability is supported.
*/
public function supports(string $capability): bool
{
return (bool) $this->find($capability);
}

/**
* Determine if the capability is enabled.
*/
public function enabled(string $capability): bool
{
return ($this->items[strtoupper($capability)] ?? null)?->enabled() ?? false;
}

/**
* Find a supported capability.
*/
protected function find(string $capability): ?Capability
{
foreach ($this->items as $item) {
if ($item->matches($capability)) {
return $item;
}
}

return null;
}
}
49 changes: 49 additions & 0 deletions src/Capability.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace DirectoryTree\ImapEngine;

readonly class Capability
{
/**
* Constructor.
*/
protected function __construct(
protected string $name,
protected bool $enabled = false,
) {}

/**
* Make a new capability instance.
*/
public static function make(string $name, bool $enabled = false): static
{
return new static(strtoupper($name), $enabled);
}

/**
* Get the capability name.
*/
public function name(): string
{
return $this->name;
}

/**
* Determine if the capability matches the given name.
*/
public function matches(string $capability): bool
{
$capability = strtoupper($capability);

return $this->name === $capability
|| str_starts_with($this->name, "{$capability}=");
}

/**
* Determine if the capability is enabled.
*/
public function enabled(): bool
{
return $this->enabled;
}
}
Loading
Loading