Skip to content

Commit

Permalink
Merge pull request #21 from DirectoryTree/bug-20
Browse files Browse the repository at this point in the history
Bug 20 - IDLE fails with Invalid message number when message moved/deleted after receiving
  • Loading branch information
stevebauman authored Feb 18, 2025
2 parents ac21806 + 0aa7f9f commit ef9f4ba
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 8 deletions.
26 changes: 25 additions & 1 deletion src/Connection/ImapCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ public function __construct(
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.
*
Expand Down Expand Up @@ -62,7 +86,7 @@ public function compile(): array
*/
public function redacted(): ImapCommand
{
return new ImapCommand($this->tag, $this->command, array_map(
return new static($this->tag, $this->command, array_map(
function (mixed $token) {
return is_array($token)
? array_map(fn () => '[redacted]', $token)
Expand Down
33 changes: 32 additions & 1 deletion src/Exceptions/ImapCommandException.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,42 @@

class ImapCommandException extends Exception
{
/**
* The IMAP response.
*/
protected Response $response;

/**
* The failed IMAP command.
*/
protected ImapCommand $command;

/**
* Make a new instance from a failed command and response.
*/
public static function make(ImapCommand $command, Response $response): static
{
return new static(sprintf('IMAP command "%s" failed. Response: "%s"', $command, $response));
$exception = new static(sprintf('IMAP command "%s" failed. Response: "%s"', $command, $response));

$exception->command = $command;
$exception->response = $response;

return $exception;
}

/**
* Get the failed IMAP command.
*/
public function command(): ImapCommand
{
return $this->command;
}

/**
* Get the IMAP response.
*/
public function response(): Response
{
return $this->response;
}
}
7 changes: 6 additions & 1 deletion src/Folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use DirectoryTree\ImapEngine\Exceptions\Exception;
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\ItemNotFoundException;
use JsonSerializable;

class Folder implements Arrayable, JsonSerializable
Expand Down Expand Up @@ -115,8 +116,12 @@ function (int $msgn) use ($callback, $fetch) {

try {
$message = $fetch($msgn);
} catch (ItemNotFoundException) {
// The message wasn't found. We will skip
// it and continue awaiting new messages.
return;
} catch (Exception) {
// If fetching the message fails, we'll attempt
// Something else happened. We will attempt
// reconnecting and re-fetching the message.
$this->mailbox->reconnect();

Expand Down
29 changes: 24 additions & 5 deletions src/MessageQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier;
use DirectoryTree\ImapEngine\Exceptions\ImapCommandException;
use DirectoryTree\ImapEngine\Pagination\LengthAwarePaginator;
use DirectoryTree\ImapEngine\Support\ForwardsCalls;
use DirectoryTree\ImapEngine\Support\Str;
Expand Down Expand Up @@ -531,9 +532,10 @@ public function paginate(int $perPage = 5, $page = null, string $pageName = 'pag
*/
public function findOrFail(int $id, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid)
{
$uid = $this->uid($id, $identifier)
->firstOrFail() // Untagged response
->tokenAt(3) // ListData
/** @var UntaggedResponse $response */
$response = $this->uid($id, $identifier)->firstOrFail();

$uid = $response->tokenAt(3) // ListData
->tokenAt(1) // Atom
->value; // UID

Expand All @@ -545,6 +547,7 @@ public function findOrFail(int $id, ImapFetchIdentifier $identifier = ImapFetchI
*/
public function find(int $id, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid): ?Message
{
/** @var UntaggedResponse $response */
if (! $response = $this->uid($id, $identifier)->first()) {
return null;
}
Expand All @@ -557,11 +560,27 @@ public function find(int $id, ImapFetchIdentifier $identifier = ImapFetchIdentif
}

/**
* Get the UID for theb given identifier.
* Get the UID for the given identifier.
*/
protected function uid(int $id, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid): ResponseCollection
{
return $this->connection()->uid([$id], $identifier);
try {
return $this->connection()->uid([$id], $identifier);
} catch (ImapCommandException $e) {
// IMAP servers may return an error if the message number is not found.
// If the identifier being used is a message number, and the message
// number is in the command tokens, we can assume this has occurred
// and safely ignore the error and return an empty collection.
if (
$identifier === ImapFetchIdentifier::MessageNumber
&& in_array($id, $e->command()->tokens())
) {
return ResponseCollection::make();
}

// Otherwise, re-throw the exception.
throw $e;
}
}

/**
Expand Down

0 comments on commit ef9f4ba

Please sign in to comment.