Dovecot supports

This commit is contained in:
djmaze 2021-11-19 17:08:30 +01:00
parent 4fdfd54f87
commit 796b10dc14
4 changed files with 99 additions and 41 deletions

View file

@ -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

View file

@ -113,6 +113,11 @@ class Folder
$this->aMetadata[$sName] = $sData;
}
public function SetAllMetadata(array $aMetadata) : void
{
$this->aMetadata = $aMetadata;
}
/**
* @return mixed
*/

View file

@ -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 {

View file

@ -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();