Repository: DirectoryTree/ImapEngine (php)

Issue:
Errors thrown by ImapParser during Folder::idle
PHP 8.4.3
ImapEngine 1.0.1
Custom


I am getting some of these randomly:



```bash

************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in /vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 /vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 /vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 /vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 /vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 /vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 /vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 /listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}
```
I have enabled debug to provide more context to you when these happen.

Just catched one, see (they seem to be way more often that the very random ones I was getting before with your 5.x previous library):

```bash

2025-02-13 10:56:38: >> TAG3 SELECT "INBOX"
2025-02-13 10:56:38: << * FLAGS (\Answered \Flagged \Draft \Deleted \Seen)
2025-02-13 10:56:38: << * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
2025-02-13 10:56:38: << * 157 EXISTS
2025-02-13 10:56:38: << * 0 RECENT
2025-02-13 10:56:38: << * OK [UNSEEN 1]
2025-02-13 10:56:38: << * OK [UIDVALIDITY 1279614776]
2025-02-13 10:56:38: << * OK [UIDNEXT 301026]
2025-02-13 10:56:38: << TAG3 OK [READ-WRITE] Completed
2025-02-13 10:56:38: >> TAG4 IDLE
2025-02-13 10:56:38: << idling
2025-02-13 10:58:37: >> DONE
2025-02-13 10:58:37: << TAG4 OK Completed
2025-02-13 10:58:37: >> TAG5 LOGOUT


************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in phpengine/vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 phpengine/vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 phpengine/vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 phpengine/vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 phpengine/vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 phpengine/listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}******account ONE**********10:58:37

2025-02-13 10:58:37: >> DONE
2025-02-13 10:58:37: << TAG4 OK Completed
2025-02-13 10:58:37: >> TAG5 LOGOUT


************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in phpengine/vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 phpengine/vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 phpengine/vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 phpengine/vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 phpengine/vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 phpengine/listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}*****account TWO**********10:58:37

2025-02-13 10:58:38: >> DONE
2025-02-13 10:58:38: << TAG4 OK Completed
2025-02-13 10:58:38: >> TAG5 LOGOUT
```







_________________________ another one


```bash

Removing : 4465732
Removing : 44657312025-02-13 11:03:37: << * 2 RECENT
2025-02-13 11:03:38: >> DONE
2025-02-13 11:03:38: << TAG4 OK Completed
2025-02-13 11:03:38: >> TAG5 LOGOUT


************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in phpengine/vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 phpengine/vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 phpengine/vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 phpengine/vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 phpengine/vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 phpengine/listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}******account ONE**********11:03:38

2025-02-13 11:03:42: >> DONE
2025-02-13 11:03:42: << TAG4 OK Completed
2025-02-13 11:03:42: >> TAG5 LOGOUT


************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in phpengine/vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 phpengine/vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 phpengine/vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 phpengine/vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 phpengine/vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 phpengine/listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}******ACCOUNT TWO**********11:03:42

2025-02-13 11:03:48: << * 234 FETCH (FLAGS (\Recent \Seen))
2025-02-13 11:03:48: << * 234 FETCH (FLAGS (\Recent \Deleted \Seen))
2025-02-13 11:03:48: << * 234 EXPUNGE
2025-02-13 11:03:48: << * 233 EXISTS
2025-02-13 11:03:48: >> TAG54 SELECT "INBOX"
2025-02-13 11:03:48: << * 234 FETCH (FLAGS (\Seen))
2025-02-13 11:03:48: << * 234 FETCH (FLAGS (\Deleted \Seen))
2025-02-13 11:03:48: << * FLAGS (\Answered \Flagged \Draft \Deleted \Seen $Junk $Forwarded)
```


Some more context of the catches…

These always happen with I get TAG4 and then 5

Seem to happen when the messages are received between a few seconds from each other… these are different accounts but the same server.

I am running the library in forked processes from the same parent php code with pcntl… so there shall be no sharing from my side.
I think I sent you the high level code of how I am doing it.

