From 796b10dc146205c02f47a04a79a4b2f1cce809aa Mon Sep 17 00:00:00 2001 From: djmaze Date: Fri, 19 Nov 2021 17:08:30 +0100 Subject: [PATCH] Dovecot supports --- .../MailSo/Imap/Commands/Metadata.php | 52 ++++++++++++++- .../app/libraries/MailSo/Imap/Folder.php | 5 ++ .../app/libraries/MailSo/Imap/ImapClient.php | 65 ++++++++++++------- .../MailSo/Imap/ResponseCollection.php | 18 ----- 4 files changed, 99 insertions(+), 41 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Metadata.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Metadata.php index 4bf6d7cbd..6aaa7b0e9 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Metadata.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Metadata.php @@ -20,6 +20,40 @@ namespace MailSo\Imap\Commands; trait Metadata { + /** + * Dovecot 2.2+ supports fetching all METADATA at once (wildcard). + * RFC 5464 doesn't specify this, but its earlier draft did, and Kolab uses it. + */ + public function getAllMetadata() : array + { + $aReturn = array(); + try { + $arguments = [ + '(DEPTH infinity)', + $this->EscapeString('*') + ]; + $arguments[] = '(' . \implode(' ', \array_map([$this, 'EscapeString'], ['/shared', '/private'])) . ')'; + $oResult = $this->SendRequestGetResponse('GETMETADATA', $arguments); + foreach ($oResult as $oResponse) { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType + && 4 === \count($oResponse->ResponseList) + && 'METADATA' === $oResponse->ResponseList[1] + && \is_array($oResponse->ResponseList[3])) + { + $aMetadata = array(); + $c = \count($oResponse->ResponseList[3]); + for ($i = 0; $i < $c; $i += 2) { + $aMetadata[$oResponse->ResponseList[3][$i]] = $oResponse->ResponseList[3][$i+1]; + } + $aReturn[$oResponse->ResponseList[2]] = $aMetadata; + } + } + } catch (\Throwable $e) { + //\error_log($e->getMessage()); + } + return $aReturn; + } + public function getMetadata(string $sFolderName, array $aEntries, array $aOptions = []) : array { $arguments = []; @@ -44,7 +78,23 @@ trait Metadata $arguments[] = $this->EscapeString($sFolderName); $arguments[] = '(' . \implode(' ', \array_map([$this, 'EscapeString'], $aEntries)) . ')'; - return $this->SendRequestGetResponse('GETMETADATA', $arguments)->getFolderMetadataResult(); + + $oResult = $this->SendRequestGetResponse('GETMETADATA', $arguments); + + $aReturn = array(); + foreach ($oResult as $oResponse) { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType + && 4 === \count($oResponse->ResponseList) + && 'METADATA' === $oResponse->ResponseList[1] + && \is_array($oResponse->ResponseList[3])) + { + $c = \count($oResponse->ResponseList[3]); + for ($i = 0; $i < $c; $i += 2) { + $aReturn[$oResponse->ResponseList[3][$i]] = $oResponse->ResponseList[3][$i+1]; + } + } + } + return $aReturn; } public function ServerGetMetadata(array $aEntries, array $aOptions = []) : array diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php index 2af2e056d..dc25071a8 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php @@ -113,6 +113,11 @@ class Folder $this->aMetadata[$sName] = $sData; } + public function SetAllMetadata(array $aMetadata) : void + { + $this->aMetadata = $aMetadata; + } + /** * @return mixed */ diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index 903ca39dd..c421bec65 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -534,35 +534,30 @@ class ImapClient extends \MailSo\Net\NetClient $this->SendRequest($sCmd, $aParameters); $bPassthru = false; if ($bPassthru) { - // TODO: passthru to parse response in JavaScript - // This will reduce CPU time on server and moves it to the client - // And can be used with the new JavaScript AbstractFetchRemote.streamPerLine(fCallback, sGetAdd) - if (\is_resource($this->ConnectionResource())) { - \SnappyMail\HTTP\Stream::start(); - $sEndTag = $this->getCurrentTag(); - $sLine = \fgets($this->ConnectionResource()); - do { - echo $sLine; - if (0 === \strpos($sLine, $sEndTag)) { - break; - } - $sLine = \fgets($this->ConnectionResource()); - } while (\strlen($sLine)); - exit; - } + $this->streamResponse(); } else { $aReturn = $this->getResponse()->getFoldersResult($sCmd); } // RFC 5464 if (!$bIsSubscribeList && $this->IsSupported('METADATA')) { - foreach ($aReturn as $oFolder) { - try { - foreach ($this->getMetadata($oFolder->FullNameRaw(), ['/shared', '/private'], ['DEPTH'=>'infinity']) as $key => $value) { - $oFolder->SetMetadata($key, $value); + // Dovecot supports fetching all METADATA at once + $aMetadata = $this->getAllMetadata(); + if ($aMetadata) { + foreach ($aReturn as $oFolder) { + if (isset($aMetadata[$oFolder->FullNameRaw()])) { + $oFolder->SetAllMetadata($aMetadata[$oFolder->FullNameRaw()]); + } + } + } else { + foreach ($aReturn as $oFolder) { + try { + $oFolder->SetAllMetadata( + $this->getMetadata($oFolder->FullNameRaw(), ['/shared', '/private'], ['DEPTH'=>'infinity']) + ); + } catch (\Throwable $e) { + // Ignore error } - } catch (\Throwable $e) { - // Ignore error } } } @@ -1099,10 +1094,36 @@ class ImapClient extends \MailSo\Net\NetClient } /** + * TODO: passthru to parse response in JavaScript + * This will reduce CPU time on server and moves it to the client + * And can be used with the new JavaScript AbstractFetchRemote.streamPerLine(fCallback, sGetAdd) + * * @throws \MailSo\Imap\Exceptions\ResponseNotFoundException * @throws \MailSo\Imap\Exceptions\InvalidResponseException * @throws \MailSo\Imap\Exceptions\NegativeResponseException */ + private function streamResponse(string $sEndTag = null) : void + { + try { + if (\is_resource($this->ConnectionResource())) { + \SnappyMail\HTTP\Stream::start(); + $sEndTag = $sEndTag ?: $this->getCurrentTag(); + $sLine = \fgets($this->ConnectionResource()); + do { + echo $sLine; + if (0 === \strpos($sLine, $sEndTag)) { + break; + } + $sLine = \fgets($this->ConnectionResource()); + } while (\strlen($sLine)); + exit; + } + } catch (\Throwable $e) { + $this->writeLogException($e, \MailSo\Log\Enumerations\Type::WARNING); + throw $e; + } + } + private function getResponse(string $sEndTag = null) : ResponseCollection { try { diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ResponseCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ResponseCollection.php index 0f8bccc64..f0af06e70 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ResponseCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ResponseCollection.php @@ -88,24 +88,6 @@ class ResponseCollection extends \MailSo\Base\Collection return $aReturn; } - public function getFolderMetadataResult() : array - { - $aReturn = array(); - foreach ($this as $oResponse) { - if (Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType - && 4 === \count($oResponse->ResponseList) - && 'METADATA' === $oResponse->ResponseList[1] - && \is_array($oResponse->ResponseList[3])) - { - $c = \count($oResponse->ResponseList[3]); - for ($i = 0; $i < $c; $i += 2) { - $aReturn[$oResponse->ResponseList[3][$i]] = $oResponse->ResponseList[3][$i+1]; - } - } - } - return $aReturn; - } - public function getFoldersResult(string $sStatus) : array { $aReturn = array();