Bartlomiej Nogas
2014-10-24 281ca0ea2a368a5ecd62344bffaa4b4a5986db9e
program/lib/Roundcube/rcube_imap_generic.php
@@ -48,6 +48,8 @@
        '*'        => '\\*',
    );
    public static $mupdate;
    private $fp;
    private $host;
    private $logged = false;
@@ -71,6 +73,9 @@
    const COMMAND_NORESPONSE = 1;
    const COMMAND_CAPABILITY = 2;
    const COMMAND_LASTLINE   = 4;
    const COMMAND_ANONYMIZED = 8;
    const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
    /**
     * Object constructor
@@ -84,16 +89,28 @@
     *
     * @param string $string Command string
     * @param bool   $endln  True if CRLF need to be added at the end of command
     * @param bool   $anonymized Don't write the given data to log but a placeholder
     *
     * @param int Number of bytes sent, False on error
     */
    function putLine($string, $endln=true)
    function putLine($string, $endln=true, $anonymized=false)
    {
        if (!$this->fp)
            return false;
        if ($this->_debug) {
            $this->debug('C: '. rtrim($string));
            // anonymize the sent command for logging
            $cut = $endln ? 2 : 0;
            if ($anonymized && preg_match('/^(A\d+ (?:[A-Z]+ )+)(.+)/', $string, $m)) {
                $log = $m[1] . sprintf('****** [%d]', strlen($m[2]) - $cut);
            }
            else if ($anonymized) {
                $log = sprintf('****** [%d]', strlen($string) - $cut);
            }
            else {
                $log = rtrim($string);
            }
            $this->debug('C: ' . $log);
        }
        $res = fwrite($this->fp, $string . ($endln ? "\r\n" : ''));
@@ -112,10 +129,11 @@
     *
     * @param string $string Command string
     * @param bool   $endln  True if CRLF need to be added at the end of command
     * @param bool   $anonymized Don't write the given data to log but a placeholder
     *
     * @return int|bool Number of bytes sent, False on error
     */
    function putLineC($string, $endln=true)
    function putLineC($string, $endln=true, $anonymized=false)
    {
        if (!$this->fp) {
            return false;
@@ -134,7 +152,7 @@
                        $parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]);
                    }
                    $bytes = $this->putLine($parts[$i].$parts[$i+1], false);
                    $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized);
                    if ($bytes === false)
                        return false;
                    $res += $bytes;
@@ -149,7 +167,7 @@
                    $i++;
                }
                else {
                    $bytes = $this->putLine($parts[$i], false);
                    $bytes = $this->putLine($parts[$i], false, $anonymized);
                    if ($bytes === false)
                        return false;
                    $res += $bytes;
@@ -515,7 +533,7 @@
                $reply = base64_encode($user . ' ' . $hash);
                // send result
                $this->putLine($reply);
                $this->putLine($reply, true, true);
            }
            else {
                // RFC2831: DIGEST-MD5
@@ -533,7 +551,7 @@
                    base64_decode($challenge), $this->host, 'imap', $user));
                // send result
                $this->putLine($reply);
                $this->putLine($reply, true, true);
                $line = trim($this->readReply());
                if ($line[0] == '+') {
@@ -573,7 +591,7 @@
            // RFC 4959 (SASL-IR): save one round trip
            if ($this->getCapability('SASL-IR')) {
                list($result, $line) = $this->execute("AUTHENTICATE PLAIN", array($reply),
                    self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY);
                    self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
            }
            else {
                $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN");
@@ -584,7 +602,7 @@
                }
                // send result, get reply and process it
                $this->putLine($reply);
                $this->putLine($reply, true, true);
                $line = $this->readReply();
                $result = $this->parseResult($line);
            }
