alecpl
2011-02-09 99897b7c4e52a5ff026c3828b84653f460f571f0
program/include/rcube_imap_generic.php
@@ -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?
@@ -728,22 +759,27 @@
            }
        }
        // Send ID info
        if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
            $this->id($this->prefs['ident']);
        }
        $auth_methods = array();
        $result       = null;
        // 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 +800,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;
@@ -1124,6 +1162,44 @@
        return false;
    }
    /**
     * Executes ID command (RFC2971)
     *
     * @param array $items Client identification information key/value hash
     *
     * @return array Server identification information key/value hash
     * @access public
     * @since 0.6
     */
    function id($items=array())
    {
        if (is_array($items) && !empty($items)) {
            foreach ($items as $key => $value) {
                $args[] = $this->escape($key);
                $args[] = $this->escape($value);
            }
        }
        list($code, $response) = $this->execute('ID', array(
            !empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)
        ));
        if ($code == self::ERROR_OK && preg_match('/\* ID /i', $response)) {
            $response = substr($response, 5); // remove prefix "* ID "
            $items    = $this->tokenizeResponse($response);
            $result   = null;
            for ($i=0, $len=count($items); $i<$len; $i += 2) {
                $result[$items[$i]] = $items[$i+1];
            }
            return $result;
        }
        return false;
    }
    function sort($mailbox, $field, $add='', $is_uid=FALSE, $encoding = 'US-ASCII')
    {
        $field = strtoupper($field);
@@ -1461,7 +1537,7 @@
                // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...)
                // BODY[HEADER.FIELDS ...
                if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) {
                if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/sU', $line, $matches)) {
                    $str = $matches[1];
                    // swap parents with quotes, then explode
@@ -1498,7 +1574,7 @@
                    // BODYSTRUCTURE
                    if ($bodystr) {
                        while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) {
                        while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/sU', $line, $m)) {
                            $line2 = $this->readLine(1024);
                            $line .= $this->multLine($line2, true);
                        }
@@ -1598,7 +1674,7 @@
                        break;
                        case 'content-type':
                            $ctype_parts = preg_split('/[; ]/', $string);
                            $result[$id]->ctype = array_shift($ctype_parts);
                            $result[$id]->ctype = strtolower(array_shift($ctype_parts));
                            if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
                                $result[$id]->charset = $regs[1];
                            }
@@ -2604,13 +2680,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
@@ -2677,7 +2753,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 ")
@@ -2804,37 +2880,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;
@@ -2870,8 +2955,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);
@@ -2921,8 +3007,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) . ')';
@@ -2931,50 +3018,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;
@@ -3211,12 +3313,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';
@@ -3224,8 +3327,11 @@
        else if ($string === '') {
            return '""';
        }
        else if (preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5C\x5D\x7F]+)/', $string)) {
            // string: special chars: SP, CTL, (, ), {, %, *, ", \, ]
        // need quoted-string? find special chars: SP, CTL, (, ), {, %, *, ", \, ]
        // plus [ character as a workaround for DBMail's bug (#1487766)
        else if ($force_quotes ||
            preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5B\x5C\x5D\x7F]+)/', $string)
        ) {
            return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"';
        }
@@ -3238,4 +3344,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";
        }
    }
}