From 8b6eff6e69c2842cefbfe5ca384732c3ccd1305e Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Mon, 18 Oct 2010 08:55:07 -0400
Subject: [PATCH] - Add ACL extension support into IMAP classes (RFC4314)                                                                                           - Add ANNOTATEMORE extension support into IMAP classes (draft-daboo-imap-annotatemore)                                                            - Add METADATA extension support into IMAP classes (RFC5464)

---
 CHANGELOG                              |    3 
 program/include/rcube_imap.php         |  245 ++++++++++++++++
 program/include/rcube_imap_generic.php |  609 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 856 insertions(+), 1 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 98dbfa2..37f82e1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -32,6 +32,9 @@
 - Display IMAP errors for LIST/THREAD/SEARCH commands (#1486905)
 - Add LITERAL+ (IMAP4 non-synchronizing literals) support (RFC2088)
 - Add separate column for message status icon (#1486665)
+- Add ACL extension support into IMAP classes (RFC4314)
+- Add ANNOTATEMORE extension support into IMAP classes (draft-daboo-imap-annotatemore)
+- Add METADATA extension support into IMAP classes (RFC5464)
 
 RELEASE 0.4.2
 -------------
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 9d027c8..b786392 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -3125,6 +3125,251 @@
     }
 
 
+    /* -----------------------------------------
+     *   ACL and METADATA/ANNOTATEMORE methods
+     * ----------------------------------------*/
+
+    /**
+     * Changes the ACL on the specified mailbox (SETACL)
+     *
+     * @param string $mailbox Mailbox name
+     * @param string $user    User name
+     * @param string $acl     ACL string
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function set_acl($mailbox, $user, $acl)
+    {
+        $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('ACL'))
+            return $this->conn->setACL($mailbox, $user, $acl);
+
+        return false;
+    }
+
+
+    /**
+     * Removes any <identifier,rights> pair for the
+     * specified user from the ACL for the specified
+     * mailbox (DELETEACL)
+     *
+     * @param string $mailbox Mailbox name
+     * @param string $user    User name
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function delete_acl($mailbox, $user)
+    {
+        $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('ACL'))
+            return $this->conn->deleteACL($mailbox, $user);
+
+        return false;
+    }
+
+
+    /**
+     * Returns the access control list for mailbox (GETACL)
+     *
+     * @param string $mailbox Mailbox name
+     *
+     * @return array User-rights array on success, NULL on error
+     * @access public
+     * @since 0.5-beta
+     */
+    function get_acl($mailbox)
+    {
+        $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('ACL'))
+            return $this->conn->getACL($mailbox);
+
+        return NULL;
+    }
+
+
+    /**
+     * Returns information about what rights can be granted to the
+     * user (identifier) in the ACL for the mailbox (LISTRIGHTS)
+     *
+     * @param string $mailbox Mailbox name
+     * @param string $user    User name
+     *
+     * @return array List of user rights
+     * @access public
+     * @since 0.5-beta
+     */
+    function list_rights($mailbox, $user)
+    {
+        $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('ACL'))
+            return $this->conn->listRights($mailbox, $user);
+
+        return NULL;
+    }
+
+
+    /**
+     * Returns the set of rights that the current user has to
+     * mailbox (MYRIGHTS)
+     *
+     * @param string $mailbox Mailbox name
+     *
+     * @return array MYRIGHTS response on success, NULL on error
+     * @access public
+     * @since 0.5-beta
+     */
+    function my_rights($mailbox)
+    {
+        $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('ACL'))
+            return $this->conn->myRights($mailbox);
+
+        return NULL;
+    }
+
+
+    /**
+     * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
+     *
+     * @param string $mailbox Mailbox name (empty for server metadata)
+     * @param array  $entries Entry-value array (use NULL value as NIL)
+     *
+     * @return boolean True on success, False on failure
+     * @access public
+     * @since 0.5-beta
+     */
+    function set_metadata($mailbox, $entries)
+    {
+        if ($mailbox)
+            $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('METADATA') || 
+            empty($mailbox) && $this->get_capability('METADATA-SERVER')
+        ) {
+            return $this->conn->setMetadata($mailbox, $entries);
+        }
+        else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
+            foreach ($entries as $entry => $value) {
+                list($ent, $attr) = $this->md2annotate($entry);
+                $entries[$entry] = array($ent, $attr, $value);
+            }
+            return $this->conn->setAnnotation($mailbox, $entries);
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Unsets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
+     *
+     * @param string $mailbox Mailbox name (empty for server metadata)
+     * @param array  $entries Entry names array
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function delete_metadata($mailbox, $entries)
+    {
+        if ($mailbox)
+            $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('METADATA') || 
+            empty($mailbox) && $this->get_capability('METADATA-SERVER')
+        ) {
+            return $this->conn->deleteMetadata($mailbox, $entries);
+        }
+        else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
+            foreach ($entries as $idx => $entry) {
+                list($ent, $attr) = $this->md2annotate($entry);
+                $entries[$idx] = array($ent, $attr, NULL);
+            }
+            return $this->conn->setAnnotation($mailbox, $entries);
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
+     *
+     * @param string $mailbox Mailbox name (empty for server metadata)
+     * @param array  $entries Entries
+     * @param array  $options Command options (with MAXSIZE and DEPTH keys)
+     *
+     * @return array Metadata entry-value hash array on success, NULL on error
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function get_metadata($mailbox, $entries, $options=array())
+    {
+        if ($mailbox)
+            $mailbox = $this->mod_mailbox($mailbox);
+
+        if ($this->get_capability('METADATA') || 
+            empty($mailbox) && $this->get_capability('METADATA-SERVER')
+        ) {
+            return $this->conn->getMetadata($mailbox, $entries, $options);
+        }
+        else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
+            $queries = array();
+            $res     = array();
+
+            // Convert entry names
+            foreach ($entries as $entry) {
+                list($ent, $attr) = $this->md2annotate($entry);
+                $queries[$attr][] = $ent;
+            }
+
+            // @TODO: Honor MAXSIZE and DEPTH options
+            foreach ($queries as $attrib => $entry)
+                if ($result = $this->conn->getAnnotation($mailbox, $entry, $attrib))
+                    $res = array_merge($res, $result);
+
+            return $res;
+        }
+
+        return NULL;
+    }
+
+
+    /**
+     * Converts the METADATA extension entry name into the correct
+     * entry-attrib names for older ANNOTATEMORE version.
+     *
+     * @param string Entry name
+     *
+     * @return array Entry-attribute list, NULL if not supported (?)
+     */
+    private function md2annotate($name)
+    {
+        if (substr($entry, 0, 7) == '/shared') {
+            return array(substr($entry, 7), 'value.shared');
+        }
+        else if (substr($entry, 0, 8) == '/private') {
+            return array(substr($entry, 8), 'value.priv');
+        }
+
+        // @TODO: log error
+        return NULL;
+    }
+
+
     /* --------------------------------
      *   internal caching methods
      * --------------------------------*/
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 80f25b9..e952b20 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -2181,6 +2181,607 @@
 	    return $result;
     }
 