@@ -615,7 +633,7 @@
    function login($user, $password)
    {
        list($code, $response) = $this->execute('LOGIN', array(
            $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY);
            $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
        // re-set capabilities list if untagged CAPABILITY response provided
        if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) {
@@ -702,115 +720,33 @@
     */
    function connect($host, $user, $password, $options=null)
    {
        // set options
        if (is_array($options)) {
            $this->prefs = $options;
        }
        // set auth method
        if (!empty($this->prefs['auth_type'])) {
            $auth_method = strtoupper($this->prefs['auth_type']);
        } else {
            $auth_method = 'CHECK';
        }
        // configure
        $this->set_prefs($options);
        $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'] = 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')) {
            if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
                $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
@@ -818,6 +754,7 @@
            $this->id($this->prefs['ident']);
        }
        $auth_method  = $this->prefs['auth_type'];
        $auth_methods = array();
        $result       = null;
@@ -889,6 +826,133 @@
        $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 ? $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;
    }
    /**
     * Initializes environment
     */
    protected function set_prefs($prefs)
    {
        // set preferences
        if (is_array($prefs)) {
            $this->prefs = $prefs;
        }
        // set auth method
        if (!empty($this->prefs['auth_type'])) {
            $this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']);
        }
        else {
            $this->prefs['auth_type'] = 'CHECK';
        }
        // disabled capabilities
        if (!empty($this->prefs['disabled_caps'])) {
            $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']);
        }
        // additional message flags
        if (!empty($this->prefs['message_flags'])) {
            $this->flags = array_merge($this->flags, $this->prefs['message_flags']);
            unset($this->prefs['message_flags']);
        }
    }
    /**
@@ -1077,7 +1141,7 @@
        }
        if (!$this->data['READ-WRITE']) {
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE');
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
            return false;
        }
@@ -1327,9 +1391,8 @@
                        $folders[$mailbox] = array();
                    }
                    // store LSUB options only if not empty, this way
                    // we can detect a situation when LIST doesn't return specified folder
                    if (!empty($opts) || $cmd == 'LIST') {
                    // store folder options
                    if ($cmd == 'LIST') {
                        // Add to options array
                        if (empty($this->data['LIST'][$mailbox]))
                            $this->data['LIST'][$mailbox] = $opts;
@@ -1561,11 +1624,12 @@
        }
        // message IDs
        if (!empty($add))
        if (!empty($add)) {
            $add = $this->compressMessageSet($add);
        }
        list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT',
            array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));
            array("($field)", $encoding, !empty($add) ? $add : 'ALL'));
        if ($code != self::ERROR_OK) {
            $response = null;
@@ -1652,7 +1716,6 @@
        }
        if (!empty($criteria)) {
            $modseq = stripos($criteria, 'MODSEQ') !== false;
            $params .= ($params ? ' ' : '') . $criteria;
        }
        else {
@@ -1791,7 +1854,6 @@
                if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
                    $flags = explode(' ', strtoupper($matches[1]));
                    if (in_array('\\DELETED', $flags)) {
                        $deleted[$id] = $id;
                        continue;
                    }
                }
@@ -1817,8 +1879,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;
                    }
@@ -1927,16 +1989,28 @@
     */
    private function modFlag($mailbox, $messages, $flag, $mod = '+')
    {
        if ($mod != '+' && $mod != '-') {
            $mod = '+';
        }
        if (!$this->select($mailbox)) {
            return false;
        }
        if (!$this->data['READ-WRITE']) {
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
            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;
        }
@@ -1945,7 +2019,10 @@
            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);
@@ -1997,7 +2074,7 @@
        }
        if (!$this->data['READ-WRITE']) {
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
            return false;
        }
