Thomas Bruederli
2015-07-31 2965a981b7ec22866fbdf2d567d87e2d068d3617
program/lib/Roundcube/rcube_imap_generic.php
@@ -1,6 +1,6 @@
<?php
/**
/*
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
@@ -48,19 +48,17 @@
        '*'        => '\\*',
    );
    public static $mupdate;
    private $fp;
    private $host;
    private $logged = false;
    private $capability = array();
    private $capability_readed = false;
    private $prefs;
    private $cmd_tag;
    private $cmd_num = 0;
    private $resourceid;
    private $_debug = false;
    private $_debug_handler = false;
    protected $fp;
    protected $host;
    protected $logged = false;
    protected $capability = array();
    protected $capability_readed = false;
    protected $prefs;
    protected $cmd_tag;
    protected $cmd_num = 0;
    protected $resourceid;
    protected $_debug = false;
    protected $_debug_handler = false;
    const ERROR_OK = 0;
    const ERROR_NO = -1;
@@ -352,7 +350,7 @@
     *
     * @return bool True if connection is closed
     */
    private function eof()
    protected function eof()
    {
        if (!is_resource($this->fp)) {
            return true;
@@ -375,7 +373,7 @@
    /**
     * Closes connection stream.
     */
    private function closeSocket()
    protected function closeSocket()
    {
        @fclose($this->fp);
        $this->fp = null;
@@ -421,7 +419,7 @@
        return false;
    }
    private function hasCapability($name)
    protected function hasCapability($name)
    {
        if (empty($this->capability) || $name == '') {
            return false;
@@ -723,103 +721,30 @@
        // configure
        $this->set_prefs($options);
        $auth_method = $this->prefs['auth_type'];
        $result      = false;
        // initialize connection
        $this->error    = '';
        $this->errornum = self::ERROR_OK;
        $this->selected = null;
        $this->user     = $user;
        $this->host     = $host;
        $this->user     = $user;
        $this->logged   = false;
        $this->selected = null;
        // check input
        if (empty($host)) {
            $this->setError(self::ERROR_BAD, "Empty host");
            return false;
        }
        if (empty($user)) {
            $this->setError(self::ERROR_NO, "Empty user");
            return false;
        }
        if (empty($password)) {
            $this->setError(self::ERROR_NO, "Empty password");
            return false;
        }
        if (!$this->prefs['port']) {
            $this->prefs['port'] = 143;
        }
        // check for SSL
        if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {
            $host = $this->prefs['ssl_mode'] . '://' . $host;
        }
        if ($this->prefs['timeout'] <= 0) {
            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
        }
        // Connect
        $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
        if (!$this->fp) {
            if (!$errstr) {
                $errstr = "Unknown reason (fsockopen() function disabled?)";
            }
            $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
        if (!$this->_connect($host)) {
            return false;
        }
        if ($this->prefs['timeout'] > 0) {
            stream_set_timeout($this->fp, $this->prefs['timeout']);
        }
        $line = trim(fgets($this->fp, 8192));
        if ($this->_debug) {
            // set connection identifier for debug output
            preg_match('/#([0-9]+)/', (string)$this->fp, $m);
            $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
            if ($line)
                $this->debug('S: '. $line);
        }
        // Connected to wrong port or connection error?
        if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
            if ($line)
                $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
            else
                $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
            $this->setError(self::ERROR_BAD, $error);
            $this->closeConnection();
            return false;
        }
        // RFC3501 [7.1] optional CAPABILITY response
        if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
            $this->parseCapability($matches[1], true);
        }
        // TLS connection
        if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
            $res = $this->execute('STARTTLS');
            if ($res[0] != self::ERROR_OK) {
                $this->closeConnection();
                return false;
            }
            if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
                $this->closeConnection();
                return false;
            }
            // Now we're secure, capabilities need to be reread
            $this->clearCapability();
        }
        // Send ID info
@@ -827,6 +752,7 @@
            $this->id($this->prefs['ident']);
        }
        $auth_method  = $this->prefs['auth_type'];
        $auth_methods = array();
        $result       = null;