Perhaps this can help the troubleshooting and fixing. Perhaps I need to run these differently and differentiate some more variables inside the fork?

Thanks.

```bash
2025-02-13 13:44:47: >> DONE
2025-02-13 13:44:47: << TAG4 OK Completed
2025-02-13 13:44:47: >> TAG5 LOGOUT


************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in /phpengine/vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 /phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 /phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 /phpengine/vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 /phpengine/vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 /phpengine/vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 /phpengine/vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 /phpengine/listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}******ACCOUNT 1**********13:44:47

2025-02-13 13:44:55: >> DONE
2025-02-13 13:44:55: << TAG4 OK Completed
2025-02-13 13:44:55: >> TAG5 LOGOUT


************CATCH Throwable DirectoryTree\ImapEngine\Exceptions\ImapParserException: Empty response in /phpengine/vendor/directorytree/imapengine/src/Connection/ImapParser.php:54
Stack trace:
#0 /phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(720): DirectoryTree\ImapEngine\Connection\ImapParser->next()
#1 /phpengine/vendor/directorytree/imapengine/src/Connection/ImapConnection.php(527): DirectoryTree\ImapEngine\Connection\ImapConnection->nextReply()
#2 /phpengine/vendor/directorytree/imapengine/src/Idle.php(149): DirectoryTree\ImapEngine\Connection\ImapConnection->idle(60)
#3 /phpengine/vendor/directorytree/imapengine/src/Idle.php(58): DirectoryTree\ImapEngine\Idle->idle()
#4 /phpengine/vendor/directorytree/imapengine/src/Idle.php(43): DirectoryTree\ImapEngine\Idle->listen(Object(Closure), Object(Carbon\Carbon))
#5 /phpengine/vendor/directorytree/imapengine/src/Folder.php(105): DirectoryTree\ImapEngine\Idle->await(Object(Closure))
#6 /phpengine/listener.php(181): DirectoryTree\ImapEngine\Folder->idle(Object(Closure), Object(Closure), 60)
#7 {main}******ACCOUNT TWO**********13:44:55
```




Pull request description:
Fix parser exception being thrown when there are no more tokens in response
Closes #13 

Patch:
diff --git a/src/Connection/ImapParser.php b/src/Connection/ImapParser.php
index 74dff1d..1bd0c86 100644
--- a/src/Connection/ImapParser.php
+++ b/src/Connection/ImapParser.php
@@ -45,13 +45,14 @@ class ImapParser
      */
     public function next(): Data|Token|Response|null
     {
-        // Load the first token.
+        // Attempt to load the first token.
         if (! $this->currentToken) {
             $this->advance();
         }
 
+        // No token was found, return null.
         if (! $this->currentToken) {
-            throw new ImapParserException('Empty response');
+            return null;
         }
 
         // If the token indicates the beginning of a list, parse it.


Test patch:
diff --git a/tests/Unit/Connection/ImapConnectionTest.php b/tests/Unit/Connection/ImapConnectionTest.php
index 78637a5..749b245 100644
--- a/tests/Unit/Connection/ImapConnectionTest.php
+++ b/tests/Unit/Connection/ImapConnectionTest.php
@@ -3,8 +3,8 @@
 use DirectoryTree\ImapEngine\Connection\ImapConnection;
 use DirectoryTree\ImapEngine\Connection\Streams\FakeStream;
 use DirectoryTree\ImapEngine\Exceptions\ImapCommandException;
+use DirectoryTree\ImapEngine\Exceptions\ImapConnectionException;
 use DirectoryTree\ImapEngine\Exceptions\ImapConnectionFailedException;
-use DirectoryTree\ImapEngine\Exceptions\ImapParserException;
 use DirectoryTree\ImapEngine\Support\Str;
 
 test('connect success', function () {
@@ -681,7 +681,7 @@ test('idle', function () {
 
     expect(function () use ($connection) {
         iterator_to_array($connection->idle(30));
-    })->toThrow(ImapParserException::class);
+    })->toThrow(ImapConnectionException::class);
 
     $stream->assertWritten('TAG1 IDLE');
 });