thomascube
2011-01-26 cdb8b96e31fdb71b37fe6b0fca81ef5221de7a6d
program/include/rcube_imap_generic.php
@@ -5,7 +5,7 @@
 | program/include/rcube_imap_generic.php                                |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland                 |
 | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | PURPOSE:                                                              |
@@ -109,6 +109,8 @@
    private $prefs;
    private $cmd_tag;
    private $cmd_num = 0;
    private $_debug = false;
    private $_debug_handler = false;
    const ERROR_OK = 0;
    const ERROR_NO = -1;
@@ -142,8 +144,8 @@
        if (!$this->fp)
            return false;
        if (!empty($this->prefs['debug_mode'])) {
            write_log('imap', 'C: '. rtrim($string));
        if ($this->_debug) {
            $this->debug('C: '. rtrim($string));
        }
        $res = fwrite($this->fp, $string . ($endln ? "\r\n" : ''));
@@ -231,8 +233,8 @@
                $this->fp = null;
                break;
            }
            if (!empty($this->prefs['debug_mode'])) {
                write_log('imap', 'S: '. rtrim($buffer));
            if ($this->_debug) {
                $this->debug('S: '. rtrim($buffer));
            }
            $line .= $buffer;
        } while ($buffer[strlen($buffer)-1] != "\n");
