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 0cd33b8d5..48df29988 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 @@ -47,6 +47,11 @@ class Folder */ private $aExtended = array(); + /** + * RFC 5464 + */ + private $aMetadata = array(); + /** * @throws \MailSo\Base\Exceptions\InvalidArgumentException */ @@ -139,4 +144,20 @@ class Folder { return isset($this->aExtended[$sName]) ? $this->aExtended[$sName] : null; } + + /** + * @param mixed $mData + */ + public function SetMetadata(string $sName, string $sData) : void + { + $this->aMetadata[$sName] = $sData; + } + + /** + * @return mixed + */ + public function GetMetadata(string $sName) : ?string + { + return isset($this->aMetadata[$sName]) ? $this->aMetadata[$sName] : null; + } } 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 6187c018a..e99626b81 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 @@ -304,6 +304,8 @@ class ImapClient extends \MailSo\Net\NetClient * URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED * I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH * LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY STATUS=SIZE LITERAL+ NOTIFY SPECIAL-USE + * STARTTLS AUTH= LOGIN LOGINDISABLED QUOTA + * METADATA METADATA-SERVER * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception @@ -463,7 +465,18 @@ class ImapClient extends \MailSo\Net\NetClient $bUseListStatus = false; } - return $this->SendRequestGetResponse($sCmd, $aParameters)->getFoldersResult($sCmd, $bUseListStatus); + $aReturn = $this->SendRequestGetResponse($sCmd, $aParameters)->getFoldersResult($sCmd, $bUseListStatus); + + // RFC 5464 + if ($this->IsSupported('METADATA')) { + foreach ($aReturn as $oFolder) { + foreach ($this->FolderGetMetadata($oFolder->FullNameRaw(), ['/shared', '/private'], ['DEPTH'=>'infinity']) as $key => $value) { + $oFolder->SetMetadata($key, $value); + } + } + } + + return $aReturn; } /** @@ -1717,13 +1730,96 @@ class ImapClient extends \MailSo\Net\NetClient return self::TAG_PREFIX.$this->iTagCount; } - public function EscapeString(string $sStringForEscape) : string + public function EscapeString(?string $sStringForEscape) : string { - return '"'.\str_replace(array('\\', '"'), array('\\\\', '\\"'), $sStringForEscape).'"'; + if (null === $sStringForEscape) { + return 'NIL'; + } +/* + // literal-string + if (\preg_match('/[\r\n\x00\x80-\xFF]/', $sStringForEscape)) { + return \sprintf("{%d}\r\n%s", \strlen($sStringForEscape), $sStringForEscape); + } +*/ + // quoted-string + return '"' . \addcslashes($sStringForEscape, '\\"') . '"'; } protected function getLogName() : string { return 'IMAP'; } + + /** + * RFC 5464 + */ + + const + ADMIN_SHARED = '/shared/admin', // Server + COMMENT_SHARED = '/shared/comment', // Server & Mailbox + COMMENT_PRIVATE = '/private/comment', // Mailbox + // RFC 6154 + SPECIALUSE_PRIVATE = '/private/specialuse', + // Kolab + KOLAB_CTYPE_KEY_SHARED = '/shared/vendor/kolab/folder-type', + KOLAB_CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type', + KOLAB_COLOR_KEY_SHARED = '/shared/vendor/kolab/color', + KOLAB_COLOR_KEY_PRIVATE = '/private/vendor/kolab/color', + KOLAB_NAME_KEY_SHARED = '/shared/vendor/kolab/displayname', + KOLAB_NAME_KEY_PRIVATE = '/private/vendor/kolab/displayname', + KOLAB_UID_KEY_SHARED = '/shared/vendor/kolab/uniqueid', + CYRUS_UID_KEY_SHARED = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + public function FolderGetMetadata(string $sFolderName, array $aEntries, array $aOptions = []) : array + { + if (!$this->IsSupported('METADATA') && !(!\strlen($sFolderName) && $this->IsSupported('METADATA-SERVER'))) { + return []; + } + + $arguments = []; + + if ($aOptions) { + $options = []; + $aOptions = \array_intersect_key( + \array_change_key_case($aOptions, CASE_UPPER), + ['MAXSIZE' => 0, 'DEPTH' => 0] + ); + if (isset($aOptions['MAXSIZE']) && 0 < \intval($aOptions['MAXSIZE'])) { + $options[] = 'MAXSIZE ' . \intval($aOptions['MAXSIZE']); + } + if (isset($aOptions['DEPTH']) && (1 == $aOptions['DEPTH'] || 'infinity' === $aOptions['DEPTH'])) { + $options[] = "DEPTH {$aOptions['DEPTH']}"; + } + if ($options) { + $arguments[] = '(' . \implode(' ', $options) . ')'; + } + } + + $arguments[] = $this->EscapeString($sFolderName); + + $arguments[] = '(' . \implode(' ', \array_map([$this, 'EscapeString'], $aEntries)) . ')'; + return $this->SendRequestGetResponse('GETMETADATA', $arguments)->getFolderMetadataResult(); + } + + public function FolderSetMetadata(string $sFolderName, array $aEntries) + { + if (!$aEntries) { + throw new \MailSo\Base\Exceptions\InvalidArgumentException("Wrong argument for SETMETADATA command"); + } + + $arguments = [$this->EscapeString($sFolderName)]; + + \array_walk($aEntries, function(&$v, $k){ + $v = $this->EscapeString($k) . ' ' . $this->EscapeString($v); + }); + $arguments[] = '(' . \implode(' ', $aEntries) . ')'; + + $result = $this->SendRequestGetResponse('SETMETADATA', $arguments); + } + + public function FolderDeleteMetadata($sFolderName, array $aEntries) + { + $this->SetMetadata($sFolderName, \array_fill_keys(\array_keys($aEntries), null)); + } + } 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 6e0c89d42..d7e37a3a3 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 @@ -119,6 +119,25 @@ class ResponseCollection extends \MailSo\Base\Collection return ''; } + public function getFolderMetadataResult() : array + { + $aReturn = array(); + foreach ($this 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) { + $value = $oResponse->ResponseList[3][$i+1]; + $aReturn[$oResponse->ResponseList[3][$i]] = ('NIL' === $value) ? null : $value; + } + } + } + return $aReturn; + } + public function getFoldersResult(string $sStatus, bool $bUseListStatus = false) : array { $aReturn = array();