+    /**
+     * Send the SETACL command (RFC4314)
+     *
+     * @param string $mailbox Mailbox name
+     * @param string $user    User name
+     * @param mixed  $acl     ACL string or array
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function setACL($mailbox, $user, $acl)
+    {
+        if (is_array($acl)) {
+            $acl = implode('', $acl);
+        }
+
+        $key     = 'acl1';
+        $command = sprintf("%s SETACL \"%s\" \"%s\" %s",
+            $key, $this->escape($mailbox), $this->escape($user), strtolower($acl));
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return false;
+        }
+
+		$line = $this->readReply();
+	    return ($this->parseResult($line, 'SETACL: ') == self::ERROR_OK);
+    }
+
+    /**
+     * Send the DELETEACL command (RFC4314)
+     *
+     * @param string $mailbox Mailbox name
+     * @param string $user    User name
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function deleteACL($mailbox, $user)
+    {
+        $key     = 'acl2';
+        $command = sprintf("%s DELETEACL \"%s\" \"%s\"", 
+            $key, $this->escape($mailbox), $this->escape($user));
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return false;
+        }
+
+		$line = $this->readReply();
+	    return ($this->parseResult($line, 'DELETEACL: ') == self::ERROR_OK);
+    }
+
+    /**
+     * Send the GETACL command (RFC4314)
+     *
+     * @param string $mailbox Mailbox name
+     *
+     * @return array User-rights array on success, NULL on error
+     * @access public
+     * @since 0.5-beta
+     */
+    function getACL($mailbox)
+    {
+        $key     = 'acl3';
+        $command = sprintf("%s GETACL \"%s\"", $key, $this->escape($mailbox));
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");
+            return NULL;
+        }
+
+        $response = '';
+
+		do {
+		    $line = $this->readLine(4096);
+            $response .= $line;
+        } while (!$this->startsWith($line, $key, true, true));
+        
+        if ($this->parseResult($line, 'GETACL: ') == self::ERROR_OK) {
+            // Parse server response (remove "* ACL " and "\r\nacl3 OK...")
+            $response = substr($response, 6, -(strlen($line)+2));
+            $ret  = $this->tokenizeResponse($response);
+            $mbox = array_unshift($ret);
+            $size = count($ret);
+
+            // Create user-rights hash array
+            // @TODO: consider implementing fixACL() method according to RFC4314.2.1.1
+            // so we could return only standard rights defined in RFC4314,
+            // excluding 'c' and 'd' defined in RFC2086.
+            if ($size % 2 == 0) {
+                for ($i=0; $i<$size; $i++) {
+                    $ret[$ret[$i]] = str_split($ret[++$i]);
+                    unset($ret[$i-1]);
+                    unset($ret[$i]);
+                }
+                return $ret;
+            }
+
+            $this->set_error(self::ERROR_COMMAND, "Incomplete ACL response");
+            return NULL;
+        }
+
+        return NULL;
+    }
+
+    /**
+     * Send the LISTRIGHTS command (RFC4314)
+     *
+     * @param string $mailbox Mailbox name
+     * @param string $user    User name
+     *
+     * @return array List of user rights
+     * @access public
+     * @since 0.5-beta
+     */
+    function listRights($mailbox, $user)
+    {
+        $key     = 'acl4';
+        $command = sprintf("%s LISTRIGHTS \"%s\" \"%s\"",
+            $key, $this->escape($mailbox), $this->escape($user));
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return NULL;
+        }
+
+        $response = '';
+
+		do {
+		    $line = $this->readLine(4096);
+            $response .= $line;
+        } while (!$this->startsWith($line, $key, true, true));
+
+        if ($this->parseResult($line, 'LISTRIGHTS: ') == self::ERROR_OK) {
+            // Parse server response (remove "* LISTRIGHTS " and "\r\nacl3 OK...")
+            $response = substr($response, 13, -(strlen($line)+2));
+
+            $ret_mbox = $this->tokenizeResponse($response, 1);
+            $ret_user = $this->tokenizeResponse($response, 1);
+            $granted  = $this->tokenizeResponse($response, 1);
+            $optional = trim($response);
+
+            return array(
+                'granted'  => str_split($granted),
+                'optional' => explode(' ', $optional),
+            );
+        }
+
+        return NULL;
+    }
+
+    /**
+     * Send the MYRIGHTS command (RFC4314)
+     *
+     * @param string $mailbox Mailbox name
+     *
+     * @return array MYRIGHTS response on success, NULL on error
+     * @access public
+     * @since 0.5-beta
+     */
+    function myRights($mailbox)
+    {
+        $key = 'acl5';
+        $command = sprintf("%s MYRIGHTS \"%s\"", $key, $this->escape(mailbox));
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return NULL;
+        }
+
+        $response = '';
+
+		do {
+            $line = $this->readLine(1024);
+            $response .= $line;
+        } while (!$this->startsWith($line, $key, true, true));
+
+        if ($this->parseResult($line, 'MYRIGHTS: ') == self::ERROR_OK) {
+            // Parse server response (remove "* MYRIGHTS " and "\r\nacl5 OK...")
+            $response = substr($response, 11, -(strlen($line)+2));
+
+            $ret_mbox = $this->tokenizeResponse($response, 1);
+            $rights   = $this->tokenizeResponse($response, 1);
+
+            return str_split($rights);
+        }
+
+        return NULL;
+    }
+
+    /**
+     * Send the SETMETADATA command (RFC5464)
+     *
+     * @param string $mailbox Mailbox name
+     * @param array  $entries Entry-value array (use NULL value as NIL)
+     *
+     * @return boolean True on success, False on failure
+     * @access public
+     * @since 0.5-beta
+     */
+    function setMetadata($mailbox, $entries)
+    {
+        if (!is_array($entries) || empty($entries)) {
+            $this->set_error(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");        
+            return false;
+        }
+
+        foreach ($entries as $name => $value) {
+            if ($value === null)
+                $value = 'NIL';
+            else
+                $value = sprintf("{%d}\r\n%s", strlen($value), $value);
+
+            $entries[$name] = '"' . $this->escape($name) . '" ' . $value;
+        }
+
+        $entries = implode(' ', $entries);
+        $key     = 'md1';
+        $command = sprintf("%s SETMETADATA \"%s\" (%s)", 
+            $key, $this->escape($mailbox), $entries);
+
+		if (!$this->putLineC($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return false;
+        }
+
+        $line = $this->readReply();
+        return ($this->parseResult($line, 'SETMETADATA: ') == self::ERROR_OK);
+    }
+
+    /**
+     * Send the SETMETADATA command with NIL values (RFC5464)
+     *
+     * @param string $mailbox Mailbox name
+     * @param array  $entries Entry names array
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function deleteMetadata($mailbox, $entries)
+    {
+        if (!is_array($entries) && !empty($entries))
+            $entries = explode(' ', $entries);
+
+        if (empty($entries)) {
+            $this->set_error(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
+            return false;
+        }
+
+        foreach ($entries as $entry)
+            $data[$entry] = NULL;
+    
+        return $this->setMetadata($mailbox, $data);
+    }
+
+    /**
+     * Send the GETMETADATA command (RFC5464)
+     *
+     * @param string $mailbox Mailbox name
+     * @param array  $entries Entries
+     * @param array  $options Command options (with MAXSIZE and DEPTH keys)
+     *
+     * @return array GETMETADATA result on success, NULL on error
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function getMetadata($mailbox, $entries, $options=array())
+    {
+        if (!is_array($entries)) {
+            $entries = array($entries);
+        }
+
+        // create entries string
+        foreach ($entries as $idx => $name) {
+            $entries[$idx] = '"' . $this->escape($name) . '"';
+        }
+
+        $optlist = '';
+        $entlist = '(' . implode(' ', $entries) . ')';
+
+        // create options string
+        if (is_array($options)) {
+            $options = array_change_key_case($options, CASE_UPPER);
+            $opts = array();
+
+            if (!empty($options['MAXSIZE']))
+                $opts[] = 'MAXSIZE '.intval($options['MAXSIZE']);
+            if (!empty($options['DEPTH']))
+                $opts[] = 'DEPTH '.intval($options['DEPTH']);
+
+            if ($opts)
+                $optlist = '(' . implode(' ', $opts) . ')';
+        }
+
+        $optlist .= ($optlist ? ' ' : '') . $entlist;
+
+        $key     = 'md2';
+        $command = sprintf("%s GETMETADATA \"%s\" %s",
+            $key, $this->escape($mailbox), $optlist);
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return NULL;
+        }
+
+        $response = '';
+
+		do {
+		    $line = $this->readLine(4096);
+            $response .= $line;
+        } while (!$this->startsWith($line, $key, true, true));
+
+        if ($this->parseResult($line, 'GETMETADATA: ') == self::ERROR_OK) {
+            // Parse server response (remove "* METADATA " and "\r\nmd2 OK...")
+            $response = substr($response, 11, -(strlen($line)+2));
+            $ret_mbox = $this->tokenizeResponse($response, 1);
+            $data     = $this->tokenizeResponse($response);
+
+            // The METADATA response can contain multiple entries in a single
+            // response or multiple responses for each entry or group of entries
+            if (!empty($data) && ($size = count($data))) {
+                for ($i=0; $i<$size; $i++) {
+                    if (is_array($data[$i])) {
+                        $size_sub = count($data[$i]);
+                        for ($x=0; $x<$size_sub; $x++) {
+                            $data[$data[$i][$x]] = $data[$i][++$x];                        
+                        }
+                        unset($data[$i]);
+                    }
+                    else if ($data[$i] == '*' && $data[$i+1] == 'METADATA') {
+                        unset($data[$i]);   // "*"
+                        unset($data[++$i]); // "METADATA"
+                        unset($data[++$i]); // Mailbox
+                    }
+                    else {
+                        $data[$data[$i]] = $data[++$i];
+                        unset($data[$i]);
+                        unset($data[$i-1]);
+                    }
+                }
+            }
+
+            return $data;
+        }
+        
+        return NULL;
+    }
+
+    /**
+     * Send the SETANNOTATION command (draft-daboo-imap-annotatemore)
+     *
+     * @param string $mailbox Mailbox name
+     * @param array  $data    Data array where each item is an array with
+     *                        three elements: entry name, attribute name, value
+     *
+     * @return boolean True on success, False on failure
+     * @access public
+     * @since 0.5-beta
+     */
+    function setAnnotation($mailbox, $data)
+    {
+        if (!is_array($data) || empty($data)) {
+            $this->set_error(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");        
+            return false;
+        }
+
+        foreach ($data as $entry) {
+            $name  = $entry[0];
+            $attr  = $entry[1];
+            $value = $entry[2];
+
+            if ($value === null)
+                $value = 'NIL';
+            else
+                $value = sprintf("{%d}\r\n%s", strlen($value), $value);
+
+            $entries[] = sprintf('"%s" ("%s" %s)',
+                $this->escape($name), $this->escape($attr), $value);
+        }
+
+        $entries = implode(' ', $entries);
+        $key     = 'an1';
+        $command = sprintf("%s SETANNOTATION \"%s\" %s",
+            $key, $this->escape($mailbox), $entries);
+
+		if (!$this->putLineC($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return false;
+        }
+
+        $line = $this->readReply();
+        return ($this->parseResult($line, 'SETANNOTATION: ') == self::ERROR_OK);
+    }
+
+    /**
+     * Send the SETANNOTATION command with NIL values (draft-daboo-imap-annotatemore)
+     *
+     * @param string $mailbox Mailbox name
+     * @param array  $data    Data array where each item is an array with
+     *                        two elements: entry name and attribute name
+     *
+     * @return boolean True on success, False on failure
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function deleteAnnotation($mailbox, $data)
+    {
+        if (!is_array($data) || empty($data)) {
+            $this->set_error(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
+            return false;
+        }
+
+        return $this->setAnnotation($mailbox, $data);
+    }
+
+    /**
+     * Send the GETANNOTATION command (draft-daboo-imap-annotatemore)
+     *
+     * @param string $mailbox Mailbox name
+     * @param array  $entries Entries names
+     * @param array  $attribs Attribs names
+     *
+     * @return array Annotations result on success, NULL on error
+     *
+     * @access public
+     * @since 0.5-beta
+     */
+    function getAnnotation($mailbox, $entries, $attribs)
+    {
+        if (!is_array($entries)) {
+            $entries = array($entries);
+        }
+        // create entries string
+        foreach ($entries as $idx => $name) {
+            $entries[$idx] = '"' . $this->escape($name) . '"';
+        }
+        $entries = '(' . implode(' ', $entries) . ')';
+
+        if (!is_array($attribs)) {
+            $attribs = array($attribs);
+        }
+        // create entries string
+        foreach ($attribs as $idx => $name) {
+            $attribs[$idx] = '"' . $this->escape($name) . '"';
+        }
+        $attribs = '(' . implode(' ', $attribs) . ')';
+
+        $key     = 'an2';
+        $command = sprintf("%s GETANNOTATION \"%s\" %s %s",
+            $key, $this->escape($mailbox), $entries, $attribs);
+
+		if (!$this->putLine($command)) {
+            $this->set_error(self::ERROR_COMMAND, "Unable to send command: $command");        
+            return NULL;
+        }
+
+        $response = '';
+
+		do {
+		    $line = $this->readLine(4096);
+            $response .= $line;
+        } while (!$this->startsWith($line, $key, true, true));
+
+        if ($this->parseResult($line, 'GETANNOTATION: ') == self::ERROR_OK) {
+            // Parse server response (remove "* ANNOTATION " and "\r\nan2 OK...")
+            $response = substr($response, 13, -(strlen($line)+2));
+            $ret_mbox = $this->tokenizeResponse($response, 1);
+            $data     = $this->tokenizeResponse($response);
+            $res      = array();
+
+            // Here we returns only data compatible with METADATA result format
+            if (!empty($data) && ($size = count($data))) {
+                for ($i=0; $i<$size; $i++) {
+                    $entry = $data[$i++];
+                    if (is_array($entry)) {
+                        $attribs = $entry;
+                        $entry   = $last_entry;
+                    }
+                    else
+                        $attribs = $data[$i++];
+
+                    if (!empty($attribs)) {
+                        for ($x=0, $len=count($attribs); $x<$len;) {
+                            $attr  = $attribs[$x++];
+                            $value = $attribs[$x++];
+                            if ($attr == 'value.priv')
+                                $res['/private' . $entry] = $value;
+                            else if ($attr == 'value.shared')
+                                $res['/shared' . $entry] = $value;
+                        }
+                    }
+                    $last_entry = $entry;
+                    unset($data[$i-1]);
+                    unset($data[$i-2]);
+                }
+            }
+
+            return $res;
+        }
+        
+        return NULL;
+    }
+
+    /**
+     * Splits IMAP response into string tokens
+     *
+     * @param string &$str The IMAP's server response
+     * @param int    $num  Number of tokens to return
+     *
+     * @return mixed Tokens array or string if $num=1
+     * @access public
+     * @since 0.5-beta
+     */
+    static function tokenizeResponse(&$str, $num=0)
+    {
+        $result = array();
+
+        while (!$num || count($result) < $num) {
+            // remove spaces from the beginning of the string
+            $str = ltrim($str);
+
+            switch ($str[0]) {
+
+            // String literal
+            case '{':
+                if (($epos = strpos($str, "}\r\n", 1)) == false) {
+                    // error
+                }
+                if (!is_numeric(($bytes = substr($str, 1, $epos - 1)))) {
+                    // error
+                }
+                $result[] = substr($str, $epos + 3, $bytes);
+                // Advance the string
+                $str = substr($str, $epos + 3 + $bytes);
+            break;
+
+            // Quoted string
+            case '"':
+                $len = strlen($str);
+
+                for ($pos=1; $pos<$len; $pos++) {
+                    if ($str[$pos] == '"') {
+                        break;
+                    }
+                    if ($str[$pos] == "\\") {
+                        if ($str[$pos + 1] == '"' || $str[$pos + 1] == "\\") {
+                            $pos++;
+                        }
+                    }
+                }
+                if ($str[$pos] != '"') {
+                    // error
+                }
+                // we need to strip slashes for a quoted string
+                $result[] = stripslashes(substr($str, 1, $pos - 1));
+                $str      = substr($str, $pos + 1);
+            break;
+
+            // Parenthesized list
+            case '(':
+                $str = substr($str, 1);
+                $result[] = self::tokenizeResponse($str);
+            break;
+            case ')':
+                $str = substr($str, 1);
+                return $result;
+            break;
+
+            // String atom, number, NIL, *, %
+            default:
+                // empty or one character      
+                if ($str === '') {
+                    break 2;
+                }
+                if (strlen($str) < 2) {
+                    $result[] = $str;
+                    $str = '';
+                    break;
+                }
+
+                // excluded chars: SP, CTL, (, ), {, ", ], %
+                if (preg_match('/^([\x21\x23\x24\x26\x27\x2A-\x5C\x5E-\x7A\x7C-\x7E]+)/', $str, $m)) {
+                    $result[] = $m[1] == 'NIL' ? NULL : $m[1];
+                    $str = substr($str, strlen($m[1]));
+                }
+            break;
+            }
+        }
+
+        return $num == 1 ? $result[0] : $result;
+    }
+
     private function _xor($string, $string2)
     {
 	    $result = '';
@@ -2191,6 +2792,13 @@
 	    return $result;
     }
 
+    /**
+     * Converts datetime string into unix timestamp
+     *
+     * @param string $date Date string
+     *
+     * @return int Unix timestamp
+     */
     private function strToTime($date)
     {
 	    // support non-standard "GMTXXXX" literal
@@ -2278,4 +2886,3 @@
     }
 
 }
-

--
Gitblit v1.9.1