@@ -268,8 +270,8 @@
        while ($len < $bytes && !feof($this->fp))
        {
            $d = fread($this->fp, $bytes-$len);
            if (!empty($this->prefs['debug_mode'])) {
                write_log('imap', 'S: '. $d);
            if ($this->_debug) {
                $this->debug('S: '. $d);
            }
            $data .= $d;
            $data_len = strlen($data);
@@ -369,10 +371,43 @@
        return false;
    }
    function getCapability($name)
    private function hasCapability($name)
    {
        if (empty($this->capability) || $name == '') {
            return false;
        }
        if (in_array($name, $this->capability)) {
            return true;
        }
        else if (strpos($name, '=')) {
            return false;
        }
        $result = array();
        foreach ($this->capability as $cap) {
            $entry = explode('=', $cap);
            if ($entry[0] == $name) {
                $result[] = $entry[1];
            }
        }
        return !empty($result) ? $result : false;
    }
    /**
     * Capabilities checker
     *
     * @param string $name Capability name
     *
     * @return mixed Capability values array for key=value pairs, true/false for others
     */
    function getCapability($name)
    {
        $result = $this->hasCapability($name);
        if (!empty($result)) {
            return $result;
        }
        else if ($this->capability_readed) {
            return false;
@@ -388,11 +423,7 @@
        $this->capability_readed = true;
        if (in_array($name, $this->capability)) {
            return true;
        }
        return false;
        return $this->hasCapability($name);
    }
    function clearCapability()
@@ -686,8 +717,8 @@
        $line = trim(fgets($this->fp, 8192));
        if ($this->prefs['debug_mode'] && $line) {
            write_log('imap', 'S: '. $line);
        if ($this->_debug && $line) {
            $this->debug('S: '. $line);
        }
        // Connected to wrong port or connection error?
@@ -733,17 +764,17 @@
        // check for supported auth methods
        if ($auth_method == 'CHECK') {
            if ($this->getCapability('AUTH=DIGEST-MD5')) {
                $auth_methods[] = 'DIGEST-MD5';
            }
            if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) {
                $auth_methods[] = 'CRAM-MD5';
            }
            if ($this->getCapability('AUTH=PLAIN')) {
                $auth_methods[] = 'PLAIN';
            if ($auth_caps = $this->getCapability('AUTH')) {
                $auth_methods = $auth_caps;
            }
            // RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure
            if (!$this->getCapability('LOGINDISABLED')) {
            $login_disabled = $this->getCapability('LOGINDISABLED');
            if (($key = array_search('LOGIN', $auth_methods)) !== false) {
                if ($login_disabled) {
                    unset($auth_methods[$key]);
                }
            }
            else if (!$login_disabled) {
                $auth_methods[] = 'LOGIN';
            }
        }
@@ -764,8 +795,10 @@
        // Authenticate
        foreach ($auth_methods as $method) {
            switch ($method) {
            case 'DIGEST-MD5':
            case 'CRAM_MD5':
                $method = 'CRAM-MD5';
            case 'CRAM-MD5':
            case 'DIGEST-MD5':
            case 'PLAIN':
                $result = $this->authenticate($user, $password, $method);
                break;
@@ -1150,9 +1183,12 @@
            array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));
        if ($code == self::ERROR_OK) {
            // remove prefix and \r\n from raw response
            $response = str_replace("\r\n", '', substr($response, 7));
            return preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY);
            // remove prefix and unilateral untagged server responses
            $response = substr($response, stripos($response, '* SORT') + 7);
            if ($pos = strpos($response, '*')) {
                $response = substr($response, 0, $pos);
            }
            return preg_split('/[\s\r\n]+/', $response, -1, PREG_SPLIT_NO_EMPTY);
        }
        return false;
@@ -1889,9 +1925,15 @@
        list($code, $response) = $this->execute('THREAD', array(
            $algorithm, $encoding, $criteria));
        if ($code == self::ERROR_OK && preg_match('/^\* THREAD /i', $response)) {
            // remove prefix and \r\n from raw response
            $response    = str_replace("\r\n", '', substr($response, 9));
        if ($code == self::ERROR_OK) {
            // remove prefix...
            $response = substr($response, stripos($response, '* THREAD') + 9);
            // ...unilateral untagged server responses
            if ($pos = strpos($response, '*')) {
                $response = substr($response, 0, $pos);
            }
            $response    = str_replace("\r\n", '', $response);
            $depthmap    = array();
            $haschildren = array();
@@ -1949,9 +1991,13 @@
            array($params));
        if ($code == self::ERROR_OK) {
            // remove prefix and \r\n from raw response
            $response = substr($response, $esearch ? 10 : 9);
            $response = str_replace("\r\n", '', $response);
            // remove prefix...
            $response = substr($response, stripos($response,
                $esearch ? '* ESEARCH' : '* SEARCH') + ($esearch ? 10 : 9));
            // ...and unilateral untagged server responses
            if ($pos = strpos($response, '*')) {
                $response = rtrim(substr($response, 0, $pos));
            }
            if ($esearch) {
                // Skip prefix: ... (TAG "A285") UID ...
@@ -1970,7 +2016,7 @@
                return $result;
            }
            else {
                $response = preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY);
                $response = preg_split('/[\s\r\n]+/', $response, -1, PREG_SPLIT_NO_EMPTY);
                if (!empty($items)) {
                    $result = array();
@@ -2591,13 +2637,13 @@
     */
    function getACL($mailbox)
    {
        list($code, $response) = $this->execute('GETACL', $this->escape($mailbox));
        list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox)));
        if ($code == self::ERROR_OK && preg_match('/^\* ACL /i', $response)) {
            // Parse server response (remove "* ACL ")
            $response = substr($response, 6);
            $ret  = $this->tokenizeResponse($response);
            $mbox = array_unshift($ret);
            $mbox = array_shift($ret);
            $size = count($ret);
            // Create user-rights hash array
@@ -2664,7 +2710,7 @@
     */
    function myRights($mailbox)
    {
        list($code, $response) = $this->execute('MYRIGHTS', array($this->escape(mailbox)));
        list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox)));
        if ($code == self::ERROR_OK && preg_match('/^\* MYRIGHTS /i', $response)) {
            // Parse server response (remove "* MYRIGHTS ")
@@ -2791,37 +2837,46 @@
        list($code, $response) = $this->execute('GETMETADATA', array(
            $this->escape($mailbox), $optlist));
        if ($code == self::ERROR_OK && preg_match('/^\* METADATA /i', $response)) {
            // Parse server response (remove "* METADATA ")
            $response = substr($response, 11);
            $ret_mbox = $this->tokenizeResponse($response, 1);
            $data     = $this->tokenizeResponse($response);
        if ($code == self::ERROR_OK) {
            $result = array();
            $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])) {
                    if (isset($mbox) && is_array($data[$i])) {
                        $size_sub = count($data[$i]);
                        for ($x=0; $x<$size_sub; $x++) {
                            $data[$data[$i][$x]] = $data[$i][++$x];
                            $result[$mbox][$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 if ($data[$i] == '*') {
                        if ($data[$i+1] == 'METADATA') {
                            $mbox = $data[$i+2];
                            unset($data[$i]);   // "*"
                            unset($data[++$i]); // "METADATA"
                            unset($data[++$i]); // Mailbox
                        }
                        // get rid of other untagged responses
                        else {
                            unset($mbox);
                            unset($data[$i]);
                        }
                    }
                    else {
                        $data[$data[$i]] = $data[++$i];
                    else if (isset($mbox)) {
                        $result[$mbox][$data[$i]] = $data[++$i];
                        unset($data[$i]);
                        unset($data[$i-1]);
                    }
                    else {
                        unset($data[$i]);
                    }
                }
            }
            return $data;
            return $result;
        }
        return NULL;
@@ -2857,8 +2912,9 @@
                $value = sprintf("{%d}\r\n%s", strlen($value), $value);
            }
            // ANNOTATEMORE drafts before version 08 require quoted parameters
            $entries[] = sprintf('%s (%s %s)',
                $this->escape($name), $this->escape($attr), $value);
                $this->escape($name, true), $this->escape($attr, true), $value);
        }
        $entries = implode(' ', $entries);
@@ -2908,8 +2964,9 @@
            $entries = array($entries);
        }
        // create entries string
        // ANNOTATEMORE drafts before version 08 require quoted parameters
        foreach ($entries as $idx => $name) {
            $entries[$idx] = $this->escape($name);
            $entries[$idx] = $this->escape($name, true);
        }
        $entries = '(' . implode(' ', $entries) . ')';
@@ -2918,50 +2975,65 @@
        }
        // create entries string
        foreach ($attribs as $idx => $name) {
            $attribs[$idx] = $this->escape($name);
            $attribs[$idx] = $this->escape($name, true);
        }
        $attribs = '(' . implode(' ', $attribs) . ')';
        list($code, $response) = $this->execute('GETANNOTATION', array(
            $this->escape($mailbox), $entries, $attribs));
        if ($code == self::ERROR_OK && preg_match('/^\* ANNOTATION /i', $response)) {
            // Parse server response (remove "* ANNOTATION ")
            $response = substr($response, 13);
            $ret_mbox = $this->tokenizeResponse($response, 1);
            $data     = $this->tokenizeResponse($response);
            $res      = array();
        if ($code == self::ERROR_OK) {
            $result = array();
            $data   = $this->tokenizeResponse($response);
            // 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)) {
                    $entry = $data[$i];
                    if (isset($mbox) && is_array($entry)) {
                        $attribs = $entry;
                        $entry   = $last_entry;
                    }
                    else
                        $attribs = $data[$i++];
                    else if ($entry == '*') {
                        if ($data[$i+1] == 'ANNOTATION') {
                            $mbox = $data[$i+2];
                            unset($data[$i]);   // "*"
                            unset($data[++$i]); // "ANNOTATION"
                            unset($data[++$i]); // Mailbox
                        }
                        // get rid of other untagged responses
                        else {
                            unset($mbox);
                            unset($data[$i]);
                        }
                        continue;
                    }
                    else if (isset($mbox)) {
                        $attribs = $data[++$i];
                    }
                    else {
                        unset($data[$i]);
                        continue;
                    }
                    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;
                                $result[$mbox]['/private' . $entry] = $value;
                            }
                            else if ($attr == 'value.shared') {
                                $res['/shared' . $entry] = $value;
                                $result[$mbox]['/shared' . $entry] = $value;
                            }
                        }
                    }
                    $last_entry = $entry;
                    unset($data[$i-1]);
                    unset($data[$i-2]);
                    unset($data[$i]);
                }
            }
            return $res;
            return $result;
        }
        return NULL;
