From ea1160aa4ed249ceff10dd268d46b8d9dd7dc3b8 Mon Sep 17 00:00:00 2001 From: Gunnar Wrobel
Date: Mon, 7 Sep 2009 20:52:13 +0200 Subject: [PATCH] Implemented IMAP METADATA (RFC5464) support for both the socket and cclient based drivers. The code should support both the final RFC version of the extension as well as a predecessor of the RFC called ANNOTATEMORE. Currently Cyrus Imapd implements ANNOTATEMORE. There is also a dovecot plugin. The IMAP METADATA extension is central to Kolab support. Added a mock driver with the primary objective of supporting unit tests for the Kolab drivers. --- .../Imap_Client/lib/Horde/Imap/Client/Base.php | 154 ++++ .../Imap_Client/lib/Horde/Imap/Client/Cclient.php | 70 ++ .../Imap_Client/lib/Horde/Imap/Client/Mock.php | 896 +++++++++++++++++++++ .../Imap_Client/lib/Horde/Imap/Client/Socket.php | 153 ++++ framework/Imap_Client/package.xml | 2 + .../Imap_Client/test/Horde/Imap/test_client.php | 24 + 6 files changed, 1299 insertions(+) create mode 100644 framework/Imap_Client/lib/Horde/Imap/Client/Mock.php diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php index 99403503c..f9fa8636d 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php @@ -2440,6 +2440,160 @@ abstract class Horde_Imap_Client_Base */ abstract protected function _getMyACLRights($mailbox); + /** + * Get metadata for a given mailbox. The server must support the + * IMAP METADATA extension (RFC 5464). + * + * @param string $mailbox A mailbox. Either in UTF7-IMAP or UTF-8. + * @param array $entries The entries to fetch. + * @param array $options Additional options: + *
+ * 'maxsize' - (int) The maximal size the returned values may have.
+ * This option is only available if 'annotatemore' has
+ * not been set.
+ * DEFAULT: No maximal size.
+ * 'depth' - (string) Either "0", "1" or "infinity". Returns only
+ * the given value ("0"), only values one level below
+ * the specified value ("1") or all entries below the
+ * specified value ("infinity").
+ * This option is only available if 'annotatemore' has
+ * not been set.
+ * DEFAULT: Unset which is equivalent to "0".
+ * 'nocapability' - (boolean) Do not check for the METADATA capability of
+ * the server ("true"). Otherwise the call will fail if
+ * the server does not announce the METADATA capability
+ * ("false").
+ * DEFAULT: false - Check the capability.
+ * 'annotatemore' - (boolean) Use the ANNOTATION command rather than the
+ * METADATA command ("true"). This corresponds to
+ * an old version of the RFC 5464 that is available for
+ * some servers (cyrus, dovecot).
+ * DEFAULT: Use the newer METADATA command.
+ *
+ *
+ * @return array
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getMetadata($mailbox, $entries, $options = array())
+ {
+ if (empty($options['nocapability'])) {
+ if (empty($options['annotatemore'])) {
+ $capability = 'METADATA';
+ } else {
+ $capability = 'ANNOTATEMORE';
+ }
+ if (!$this->queryCapability($capability)) {
+ throw new Horde_Imap_Client_Exception('Server does not support the METADATA extension.',
+ Horde_Imap_Client_Exception::NOSUPPORTIMAPEXT);
+ }
+ }
+
+ if (!is_array($entries)) {
+ $entries = array($entries);
+ }
+
+ $entries_utf7 = array();
+ foreach ($entries as $entry) {
+ $entries_utf7[] = Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($entry);
+ }
+
+ return $this->_getMetadata(Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($mailbox),
+ $entries_utf7,
+ $options);
+ }
+
+ /**
+ * Get metadata for a given mailbox.
+ *
+ * @param string $mailbox A mailbox (UTF7-IMAP).
+ * @param array $entries The entries to fetch.
+ * @param array $options Additional options.
+ *
+ * @return array An array with metadata names as the keys and
+ * metadata values as the values.
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getMetadata($mailbox, $entries, $options);
+
+ /**
+ * Set metadata for a given mailbox/identifier.
+ *
+ * @param string $mailbox A mailbox. Either in UTF7-IMAP or UTF-8.
+ * @param array $data A set of data values. The metadata values
+ * corresponding to the keys of the array will
+ * be set to the values in the array.
+ * @param array $options Additional options:
+ *
+ * 'nocapability' - (boolean) Do not check for the METADATA capability of
+ * the server ("true"). Otherwise the call will fail if
+ * the server does not announce the METADATA capability
+ * ("false").
+ * DEFAULT: false - Check the capability.
+ * 'annotatemore' - (boolean) Use the ANNOTATION command rather than the
+ * METADATA command ("true"). This corresponds to
+ * an older draft version of the RFC 5464 that has been
+ * implemented in some servers (cyrus, dovecot).
+ * http://ietfreport.isoc.org/idref/draft-daboo-imap-annotatemore/
+ * DEFAULT: Use the newer METADATA command.
+ *
+ *
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function setMetadata($mailbox, $data, $options = array())
+ {
+ if (empty($options['nocapability'])) {
+ if (empty($options['annotatemore'])) {
+ $capability = 'METADATA';
+ } else {
+ $capability = 'ANNOTATEMORE';
+ }
+ if (!$this->queryCapability($capability)) {
+ throw new Horde_Imap_Client_Exception('Server does not support the METADATA extension.',
+ Horde_Imap_Client_Exception::NOSUPPORTIMAPEXT);
+ }
+ }
+
+ return $this->_setMetadata(Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($mailbox), $data, $options);
+ }
+
+ /**
+ * Set metadata for a given mailbox/identifier.
+ *
+ * @param string $mailbox A mailbox (UTF7-IMAP).
+ * @param array $data A set of data values. The metadata values
+ * corresponding to the keys of the array will
+ * be set to the values in the array.
+ * @param array $options Additional options.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _setMetadata($mailbox, $data, $options);
+
+ /**
+ * Split a name for the METADATA extension into the correct syntax for the
+ * older ANNOTATEMORE version (it is a predecessor of RFC 5464.
+ *
+ * @param string $name A name for a metadata entry.
+ *
+ * @return array A list of two elements: The entry name and the value type.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _getAnnotateMoreEntry($name)
+ {
+ if (substr($name, 0, 7) == '/shared') {
+ $entry = substr($name, 7);
+ $type = 'value.shared';
+ } else if (substr($name, 0, 8) == '/private') {
+ $entry = substr($name, 8);
+ $type = 'value.priv';
+ } else {
+ throw new Horde_Imap_Client_Exception('Invalid METADATA entry: ' . $name);
+ }
+ return array($entry, $type);
+ }
+
/* Utility functions. */
/**
diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Cclient.php b/framework/Imap_Client/lib/Horde/Imap/Client/Cclient.php
index 2b5298f34..92093ee26 100644
--- a/framework/Imap_Client/lib/Horde/Imap/Client/Cclient.php
+++ b/framework/Imap_Client/lib/Horde/Imap/Client/Cclient.php
@@ -1621,6 +1621,76 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
return $this->_getSocket()->getMyACLRights($mailbox);
}
+ /**
+ * Get metadata for a given mailbox.
+ *
+ * @param string $mailbox A mailbox (UTF7-IMAP).
+ * @param array $entries The entries to fetch.
+ * @param array $options Additional options.
+ *
+ * @return array An array with identifiers as the keys and the
+ * metadata as the values.
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _getMetadata($mailbox, $entries, $options)
+ {
+ if (!empty($options['annotatemore'])
+ && function_exists('imap_getannotation')) {
+ $result = array();
+ foreach ($entries as $md_entry) {
+ list($entry, $type) = $this->_getAnnotateMoreEntry($md_entry);
+ $old_error = error_reporting(0);
+ $res = imap_getannotation($this->_stream, $mailbox, $entry, $type);
+ error_reporting($old_error);
+ if (!$res) {
+ throw new Horde_Imap_Client_Exception('Error when fetching METADATA: ' . imap_last_error());
+ }
+ foreach ($res as $key => $value) {
+ switch ($type) {
+ case 'value.priv':
+ $result[$mailbox]['/private' . $entry] = $value;
+ break;
+ case 'value.shared':
+ $result[$mailbox]['/shared' . $entry] = $value;
+ break;
+ }
+ }
+ }
+ return $result;
+ } else {
+ return $this->_getSocket()->getMetadata($mailbox, $entry, $options);
+ }
+ }
+
+ /**
+ * Set metadata for a given mailbox/identifier.
+ *
+ * @param string $mailbox A mailbox (UTF7-IMAP).
+ * @param array $data A set of data values. The metadata values
+ * corresponding to the keys of the array will
+ * be set to the values in the array.
+ * @param array $options Additional options.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _setMetadata($mailbox, $data, $options)
+ {
+ if (!empty($options['annotatemore'])
+ && function_exists('imap_setannotation')) {
+ foreach ($data as $key => $value) {
+ list($entry, $type) = $this->_getAnnotateMoreEntry($key);
+ $old_error = error_reporting(0);
+ $res = imap_setannotation($this->_stream, $mailbox, $entry, $type, $value);
+ error_reporting($old_error);
+ if (!$res) {
+ throw new Horde_Imap_Client_Exception('Error when setting METADATA: ' . imap_last_error());
+ }
+ }
+ } else {
+ return $this->_getSocket()->setMetadata($mailbox, $data, $options);
+ }
+ }
+
/* Internal functions */
/**
diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Mock.php b/framework/Imap_Client/lib/Horde/Imap/Client/Mock.php
new file mode 100644
index 000000000..01c27d793
--- /dev/null
+++ b/framework/Imap_Client/lib/Horde/Imap/Client/Mock.php
@@ -0,0 +1,896 @@
+
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Imap_Client
+ */
+
+/**
+ * The mock driver class.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Imap_Client
+ * @author Gunnar Wrobel + * 'b' (base) - (integer) [OPTIONAL] The ID of the base message. Is not + * set, this is the only message in the thread. + * DEFAULT: Only message in thread + * 'l' (level) - (integer) [OPTIONAL] The thread level of this + * message (1 = base). + * DEFAULT: 0 + * 's' (subthread) - (boolean) [OPTIONAL] Are there more messages in this + * subthread? + * DEFAULT: No + *+ * @throws Horde_Imap_Client_Exception + */ + protected function _thread($options) + { + throw new Horde_Imap_Client_Exception('not implemented'); + } + + /** + * Fetch message data. + * + * @param array $criteria The fetch criteria. Function must not handle + * 'parse' param to FETCH_HEADERTEXT. + * @param array $options Additional options. + * + * @return array See self::fetch(). + * @throws Horde_Imap_Client_Exception + */ + protected function _fetch($criteria, $options) + { + $fetch = array(); + $result = array(); + + reset($criteria); + while (list($type, $c_val) = each($criteria)) { + if (!is_array($c_val)) { + $c_val = array(); + } + + $uid = $options['ids'][0]; + + switch ($type) { + case Horde_Imap_Client::FETCH_HEADERTEXT: + if (!isset($this->_mbox['mails'][$uid])) { + throw new Horde_Imap_Client_Exception(sprintf("No IMAP message %s!", $uid)); + } + $result['headertext'][$uid] = $this->_mbox['mails'][$uid]['header']; + break; + case Horde_Imap_Client::FETCH_BODYTEXT: + if (!isset($this->_mbox['mails'][$uid])) { + throw new Horde_Imap_Client_Exception(sprintf("No IMAP message %s!", $uid)); + } + $result['bodytext'][$uid] = $this->_mbox['mails'][$uid]['body']; + break; + + case Horde_Imap_Client::FETCH_STRUCTURE: + case Horde_Imap_Client::FETCH_FULLMSG: + case Horde_Imap_Client::FETCH_MIMEHEADER: + case Horde_Imap_Client::FETCH_BODYPART: + case Horde_Imap_Client::FETCH_HEADERS: + case Horde_Imap_Client::FETCH_BODYPARTSIZE: + case Horde_Imap_Client::FETCH_ENVELOPE: + case Horde_Imap_Client::FETCH_FLAGS: + case Horde_Imap_Client::FETCH_DATE: + case Horde_Imap_Client::FETCH_SIZE: + case Horde_Imap_Client::FETCH_UID: + case Horde_Imap_Client::FETCH_SEQ: + case Horde_Imap_Client::FETCH_MODSEQ: + throw new Horde_Imap_Client_Exception('Not supported!'); + } + } + return $result; + } + + /** + * Store message flag data. + * + * @param array $options Additional options. + * + * @return array See self::store(). + * @throws Horde_Imap_Client_Exception + */ + protected function _store($options) + { + + foreach ($options['ids'] as $uid) { + + if (!isset($this->_mbox['mails'][$uid])) { + throw new Horde_Imap_Client_Exception(sprintf("No IMAP message %s!", $uid)); + } + foreach ($options['add'] as $flag) { + $flag = strtoupper($flag); + switch ($flag) { + case '\\DELETED': + $this->_mbox['mails'][$uid]['flags'] |= self::FLAG_DELETED; + break; + default: + throw new Horde_Imap_Client_Exception(sprintf('Flag %s not implemented!', + $flag)); + } + } + } + return true; + } + + /** + * Copy messages to another mailbox. + * + * @param string $dest The destination mailbox (UTF7-IMAP). + * @param array $options Additional options. + * + * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on + * success (if the IMAP server and/or driver support the + * UIDPLUS extension) or true. + * @throws Horde_Imap_Client_Exception + */ + protected function _copy($dest, $options) + { + $new_folder = $this->_parseFolder($dest); + + foreach ($options['ids'] as $uid) { + if (!isset($this->_mbox['mails'][$uid])) { + throw new Horde_Imap_Client_Exception(sprintf("No IMAP message %s!", $uid)); + } + $mail = $this->_mbox['mails'][$uid]; + if (!empty($options['move'])) { + unset($this->_mbox['mails'][$uid]); + } + $this->_appendMessage($new_folder, $mail); + } + return true; + } + + /** + * Set quota limits. + * + * @param string $root The quota root (UTF7-IMAP). + * @param array $options Additional options. + * + * @return boolean True on success. + * @throws Horde_Imap_Client_Exception + */ + protected function _setQuota($root, $options) + { + throw new Horde_Imap_Client_Exception('not implemented'); + } + + /** + * Get quota limits. + * + * @param string $root The quota root (UTF7-IMAP). + * + * @return mixed An array with these possible keys: 'messages' and + * 'storage'; each key holds an array with 2 values: + * 'limit' and 'usage'. + * @throws Horde_Imap_Client_Exception + */ + protected function _getQuota($root) + { + throw new Horde_Imap_Client_Exception('not implemented'); + } + + /** + * Get quota limits for a mailbox. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * + * @return mixed An array with the keys being the quota roots. Each key + * holds an array with two possible keys: 'messages' and + * 'storage'; each of these keys holds an array with 2 + * values: 'limit' and 'usage'. + * @throws Horde_Imap_Client_Exception + */ + protected function _getQuotaRoot($mailbox) + { + throw new Horde_Imap_Client_Exception('not implemented'); + } + + /** + * Get ACL rights for a given mailbox. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * + * @return array An array with identifiers as the keys and an array of + * rights as the values. + * @throws Horde_Imap_Client_Exception + */ + protected function _getACL($mailbox) + { + $folder = $this->_getMailbox($mailbox); + $acl = ''; + if (isset(self::$storage[$folder]['permissions'])) { + $acl = self::$storage[$folder]['permissions']; + } + return $acl; + } + + /** + * Set ACL rights for a given mailbox/identifier. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * @param string $identifier The identifier to alter (UTF7-IMAP). + * @param array $options Additional options. + * + * @throws Horde_Imap_Client_Exception + */ + protected function _setACL($mailbox, $identifier, $options) + { + $folder = $this->_getMailbox($mailbox); + if (empty($options['rights']) && !empty($options['remove'])) { + unset(self::$storage[$folder]['permissions'][$identifier]); + } else { + self::$storage[$folder]['permissions'][$identifier] = $options['rights']; + } + } + + /** + * Get ACL rights for a given mailbox/identifier. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * @param string $identifier The identifier to alter (UTF7-IMAP). + * + * @return array An array of rights (keys: 'required' and 'optional'). + * @throws Horde_Imap_Client_Exception + */ + protected function _listACLRights($mailbox, $identifier) + { + throw new Horde_Imap_Client_Exception('not implemented'); + } + + /** + * Get the ACL rights for the current user for a given mailbox. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * + * @return array An array of rights. + * @throws Horde_Imap_Client_Exception + */ + protected function _getMyACLRights($mailbox) + { + $folder = $this->_getMailbox($mailbox); + $acl = ''; + if (isset(self::$storage[$folder]['permissions'][$this->_user])) { + $acl = self::$storage[$folder]['permissions'][$this->_user]; + } + return $acl; + } + + /** + * Get metadata for a given mailbox. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * @param array $entries The entries to fetch. + * @param array $options Additional options. + * + * @return array An array with identifiers as the keys and the + * metadata as the values. + * @throws Horde_Imap_Client_Exception + */ + protected function _getMetadata($mailbox, $entries, $options) + { + $folder = $this->_getMailbox($mailbox); + $metadata = array(); + foreach ($entries as $entry) { + $result = false; + if (isset(self::$storage[$folder]['annotations'])) { + $ref = &self::$storage[$folder]['annotations']; + $path = split('/', $entry); + foreach ($path as $element) { + if (!isset($ref[$element])) { + $result = false; + break; + } else { + $ref = &$ref[$element]; + $result = true; + } + } + if ($result && isset($ref['/'])) { + $result = $ref['/']; + } + } + $metadata[$entry] = $result; + } + return $metadata; + } + + /** + * Set metadata for a given mailbox/identifier. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * @param array $data A set of data values. The metadata values + * corresponding to the keys of the array will + * be set to the values in the array. + * @param array $options Additional options. + * + * @throws Horde_Imap_Client_Exception + */ + protected function _setMetadata($mailbox, $data, $options) + { + $folder = $this->_getMailbox($mailbox); + foreach ($data as $key => $value) { + $path = split('/', $key); + $ref = &self::$storage[$folder]['annotations']; + foreach ($path as $element) { + if (!isset($ref[$element])) { + $ref[$element] = array(); + + $ref = &$ref[$element]; + } + } + $ref['/'] = $value; + } + return true; + } +} \ No newline at end of file diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php index fd8468503..e0a6f33f6 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php @@ -32,6 +32,7 @@ * RFC 5255 - LANGUAGE/I18NLEVEL * RFC 5256 - THREAD/SORT * RFC 5267 - ESORT + * RFC 5464 - METADATA * * [NO RFC] - XIMAPPROXY * + Requires imapproxy v1.2.7-rc1 or later @@ -3005,6 +3006,152 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base $this->_temp['myrights'] = $data[1]; } + /** + * Get metadata for a given mailbox. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * @param array $entries The entries to fetch. + * @param array $options Additional options. + * + * @return array An array with identifiers as the keys and the + * metadata as the values. + * @throws Horde_Imap_Client_Exception + */ + protected function _getMetadata($mailbox, $entries, $options) + { + $this->login(); + + $this->_temp['metadata'] = array(); + + $cmd_options = array(); + $single_type = ''; + + if (!empty($options['annotatemore'])) { + if (!empty($options['maxsize']) || !empty($options['depth'])) { + throw new Horde_Imap_Client_Exception('ANNOTATEMORE does not support the "depth" and "maxsize" option.'); + } + $cmd = 'GETANNOTATION '; + + $result = array(); + foreach ($entries as $md_entry) { + list($entry, $type) = $this->_getAnnotateMoreEntry($md_entry); + if (empty($single_type)) { + $single_type = $type; + } else if ($single_type != $type) { + throw new Horde_Imap_Client_Exception('Multiple value types may not be retrieved in one call when using ANNOTATEMORE.'); + } + $result[] = $entry; + } + $entries = $result; + } else { + $cmd = 'GETMETADATA '; + + if (!empty($options['maxsize'])) { + $cmd_options[] = '(MAXSIZE ' . $options['maxsize'] . ')'; + } + if (!empty($options['depth'])) { + $cmd_options[] = '(DEPTH ' . $options['depth'] . ')'; + } + } + + if (count($entries) == 1) { + $entry_string = $this->utils->escape($entries[0]) . ' ' . $this->utils->escape($single_type); + } else { + $entry_string = '(' . join(' ', $entries) . ') ' . $single_type; + } + + if (count($cmd_options) == 0) { + $option_string = ' '; + } else if (count($cmd_options) == 1) { + $option_string = ' ' . $cmd_options[0] . ' '; + } else { + $option_string = ' (' . join(' ', $cmd_options) . ') '; + } + + $this->_sendLine($cmd . $this->utils->escape($mailbox) . $option_string . $entry_string); + return $this->_temp['metadata']; + } + + /** + * Set metadata for a given mailbox/identifier. + * + * @param string $mailbox A mailbox (UTF7-IMAP). + * @param array $data A set of data values. The metadata values + * corresponding to the keys of the array will + * be set to the values in the array. + * @param array $options Additional options. + * + * @throws Horde_Imap_Client_Exception + */ + protected function _setMetadata($mailbox, $data, $options) + { + if (!empty($options['annotatemore'])) { + $cmd = 'SETANNOTATION '; + + $data_elements = array(); + foreach ($data as $md_entry => $value) { + list($entry, $type) = $this->_getAnnotateMoreEntry($md_entry); + $i_value = ($value === null) ? 'NIL' : $this->utils->escape($value); + $data_elements[] = $this->utils->escape($entry) . ' (' . $this->utils->escape($type) . ' ' . $i_value . ')'; + } + } else { + $cmd = 'SETMETADATA '; + + foreach ($data as $key => $value) { + $i_value = ($value === null) ? 'NIL' : $this->utils->escape($value); + $data_elements[] = $this->utils->escape($key) . ' ' . $i_value; + } + } + + if (count($data_elements) == 1) { + $data_string = $data_elements[0]; + } else { + $data_string = '(' . join(' ', $data_elements) . ')'; + } + + /** + * Disallow multi-line data for now. + * @todo: Support this with sending literal data. + */ + $data_string = str_replace("\n", '', $data_string); + + $this->_sendLine($cmd . $this->utils->escape($mailbox) . ' ' . $data_string); + } + + /** + * Parse a METADATA response (RFC 5464 [4.4]). + * + * @param array $data The server response. + */ + protected function _parseMetadata($data) + { + switch ($data[0]) { + case 'ANNOTATION': + $values = $data[3]; + while (!empty($values)) { + $type = array_shift($values); + switch ($type) { + case 'value.priv': + $this->_temp['metadata'][$data[1]]['/private' . $data[2]] = array_shift($values); + break; + case 'value.shared': + $this->_temp['metadata'][$data[1]]['/shared' . $data[2]] = array_shift($values); + break; + default: + throw new Horde_Imap_Client_Exception('Invalid METADATA value type ' . $type); + } + } + break; + case 'METADATA': + $values = $data[2]; + while (!empty($values)) { + $entry = array_shift($values); + $this->_temp['metadata'][$data[1]][$entry] = array_shift($values); + } + break; + } + } + /* Internal functions. */ /** @@ -3526,6 +3673,12 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base $this->_parseVanished(array_slice($ob['token'], 1)); break; + case 'ANNOTATION': + case 'METADATA': + // Parse a ANNOTATEMORE/METADATA response (RFC 5464). + $this->_parseMetadata($ob['token']); + break; + default: // Next, look for responses where the keywords occur second. $type = strtoupper($ob['token'][1]); diff --git a/framework/Imap_Client/package.xml b/framework/Imap_Client/package.xml index 9ecf67312..6382b995a 100644 --- a/framework/Imap_Client/package.xml +++ b/framework/Imap_Client/package.xml @@ -55,6 +55,7 @@ http://pear.php.net/dtd/package-2.0.xsd">