Skip to content

Commit ef9f4ba

Browse files
authored
Merge pull request #21 from DirectoryTree/bug-20
Bug 20 - IDLE fails with Invalid message number when message moved/deleted after receiving
2 parents ac21806 + 0aa7f9f commit ef9f4ba

File tree

4 files changed

+87
-8
lines changed

4 files changed

+87
-8
lines changed

src/Connection/ImapCommand.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@ public function __construct(
2222
protected array $tokens = [],
2323
) {}
2424

25+
/**
26+
* Get the IMAP tag.
27+
*/
28+
public function tag(): string
29+
{
30+
return $this->tag;
31+
}
32+
33+
/**
34+
* Get the IMAP command.
35+
*/
36+
public function command(): string
37+
{
38+
return $this->command;
39+
}
40+
41+
/**
42+
* Get the IMAP tokens.
43+
*/
44+
public function tokens(): array
45+
{
46+
return $this->tokens;
47+
}
48+
2549
/**
2650
* Compile the command into lines for transmission.
2751
*
@@ -62,7 +86,7 @@ public function compile(): array
6286
*/
6387
public function redacted(): ImapCommand
6488
{
65-
return new ImapCommand($this->tag, $this->command, array_map(
89+
return new static($this->tag, $this->command, array_map(
6690
function (mixed $token) {
6791
return is_array($token)
6892
? array_map(fn () => '[redacted]', $token)

src/Exceptions/ImapCommandException.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,42 @@
77

88
class ImapCommandException extends Exception
99
{
10+
/**
11+
* The IMAP response.
12+
*/
13+
protected Response $response;
14+
15+
/**
16+
* The failed IMAP command.
17+
*/
18+
protected ImapCommand $command;
19+
1020
/**
1121
* Make a new instance from a failed command and response.
1222
*/
1323
public static function make(ImapCommand $command, Response $response): static
1424
{
15-
return new static(sprintf('IMAP command "%s" failed. Response: "%s"', $command, $response));
25+
$exception = new static(sprintf('IMAP command "%s" failed. Response: "%s"', $command, $response));
26+
27+
$exception->command = $command;
28+
$exception->response = $response;
29+
30+
return $exception;
31+
}
32+
33+
/**
34+
* Get the failed IMAP command.
35+
*/
36+
public function command(): ImapCommand
37+
{
38+
return $this->command;
39+
}
40+
41+
/**
42+
* Get the IMAP response.
43+
*/
44+
public function response(): Response
45+
{
46+
return $this->response;
1647
}
1748
}

src/Folder.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use DirectoryTree\ImapEngine\Exceptions\Exception;
99
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
1010
use Illuminate\Contracts\Support\Arrayable;
11+
use Illuminate\Support\ItemNotFoundException;
1112
use JsonSerializable;
1213

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

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

src/MessageQuery.php

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
1010
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
1111
use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier;
12+
use DirectoryTree\ImapEngine\Exceptions\ImapCommandException;
1213
use DirectoryTree\ImapEngine\Pagination\LengthAwarePaginator;
1314
use DirectoryTree\ImapEngine\Support\ForwardsCalls;
1415
use DirectoryTree\ImapEngine\Support\Str;
@@ -531,9 +532,10 @@ public function paginate(int $perPage = 5, $page = null, string $pageName = 'pag
531532
*/
532533
public function findOrFail(int $id, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid)
533534
{
534-
$uid = $this->uid($id, $identifier)
535-
->firstOrFail() // Untagged response
536-
->tokenAt(3) // ListData
535+
/** @var UntaggedResponse $response */
536+
$response = $this->uid($id, $identifier)->firstOrFail();
537+
538+
$uid = $response->tokenAt(3) // ListData
537539
->tokenAt(1) // Atom
538540
->value; // UID
539541

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

559562
/**
560-
* Get the UID for theb given identifier.
563+
* Get the UID for the given identifier.
561564
*/
562565
protected function uid(int $id, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid): ResponseCollection
563566
{
564-
return $this->connection()->uid([$id], $identifier);
567+
try {
568+
return $this->connection()->uid([$id], $identifier);
569+
} catch (ImapCommandException $e) {
570+
// IMAP servers may return an error if the message number is not found.
571+
// If the identifier being used is a message number, and the message
572+
// number is in the command tokens, we can assume this has occurred
573+
// and safely ignore the error and return an empty collection.
574+
if (
575+
$identifier === ImapFetchIdentifier::MessageNumber
576+
&& in_array($id, $e->command()->tokens())
577+
) {
578+
return ResponseCollection::make();
579+
}
580+
581+
// Otherwise, re-throw the exception.
582+
throw $e;
583+
}
565584
}
566585

567586
/**

0 commit comments

Comments
 (0)