@@ -3151,21 +3223,7 @@
     */
    private function strToTime($date)
    {
        // support non-standard "GMTXXXX" literal
        $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
        // if date parsing fails, we have a date in non-rfc format.
        // remove token from the end and try again
        while ((($ts = @strtotime($date))===false) || ($ts < 0)) {
            $d = explode(' ', $date);
            array_pop($d);
            if (!$d) {
                break;
            }
            $date = implode(' ', $d);
        }
        $ts = (int) $ts;
        $ts = (int) rcube_strtotime($date);
        return $ts < 0 ? 0 : $ts;
    }
@@ -3198,12 +3256,13 @@
    /**
     * Escapes a string when it contains special characters (RFC3501)
     *
     * @param string $string IMAP string
     * @param string  $string       IMAP string
     * @param boolean $force_quotes Forces string quoting
     *
     * @return string Escaped string
     * @todo String literals, lists
     */
    static function escape($string)
    static function escape($string, $force_quotes=false)
    {
        if ($string === null) {
            return 'NIL';
@@ -3211,7 +3270,9 @@
        else if ($string === '') {
            return '""';
        }
        else if (preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5C\x5D\x7F]+)/', $string)) {
        else if ($force_quotes ||
            preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5C\x5D\x7F]+)/', $string)
        ) {
            // string: special chars: SP, CTL, (, ), {, %, *, ", \, ]
            return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"';
        }
@@ -3225,4 +3286,35 @@
        return strtr($string, array('\\"'=>'"', '\\\\' => '\\'));
    }
    /**
     * Set the value of the debugging flag.
     *
     * @param   boolean $debug      New value for the debugging flag.
     *
     * @access  public
     * @since   0.5-stable
     */
    function setDebug($debug, $handler = null)
    {
        $this->_debug = $debug;
        $this->_debug_handler = $handler;
    }
    /**
     * Write the given debug text to the current debug output handler.
     *
     * @param   string  $message    Debug mesage text.
     *
     * @access  private
     * @since   0.5-stable
     */
    private function debug($message)
    {
        if ($this->_debug_handler) {
            call_user_func_array($this->_debug_handler, array(&$this, $message));
        } else {
            echo "DEBUG: $message\n";
        }
    }
}