php-imap icon indicating copy to clipboard operation
php-imap copied to clipboard

RuntimeException: failed to read - connection closed? but Client::isConnected() returns true

Open catabozan opened this issue 3 years ago • 1 comments

Describe the bug vendor/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php:109: RuntimeException: failed to read - connection closed? when deleting a folder, the folder is deleted successfully, but the exception is still thrown. Also, the $client->isConnected() method returns true.

The RuntimeException only occurs when dealing with folders.

Used config Default config.

Code to Reproduce There are 3 classes involved in managing the connections.

  • User.php - contains the imap credentials
  • EmailClientProvider.php - a singleton that stores an array of all the Clients (there is a connection for each user)
  • EmailClient - handles the creation of the Client

Folder deletion

/** @var Client $mailbox */
$mailbox = $user->getMailboxAttribute();

$folderToDelete = $mailbox->getFolderByPath($request->folder->path);

$folderToDelete->delete(); // this line throws the error

User.php

class User 
{
    ...
    public function getMailboxAttribute(): Client
    {
        return EmailClientProvider::IMAPMailbox($this->emailCredentials);
    }
}

EmailClientProvider.php

   ...
    public static function IMAPMailbox(EmailCredentials $emailCredentials): Client
    {
        return self::getInstance()->getIMAPClient($emailCredentials);
    }

    protected function getIMAPClient(EmailCredentials $emailCredentials): Client
    {
        return $this->getEmailClient($emailCredentials)->IMAPMailbox();
    }

    // returns the email client and creates one if needed
    protected function getEmailClient(EmailCredentials $emailCredentials): EmailClient
    {
        if (! array_key_exists($emailCredentials->id, $this->emailClients)) {
            $this->emailClients[$emailCredentials->id] = new EmailClient($emailCredentials);
        }

        return $this->emailClients[$emailCredentials->id];
    }

EmailClient.php

    public function __construct(
        protected EmailCredentials $credentials,
    ) {
        ...
        $this->createAndCheckConnections();
    }
    protected function createAndCheckConnections(): void
    {
        $this->IMAPClient = $this->createIMAPClient();
        $this->SMTPClient = $this->createSMTPClient();
    }
    protected function createIMAPClient(): IMAPClient
    {
        $clientManager = new IMAPClientManager();

        $client = $clientManager->make([
            'host' => $this->credentials->imap_host,
            'port' => $this->credentials->imap_port,
            'protocol' => $this->credentials->imap_protocol,
            'encryption' => 'ssl',
            'validate_cert' => true,
            'username' => $this->credentials->imap_username,
            'password' => $this->credentials->imap_password,
        ]);

        $client->checkConnection(); // start the connection

        return $client;
    }

Expected behavior The expected behavior is that the folder gets deleted without an exception being thrown.

Desktop:

  • OS: Arch Linux x86_64
  • PHP: 8.1.3
  • Version 3.2.0 (same error happened with v2.7.2)
  • Provider: Roundcube Webmail (cPanel)

Additional context This is run within a Laravel app.

This is the test that throws the RuntimeException

    // create a user with some valid IMAP credentials
    $bob = createUserWithInbox(); 
    actingAs($bob);

    // fetch a random folder
    $mailbox = $bob->mailbox;
    $randomFolder = $mailbox->getFolders(false)
            ->filter(fn (Folder $folder) => str_contains($folder->path, 'testing'))
            ->random();
    
    // trigger folder deletion action
    $response = jsonApi()
        ->delete(route(
            'v1.email-folders.destroy',
            ['email_folder' => $randomFolder->path]
        ));
    
    assertNull($mailbox->getFolderByPath($randomFolder->path));

catabozan avatar Mar 10 '22 13:03 catabozan

Hi @catabozan,

thanks for the detailed error description!

The exception is thrown in the ImapProtocol::nextLine() method. So there is a problem reading the response after the deletion happened.

Now the question is which response could not be evaluated. If the flag $expunge is true (it is true by default) of Folder::delete($expunge = true), then directly after the DELETE an EXPUNGE command is sent to the server.

Could you turn on the debug mode and provide us with the output?

HelloSebastian avatar Mar 14 '22 17:03 HelloSebastian