@@ -898,6 +824,103 @@
        $this->closeConnection();
        return false;
    }
    /**
     * Connects to IMAP server.
     *
     * @param string $host Server hostname or IP
     *
     * @return bool True on success, False on failure
     */
    protected function _connect($host)
    {
        // initialize connection
        $this->error    = '';
        $this->errornum = self::ERROR_OK;
        if (!$this->prefs['port']) {
            $this->prefs['port'] = 143;
        }
        // check for SSL
        if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {
            $host = $this->prefs['ssl_mode'] . '://' . $host;
        }
        if ($this->prefs['timeout'] <= 0) {
            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
        }
        if (!empty($this->prefs['socket_options'])) {
            $context  = stream_context_create($this->prefs['socket_options']);
            $this->fp = stream_socket_client($host . ':' . $this->prefs['port'], $errno, $errstr,
                $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context);
        }
        else {
            $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
        }
        if (!$this->fp) {
            $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s",
                $host, $this->prefs['port'], $errstr ?: "Unknown reason"));
            return false;
        }
        if ($this->prefs['timeout'] > 0) {
            stream_set_timeout($this->fp, $this->prefs['timeout']);
        }
        $line = trim(fgets($this->fp, 8192));
        if ($this->_debug) {
            // set connection identifier for debug output
            preg_match('/#([0-9]+)/', (string) $this->fp, $m);
            $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
            if ($line) {
                $this->debug('S: '. $line);
            }
        }
        // Connected to wrong port or connection error?
        if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
            if ($line)
                $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
            else
                $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
            $this->setError(self::ERROR_BAD, $error);
            $this->closeConnection();
            return false;
        }
        // RFC3501 [7.1] optional CAPABILITY response
        if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
            $this->parseCapability($matches[1], true);
        }
        // TLS connection
        if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
            $res = $this->execute('STARTTLS');
            if ($res[0] != self::ERROR_OK) {
                $this->closeConnection();
                return false;
            }
            if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
                $this->closeConnection();
                return false;
            }
            // Now we're secure, capabilities need to be reread
            $this->clearCapability();
        }
        return true;
    }
    /**
@@ -1083,7 +1106,8 @@
            // folder name with spaces. Let's try to handle this situation
            if (!is_array($items) && ($pos = strpos($response, '(')) !== false) {
                $response = substr($response, $pos);
                $items = $this->tokenizeResponse($response, 1);
                $items    = $this->tokenizeResponse($response, 1);
                if (!is_array($items)) {
                    return $result;
                }
@@ -1121,7 +1145,7 @@
        }
        // Clear internal status cache
        unset($this->data['STATUS:'.$mailbox]);
        $this->clear_status_cache($mailbox);
        if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
            $messages = self::compressMessageSet($messages);
@@ -1268,15 +1292,15 @@
     *
     * @param string $ref         Reference name
     * @param string $mailbox     Mailbox name
     * @param array  $status_opts (see self::_listMailboxes)
     * @param array  $return_opts (see self::_listMailboxes)
     * @param array  $select_opts (see self::_listMailboxes)
     *
     * @return array List of mailboxes or hash of options if $status_opts argument
     *               is non-empty.
     * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
     *                    is requested, False on error.
     */
    function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array())
    function listMailboxes($ref, $mailbox, $return_opts=array(), $select_opts=array())
    {
        return $this->_listMailboxes($ref, $mailbox, false, $status_opts, $select_opts);
        return $this->_listMailboxes($ref, $mailbox, false, $return_opts, $select_opts);
    }
    /**
@@ -1284,14 +1308,14 @@
     *
     * @param string $ref         Reference name
     * @param string $mailbox     Mailbox name
     * @param array  $status_opts (see self::_listMailboxes)
     * @param array  $return_opts (see self::_listMailboxes)
     *
     * @return array List of mailboxes or hash of options if $status_opts argument
     *               is non-empty.
     * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
     *                    is requested, False on error.
     */
    function listSubscribed($ref, $mailbox, $status_opts=array())
    function listSubscribed($ref, $mailbox, $return_opts=array())
    {
        return $this->_listMailboxes($ref, $mailbox, true, $status_opts, NULL);
        return $this->_listMailboxes($ref, $mailbox, true, $return_opts, NULL);
    }
    /**
@@ -1300,18 +1324,18 @@
     * @param string $ref         Reference name
     * @param string $mailbox     Mailbox name
     * @param bool   $subscribed  Enables returning subscribed mailboxes only
     * @param array  $status_opts List of STATUS options
     *                            (RFC5819: LIST-STATUS:  MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN)
     *                            or RETURN options (RFC5258: LIST_EXTENDED: SUBSCRIBED, CHILDREN)
     * @param array  $return_opts List of RETURN options (RFC5819: LIST-STATUS, RFC5258: LIST-EXTENDED)
     *                            Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN,
     *                                      MYRIGHTS, SUBSCRIBED, CHILDREN
     * @param array  $select_opts List of selection options (RFC5258: LIST-EXTENDED)
     *                            Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE,
     *                                      SPECIAL-USE (RFC6154)
     *
     * @return array List of mailboxes or hash of options if $status_ops argument
     *               is non-empty.
     * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
     *                    is requested, False on error.
     */
    private function _listMailboxes($ref, $mailbox, $subscribed=false,
        $status_opts=array(), $select_opts=array())
    protected function _listMailboxes($ref, $mailbox, $subscribed=false,
        $return_opts=array(), $select_opts=array())
    {
        if (!strlen($mailbox)) {
            $mailbox = '*';
@@ -1329,16 +1353,24 @@
        $args[] = $this->escape($ref);
        $args[] = $this->escape($mailbox);
        if (!empty($status_opts) && $this->getCapability('LIST-EXTENDED')) {
            $rets = array_intersect($status_opts, array('SUBSCRIBED', 'CHILDREN'));
        if (!empty($return_opts) && $this->getCapability('LIST-EXTENDED')) {
            $ext_opts    = array('SUBSCRIBED', 'CHILDREN');
            $rets        = array_intersect($return_opts, $ext_opts);
            $return_opts = array_diff($return_opts, $rets);
        }
        if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) {
            $status_opts = array_intersect($status_opts, array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'));
        if (!empty($return_opts) && $this->getCapability('LIST-STATUS')) {
            $lstatus     = true;
            $status_opts = array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN');
            $opts        = array_diff($return_opts, $status_opts);
            $status_opts = array_diff($return_opts, $opts);
            if (!empty($status_opts)) {
                $lstatus = true;
                $rets[] = 'STATUS (' . implode(' ', $status_opts) . ')';
            }
            if (!empty($opts)) {
                $rets = array_merge($rets, $opts);
            }
        }
@@ -1363,9 +1395,10 @@
                $line = substr($response, $last, $pos - $last);
                $last = $pos + 2;
                if (!preg_match('/^\* (LIST|LSUB|STATUS) /i', $line, $m)) {
                if (!preg_match('/^\* (LIST|LSUB|STATUS|MYRIGHTS) /i', $line, $m)) {
                    continue;
                }
                $cmd  = strtoupper($m[1]);
                $line = substr($line, strlen($m[0]));
@@ -1396,13 +1429,20 @@
                                $this->data['LIST'][$mailbox], $opts));
                    }
                }
                // * STATUS <mailbox> (<result>)
                else if ($cmd == 'STATUS') {
                    list($mailbox, $status) = $this->tokenizeResponse($line, 2);
                else if ($lstatus) {
                    // * STATUS <mailbox> (<result>)
                    if ($cmd == 'STATUS') {
                        list($mailbox, $status) = $this->tokenizeResponse($line, 2);
                    for ($i=0, $len=count($status); $i<$len; $i += 2) {
                        list($name, $value) = $this->tokenizeResponse($status, 2);
                        $folders[$mailbox][$name] = $value;
                        for ($i=0, $len=count($status); $i<$len; $i += 2) {
                            list($name, $value) = $this->tokenizeResponse($status, 2);
                            $folders[$mailbox][$name] = $value;
                        }
                    }
                    // * MYRIGHTS <mailbox> <acl>
                    else if ($cmd == 'MYRIGHTS') {
                        list($mailbox, $acl)  = $this->tokenizeResponse($line, 2);
                        $folders[$mailbox]['MYRIGHTS'] = $acl;
                    }
                }
            }
@@ -1420,13 +1460,9 @@
     *
     * @return int Number of messages, False on error
     */
    function countMessages($mailbox, $refresh = false)
    function countMessages($mailbox)
    {
        if ($refresh) {
            $this->selected = null;
        }
        if ($this->selected === $mailbox) {
        if ($this->selected === $mailbox && isset($this->data['EXISTS'])) {
            return $this->data['EXISTS'];
        }
@@ -1454,14 +1490,20 @@
     */
    function countRecent($mailbox)
    {
        if (!strlen($mailbox)) {
            $mailbox = 'INBOX';
        if ($this->selected === $mailbox && isset($this->data['RECENT'])) {
            return $this->data['RECENT'];
        }
        $this->select($mailbox);
        // Check internal cache
        $cache = $this->data['STATUS:'.$mailbox];
        if (!empty($cache) && isset($cache['RECENT'])) {
            return (int) $cache['RECENT'];
        }
        if ($this->selected === $mailbox) {
            return $this->data['RECENT'];
        // Try STATUS (should be faster than SELECT)
        $counts = $this->status($mailbox, array('RECENT'));
        if (is_array($counts)) {
            return (int) $counts['RECENT'];
        }
        return false;
@@ -1663,7 +1705,6 @@
        $encoding  = $encoding ? trim($encoding) : 'US-ASCII';
        $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
        $criteria  = $criteria ? 'ALL '.trim($criteria) : 'ALL';
        $data      = '';
        list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
            array($algorithm, $encoding, $criteria));
@@ -1877,8 +1918,8 @@
                        $result[$id] = '';
                    }
                } else if ($mode == 2) {
                    if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) {
                        $result[$id] = trim($matches[2]);
                    if (preg_match('/' . $index_field . ' ([0-9]+)/', $line, $matches)) {
                        $result[$id] = trim($matches[1]);
                    } else {
                        $result[$id] = 0;
                    }
@@ -1985,12 +2026,8 @@
     *
     * @return bool True on success, False on failure
     */
    private function modFlag($mailbox, $messages, $flag, $mod = '+')
    protected function modFlag($mailbox, $messages, $flag, $mod = '+')
    {
        if ($mod != '+' && $mod != '-') {
            $mod = '+';
        }
        if (!$this->select($mailbox)) {
            return false;
        }
@@ -2000,12 +2037,31 @@
            return false;
        }
        if ($this->flags[strtoupper($flag)]) {
            $flag = $this->flags[strtoupper($flag)];
        }
        if (!$flag) {
            return false;
        }
        // if PERMANENTFLAGS is not specified all flags are allowed
        if (!empty($this->data['PERMANENTFLAGS'])
            && !in_array($flag, (array) $this->data['PERMANENTFLAGS'])
            && !in_array('\\*', (array) $this->data['PERMANENTFLAGS'])
        ) {
            return false;
        }
        // Clear internal status cache
        if ($flag == 'SEEN') {
            unset($this->data['STATUS:'.$mailbox]['UNSEEN']);
        }
        $flag   = $this->flags[strtoupper($flag)];
        if ($mod != '+' && $mod != '-') {
            $mod = '+';
        }
        $result = $this->execute('UID STORE', array(
            $this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"),
            self::COMMAND_NORESPONSE);
@@ -2068,7 +2124,7 @@
            // Clear internal status cache
            unset($this->data['STATUS:'.$to]);
            unset($this->data['STATUS:'.$from]);
            $this->clear_status_cache($from);
            $result = $this->execute('UID MOVE', array(
                $this->compressMessageSet($messages), $this->escape($to)),
@@ -2513,50 +2569,61 @@
            return false;
        }
        switch ($encoding) {
        case 'base64':
            $mode = 1;
            break;
        case 'quoted-printable':
            $mode = 2;
            break;
        case 'x-uuencode':
        case 'x-uue':
        case 'uue':
        case 'uuencode':
            $mode = 3;
            break;
        default:
            $mode = 0;
        }
        // Use BINARY extension when possible (and safe)
        $binary     = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
        $fetch_mode = $binary ? 'BINARY' : 'BODY';
        $partial    = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
        // format request
        $key     = $this->nextTag();
        $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
        $result  = false;
        $found   = false;
        // send request
        if (!$this->putLine($request)) {
            $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
            return false;
        }
        if ($binary) {
            // WARNING: Use $formatted argument with care, this may break binary data stream
            $mode = -1;
        }
        $binary    = true;
        do {
            if (!$initiated) {
                switch ($encoding) {
                case 'base64':
                    $mode = 1;
                    break;
                case 'quoted-printable':
                    $mode = 2;
                    break;
                case 'x-uuencode':
                case 'x-uue':
                case 'uue':
                case 'uuencode':
                    $mode = 3;
                    break;
                default:
                    $mode = 0;
                }
                // Use BINARY extension when possible (and safe)
                $binary     = $binary && $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
                $fetch_mode = $binary ? 'BINARY' : 'BODY';
                $partial    = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
                // format request
                $key       = $this->nextTag();
                $request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
                $result    = false;
                $found     = false;
                $initiated = true;
                // send request
                if (!$this->putLine($request)) {
                    $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
                    return false;
                }
                if ($binary) {
                    // WARNING: Use $formatted argument with care, this may break binary data stream
                    $mode = -1;
                }
            }
            $line = trim($this->readLine(1024));
            if (!$line) {
                break;
            }
            // handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request
            if ($binary && !$found && preg_match('/^' . $key . ' NO \[UNKNOWN-CTE\]/i', $line)) {
                $binary = $initiated = false;
                continue;
            }
            // skip irrelevant untagged responses (we have a result already)
@@ -2619,7 +2686,7 @@
                    // BASE64
                    if ($mode == 1) {
                        $line = rtrim($line, "\t\r\n\0\x0B");
                        $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line);
                        // create chunks with proper length for base64 decoding
                        $line = $prev.$line;
                        $length = strlen($line);
@@ -2664,7 +2731,7 @@
                    }
                }
            }
        } while (!$this->startsWith($line, $key, true));
        } while (!$this->startsWith($line, $key, true) || !$initiated);
        if ($result !== false) {
            if ($file) {
@@ -2834,57 +2901,64 @@
    /**
     * Returns QUOTA information
     *
     * @param string $mailbox Mailbox name
     *
     * @return array Quota information
     */
    function getQuota()
    function getQuota($mailbox = null)
    {
        /*
         * GETQUOTAROOT "INBOX"
         * QUOTAROOT INBOX user/rchijiiwa1
         * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
         * OK Completed
         */
        $result      = false;
        $quota_lines = array();
        $key         = $this->nextTag();
        $command     = $key . ' GETQUOTAROOT INBOX';
        // get line(s) containing quota info
        if ($this->putLine($command)) {
            do {
                $line = rtrim($this->readLine(5000));
                if (preg_match('/^\* QUOTA /', $line)) {
                    $quota_lines[] = $line;
                }
            } while (!$this->startsWith($line, $key, true, true));
        }
        else {
            $this->setError(self::ERROR_COMMAND, "Unable to send command: $command");
        if ($mailbox === null || $mailbox === '') {
            $mailbox = 'INBOX';
        }
        // return false if not found, parse if found
        // a0001 GETQUOTAROOT INBOX
        // * QUOTAROOT INBOX user/sample
        // * QUOTA user/sample (STORAGE 654 9765)
        // a0001 OK Completed
        list($code, $response) = $this->execute('GETQUOTAROOT', array($this->escape($mailbox)));
        $result   = false;
        $min_free = PHP_INT_MAX;
        foreach ($quota_lines as $key => $quota_line) {
            $quota_line   = str_replace(array('(', ')'), '', $quota_line);
            $parts        = explode(' ', $quota_line);
            $storage_part = array_search('STORAGE', $parts);
        $all      = array();
            if (!$storage_part) {
                continue;
        if ($code == self::ERROR_OK) {
            foreach (explode("\n", $response) as $line) {
                if (preg_match('/^\* QUOTA /', $line)) {
                    list(, , $quota_root) = $this->tokenizeResponse($line, 3);
                    while ($line) {
                        list($type, $used, $total) = $this->tokenizeResponse($line, 1);
                        $type = strtolower($type);
                        if ($type && $total) {
                            $all[$quota_root][$type]['used']  = intval($used);
                            $all[$quota_root][$type]['total'] = intval($total);
                        }
                    }
                    if (empty($all[$quota_root]['storage'])) {
                        continue;
                    }
                    $used  = $all[$quota_root]['storage']['used'];
                    $total = $all[$quota_root]['storage']['total'];
                    $free  = $total - $used;
                    // calculate lowest available space from all storage quotas
                    if ($free < $min_free) {
                        $min_free          = $free;
                        $result['used']    = $used;
                        $result['total']   = $total;
                        $result['percent'] = min(100, round(($used/max(1,$total))*100));
                        $result['free']    = 100 - $result['percent'];
                    }
                }
            }
        }
            $used  = intval($parts[$storage_part+1]);
            $total = intval($parts[$storage_part+2]);
            $free  = $total - $used;
            // return lowest available space from all quotas
            if ($free < $min_free) {
                $min_free          = $free;
                $result['used']    = $used;
                $result['total']   = $total;
                $result['percent'] = min(100, round(($used/max(1,$total))*100));
                $result['free']    = 100 - $result['percent'];
            }
        if (!empty($result)) {
            $result['all'] = $all;
        }
        return $result;
@@ -3142,8 +3216,9 @@
                for ($i=0; $i<$size; $i++) {
                    if (isset($mbox) && is_array($data[$i])) {
                        $size_sub = count($data[$i]);
                        for ($x=0; $x<$size_sub; $x++) {
                            $result[$mbox][$data[$i][$x]] = $data[$i][++$x];
                        for ($x=0; $x<$size_sub; $x+=2) {
                            if ($data[$i][$x+1] !== null)
                                $result[$mbox][$data[$i][$x]] = $data[$i][$x+1];
                        }
                        unset($data[$i]);
                    }
@@ -3161,7 +3236,8 @@
                        }
                    }
                    else if (isset($mbox)) {
                        $result[$mbox][$data[$i]] = $data[++$i];
                        if ($data[++$i] !== null)
                            $result[$mbox][$data[$i-1]] = $data[$i];
                        unset($data[$i]);
                        unset($data[$i-1]);
                    }
@@ -3195,11 +3271,6 @@
        }
        foreach ($data as $entry) {
            // Workaround cyrus-murder bug, the entry[2] string needs to be escaped
            if (self::$mupdate) {
                $entry[2] = addcslashes($entry[2], '\\"');
            }
            // ANNOTATEMORE drafts before version 08 require quoted parameters
            $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true),
                $this->escape($entry[1], true), $this->escape($entry[2], true));
@@ -3306,10 +3377,10 @@
                        for ($x=0, $len=count($attribs); $x<$len;) {
                            $attr  = $attribs[$x++];
                            $value = $attribs[$x++];
                            if ($attr == 'value.priv') {
                            if ($attr == 'value.priv' && $value !== null) {
                                $result[$mbox]['/private' . $entry] = $value;
                            }
                            else if ($attr == 'value.shared') {
                            else if ($attr == 'value.shared' && $value !== null) {
                                $result[$mbox]['/shared' . $entry] = $value;
                            }
                        }
@@ -3679,7 +3750,7 @@
        return $result;
    }
    private function _xor($string, $string2)
    protected function _xor($string, $string2)
    {
        $result = '';
        $size   = strlen($string);
@@ -3692,13 +3763,24 @@
    }
    /**
     * Clear internal status cache
     */
    protected function clear_status_cache($mailbox)
    {
        unset($this->data['STATUS:' . $mailbox]);
        unset($this->data['EXISTS']);
        unset($this->data['RECENT']);
        unset($this->data['UNSEEN']);
    }
    /**
     * Converts flags array into string for inclusion in IMAP command
     *
     * @param array $flags Flags (see self::flags)
     *
     * @return string Space-separated list of flags
     */
    private function flagsToStr($flags)
    protected function flagsToStr($flags)
    {
        foreach ((array)$flags as $idx => $flag) {
            if ($flag = $this->flags[strtoupper($flag)]) {
@@ -3750,7 +3832,7 @@
    /**
     * CAPABILITY response parser
     */
    private function parseCapability($str, $trusted=false)
    protected function parseCapability($str, $trusted=false)
    {
        $str = preg_replace('/^\* CAPABILITY /i', '', $str);
@@ -3762,10 +3844,6 @@
        if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
            $this->prefs['literal+'] = true;
        }
        if (preg_match('/(\[| )MUPDATE=.*/', $str)) {
            self::$mupdate = true;
        }
        if ($trusted) {
@@ -3827,7 +3905,7 @@
     *
     * @since 0.5-stable
     */
    private function debug($message)
    protected function debug($message)
    {
        if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
            $diff    = $len - self::DEBUG_LINE_LENGTH;