@@ -2158,21 +2235,25 @@
                    else if ($name == 'RFC822') {
                        $result[$id]->body = $value;
                    }
                    else if ($name == 'BODY') {
                        $body = $this->tokenizeResponse($line, 1);
                        if ($value[0] == 'HEADER.FIELDS')
                            $headers = $body;
                        else if (!empty($value))
                            $result[$id]->bodypart[$value[0]] = $body;
                    else if (stripos($name, 'BODY[') === 0) {
                        $name = str_replace(']', '', substr($name, 5));
                        if ($name == 'HEADER.FIELDS') {
                            // skip ']' after headers list
                            $this->tokenizeResponse($line, 1);
                            $headers = $this->tokenizeResponse($line, 1);
                        }
                        else if (strlen($name))
                            $result[$id]->bodypart[$name] = $value;
                        else
                            $result[$id]->body = $body;
                            $result[$id]->body = $value;
                    }
                }
                // create array with header field:data
                if (!empty($headers)) {
                    $headers = explode("\n", trim($headers));
                    foreach ($headers as $hid => $resln) {
                    foreach ($headers as $resln) {
                        if (ord($resln[0]) <= 32) {
                            $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
                        } else {
@@ -2180,7 +2261,7 @@
                        }
                    }
                    while (list($lines_key, $str) = each($lines)) {
                    foreach ($lines as $str) {
                        list($field, $string) = explode(':', $str, 2);
                        $field  = strtolower($field);
@@ -2449,50 +2530,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 $formatting 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)
@@ -2505,10 +2597,11 @@
            // handle one line response
            if ($line[0] == '(' && substr($line, -1) == ')') {
                // tokenize content inside brackets
                // the content can be e.g.: (UID 9844 BODY[2.4] NIL)
                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
                for ($i=0; $i<count($tokens); $i+=2) {
                    if (preg_match('/^(BODY|BINARY)/i', $token)) {
                    if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
                        $result = $tokens[$i+1];
                        $found  = true;
                        break;
@@ -2533,7 +2626,11 @@
                $prev  = '';
                $found = true;
                while ($bytes > 0) {
                // empty body
                if (!$bytes) {
                    $result = '';
                }
                else while ($bytes > 0) {
                    $line = $this->readLine(8192);
                    if ($line === NULL) {
@@ -2550,7 +2647,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);
@@ -2595,7 +2692,7 @@
                    }
                }
            }
        } while (!$this->startsWith($line, $key, true));
        } while (!$this->startsWith($line, $key, true) || !$initiated);
        if ($result !== false) {
            if ($file) {
@@ -2615,11 +2712,11 @@
    /**
     * Handler for IMAP APPEND command
     *
     * @param string $mailbox Mailbox name
     * @param string $message Message content
     * @param array  $flags   Message flags
     * @param string $date    Message internal date
     * @param bool   $binary  Enable BINARY append (RFC3516)
     * @param string       $mailbox Mailbox name
     * @param string|array $message The message source string or array (of strings and file pointers)
     * @param array        $flags   Message flags
     * @param string       $date    Message internal date
     * @param bool         $binary  Enable BINARY append (RFC3516)
     *
     * @return string|bool On success APPENDUID response (if available) or True, False on failure
     */
@@ -2633,13 +2730,28 @@
        $binary       = $binary && $this->getCapability('BINARY');
        $literal_plus = !$binary && $this->prefs['literal+'];
        $len          = 0;
        $msg          = is_array($message) ? $message : array(&$message);
        $chunk_size   = 512000;
        if (!$binary) {
            $message = str_replace("\r", '', $message);
            $message = str_replace("\n", "\r\n", $message);
        for ($i=0, $cnt=count($msg); $i<$cnt; $i++) {
            if (is_resource($msg[$i])) {
                $stat = fstat($msg[$i]);
                if ($stat === false) {
                    return false;
                }
                $len += $stat['size'];
            }
            else {
                if (!$binary) {
                    $msg[$i] = str_replace("\r", '', $msg[$i]);
                    $msg[$i] = str_replace("\n", "\r\n", $msg[$i]);
                }
                $len += strlen($msg[$i]);
            }
        }
        $len = strlen($message);
        if (!$len) {
            return false;
        }
@@ -2664,7 +2776,32 @@
                }
            }
            if (!$this->putLine($message)) {
            foreach ($msg as $msg_part) {
                // file pointer
                if (is_resource($msg_part)) {
                    rewind($msg_part);
                    while (!feof($msg_part) && $this->fp) {
                        $buffer = fread($msg_part, $chunk_size);
                        $this->putLine($buffer, false);
                    }
                    fclose($msg_part);
                }
                // string
                else {
                    $size = strlen($msg_part);
                    // Break up the data by sending one chunk (up to 512k) at a time.
                    // This approach reduces our peak memory usage
                    for ($offset = 0; $offset < $size; $offset += $chunk_size) {
                        $chunk = substr($msg_part, $offset, $chunk_size);
                        if (!$this->putLine($chunk, false)) {
                            return false;
                        }
                    }
                }
            }
            if (!$this->putLine('')) { // \r\n
                return false;
            }
@@ -2703,94 +2840,23 @@
     */
    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
    {
        unset($this->data['APPENDUID']);
        if ($mailbox === null || $mailbox === '') {
            return false;
        }
        // open message file
        $in_fp = false;
        if (file_exists(realpath($path))) {
            $in_fp = fopen($path, 'r');
            $fp = fopen($path, 'r');
        }
        if (!$in_fp) {
        if (!$fp) {
            $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
            return false;
        }
        $body_separator = "\r\n\r\n";
        $len = filesize($path);
        if (!$len) {
            return false;
        }
        $message = array();
        if ($headers) {
            $headers = preg_replace('/[\r\n]+$/', '', $headers);
            $len += strlen($headers) + strlen($body_separator);
            $message[] = trim($headers, "\r\n") . "\r\n\r\n";
        }
        $message[] = $fp;
        $binary       = $binary && $this->getCapability('BINARY');
        $literal_plus = !$binary && $this->prefs['literal+'];
        // build APPEND command
        $key = $this->nextTag();
        $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
        if (!empty($date)) {
            $request .= ' ' . $this->escape($date);
        }
        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
        // send APPEND command
        if ($this->putLine($request)) {
            // Don't wait when LITERAL+ is supported
            if (!$literal_plus) {
                $line = $this->readReply();
                if ($line[0] != '+') {
                    $this->parseResult($line, 'APPEND: ');
                    return false;
                }
            }
            // send headers with body separator
            if ($headers) {
                $this->putLine($headers . $body_separator, false);
            }
            // send file
            while (!feof($in_fp) && $this->fp) {
                $buffer = fgets($in_fp, 4096);
                $this->putLine($buffer, false);
            }
            fclose($in_fp);
            if (!$this->putLine('')) { // \r\n
                return false;
            }
            // read response
            do {
                $line = $this->readLine();
            } while (!$this->startsWith($line, $key, true, true));
            // Clear internal status cache
            unset($this->data['STATUS:'.$mailbox]);
            if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
                return false;
            else if (!empty($this->data['APPENDUID']))
                return $this->data['APPENDUID'];
            else
                return true;
        }
        else {
            $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
        }
        return false;
        return $this->append($mailbox, $message, $flags, $date, $binary);
    }
    /**
@@ -3008,7 +3074,7 @@
        }
        foreach ($entries as $name => $value) {
            $entries[$name] = $this->escape($name) . ' ' . $this->escape($value);
            $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true);
        }
        $entries = implode(' ', $entries);
@@ -3157,6 +3223,11 @@
        }
        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));
@@ -3413,7 +3484,7 @@
        }
        // Send command
        if (!$this->putLineC($query)) {
        if (!$this->putLineC($query, true, ($options & self::COMMAND_ANONYMIZED))) {
            $this->setError(self::ERROR_COMMAND, "Unable to send command: $query");
            return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, '');
        }
@@ -3505,25 +3576,24 @@
            // Parenthesized list
            case '(':
            case '[':
                $str = substr($str, 1);
                $result[] = self::tokenizeResponse($str);
                break;
            case ')':
            case ']':
                $str = substr($str, 1);
                return $result;
                break;
            // String atom, number, NIL, *, %
            // String atom, number, astring, NIL, *, %
            default:
                // empty string
                if ($str === '' || $str === null) {
                    break 2;
                }
                // excluded chars: SP, CTL, ), [, ]
                if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) {
                // excluded chars: SP, CTL, ), DEL
                // we do not exclude [ and ] (#1489223)
                if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) {
                    $result[] = $m[1] == 'NIL' ? NULL : $m[1];
                    $str = substr($str, strlen($m[1]));
                }
@@ -3540,7 +3610,7 @@
        if (is_array($element)) {
            reset($element);
            while (list($key, $value) = each($element)) {
            foreach ($element as $value) {
                $string .= ' ' . self::r_implode($value);
            }
        }
@@ -3714,8 +3784,16 @@
        $this->capability = explode(' ', strtoupper($str));
        if (!empty($this->prefs['disabled_caps'])) {
            $this->capability = array_diff($this->capability, $this->prefs['disabled_caps']);
        }
        if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
            $this->prefs['literal+'] = true;
        }
        if (preg_match('/(\[| )MUPDATE=.*/', $str)) {
            self::$mupdate = true;
        }
        if ($trusted) {
@@ -3759,9 +3837,10 @@
    /**
     * Set the value of the debugging flag.
     *
     * @param   boolean $debug      New value for the debugging flag.
     * @param boolean  $debug   New value for the debugging flag.
     * @param callback $handler Logging handler function
     *
     * @since   0.5-stable
     * @since 0.5-stable
     */
    function setDebug($debug, $handler = null)
    {
@@ -3772,12 +3851,18 @@
    /**
     * Write the given debug text to the current debug output handler.
     *
     * @param   string  $message    Debug mesage text.
     * @param string $message Debug mesage text.
     *
     * @since   0.5-stable
     * @since 0.5-stable
     */
    private function debug($message)
    {
        if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
            $diff    = $len - self::DEBUG_LINE_LENGTH;
            $message = substr($message, 0, self::DEBUG_LINE_LENGTH)
                . "... [truncated $diff bytes]";
        }
        if ($this->resourceid) {
            $message = sprintf('[%s] %s', $this->resourceid, $message);
        }