From 85f4209074aab255dacd766109af5092017606ae Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Fri, 02 Oct 2015 04:56:35 -0400 Subject: [PATCH] Code improvements: CS fixes, improved internal cache cleanup on folder selection, removed redundant cache --- program/lib/Roundcube/rcube_imap_generic.php | 536 ++++++++++++++++++++++++++++++++++++---------------------- 1 files changed, 330 insertions(+), 206 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index a43dfee..85cbfa9 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -3,7 +3,7 @@ /** +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2012, The Roundcube Dev Team | + | Copyright (C) 2005-2015, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | @@ -48,26 +48,24 @@ '*' => '\\*', ); - public static $mupdate; - 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; + protected $logged = false; + protected $capability = array(); + protected $capability_readed = false; + protected $debug = false; + protected $debug_handler = false; - const ERROR_OK = 0; - const ERROR_NO = -1; - const ERROR_BAD = -2; - const ERROR_BYE = -3; - const ERROR_UNKNOWN = -4; - const ERROR_COMMAND = -5; + const ERROR_OK = 0; + const ERROR_NO = -1; + const ERROR_BAD = -2; + const ERROR_BYE = -3; + const ERROR_UNKNOWN = -4; + const ERROR_COMMAND = -5; const ERROR_READONLY = -6; const COMMAND_NORESPONSE = 1; @@ -77,28 +75,23 @@ const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n - /** - * Object constructor - */ - function __construct() - { - } /** * Send simple (one line) command to the connection stream * - * @param string $string Command string - * @param bool $endln True if CRLF need to be added at the end of command + * @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, $anonymized=false) + function putLine($string, $endln = true, $anonymized = false) { - if (!$this->fp) + if (!$this->fp) { return false; + } - if ($this->_debug) { + if ($this->debug) { // anonymize the sent command for logging $cut = $endln ? 2 : 0; if ($anonymized && preg_match('/^(A\d+ (?:[A-Z]+ )+)(.+)/', $string, $m)) { @@ -110,6 +103,7 @@ else { $log = rtrim($string); } + $this->debug('C: ' . $log); } @@ -127,8 +121,8 @@ * Send command to the connection stream with Command Continuation * Requests (RFC3501 7.5) and LITERAL+ (RFC2088) support * - * @param string $string Command string - * @param bool $endln True if CRLF need to be added at the end of command + * @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 @@ -153,23 +147,29 @@ } $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized); - if ($bytes === false) + if ($bytes === false) { return false; + } + $res += $bytes; // don't wait if server supports LITERAL+ capability if (!$this->prefs['literal+']) { $line = $this->readLine(1000); // handle error in command - if ($line[0] != '+') + if ($line[0] != '+') { return false; + } } + $i++; } else { $bytes = $this->putLine($parts[$i], false, $anonymized); - if ($bytes === false) + if ($bytes === false) { return false; + } + $res += $bytes; } } @@ -180,11 +180,11 @@ /** * Reads line from the connection stream * - * @param int $size Buffer size + * @param int $size Buffer size * * @return string Line of text response */ - function readLine($size=1024) + function readLine($size = 1024) { $line = ''; @@ -194,7 +194,7 @@ do { if ($this->eof()) { - return $line ? $line : NULL; + return $line ? $line : null; } $buffer = fgets($this->fp, $size); @@ -203,9 +203,11 @@ $this->closeSocket(); break; } - if ($this->_debug) { + + if ($this->debug) { $this->debug('S: '. rtrim($buffer)); } + $line .= $buffer; } while (substr($buffer, -1) != "\n"); @@ -231,8 +233,10 @@ while (strlen($out) < $bytes) { $line = $this->readBytes($bytes); - if ($line === NULL) + if ($line === null) { break; + } + $out .= $line; } @@ -245,7 +249,7 @@ /** * Reads specified number of bytes from the connection stream * - * @param int $bytes Number of bytes to get + * @param int $bytes Number of bytes to get * * @return string Response text */ @@ -253,10 +257,10 @@ { $data = ''; $len = 0; - while ($len < $bytes && !$this->eof()) - { + + while ($len < $bytes && !$this->eof()) { $d = fread($this->fp, $bytes-$len); - if ($this->_debug) { + if ($this->debug) { $this->debug('S: '. $d); } $data .= $d; @@ -273,21 +277,23 @@ /** * Reads complete response to the IMAP command * - * @param array $untagged Will be filled with untagged response lines + * @param array $untagged Will be filled with untagged response lines * * @return string Response text */ - function readReply(&$untagged=null) + function readReply(&$untagged = null) { do { $line = trim($this->readLine(1024)); // store untagged response lines - if ($line[0] == '*') + if ($line[0] == '*') { $untagged[] = $line; + } } while ($line[0] == '*'); - if ($untagged) + if ($untagged) { $untagged = join("\n", $untagged); + } return $line; } @@ -344,6 +350,7 @@ return $this->errornum; } + return self::ERROR_UNKNOWN; } @@ -384,7 +391,7 @@ /** * Error code/message setter. */ - function setError($code, $msg='') + function setError($code, $msg = '') { $this->errornum = $code; $this->error = $msg; @@ -401,23 +408,27 @@ * * @return bool True any check is true or connection is closed. */ - function startsWith($string, $match, $error=false, $nonempty=false) + function startsWith($string, $match, $error = false, $nonempty = false) { if (!$this->fp) { return true; } + if (strncmp($string, $match, strlen($match)) == 0) { return true; } + if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) { if (strtoupper($m[1]) == 'BYE') { $this->closeSocket(); } return true; } + if ($nonempty && !strlen($string)) { return true; } + return false; } @@ -478,20 +489,20 @@ function clearCapability() { - $this->capability = array(); + $this->capability = array(); $this->capability_readed = false; } /** * DIGEST-MD5/CRAM-MD5/PLAIN Authentication * - * @param string $user - * @param string $pass + * @param string $user Username + * @param string $pass Password * @param string $type Authentication type (PLAIN/CRAM-MD5/DIGEST-MD5) * * @return resource Connection resourse on success, error code on error */ - function authenticate($user, $pass, $type='PLAIN') + function authenticate($user, $pass, $type = 'PLAIN') { if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') { if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) { @@ -554,14 +565,12 @@ $this->putLine($reply, true, true); $line = trim($this->readReply()); - if ($line[0] == '+') { - $challenge = substr($line, 2); - } - else { + if ($line[0] != '+') { return $this->parseResult($line); } // check response + $challenge = substr($line, 2); $challenge = base64_decode($challenge); if (strpos($challenge, 'rspauth=') === false) { $this->setError(self::ERROR_BAD, @@ -573,6 +582,66 @@ } $line = $this->readReply(); + $result = $this->parseResult($line); + } + elseif ($type == 'GSSAPI') { + if (!extension_loaded('krb5')) { + $this->setError(self::ERROR_BYE, + "The krb5 extension is required for GSSAPI authentication"); + return self::ERROR_BAD; + } + + if (empty($this->prefs['gssapi_cn'])) { + $this->setError(self::ERROR_BYE, + "The gssapi_cn parameter is required for GSSAPI authentication"); + return self::ERROR_BAD; + } + + if (empty($this->prefs['gssapi_context'])) { + $this->setError(self::ERROR_BYE, + "The gssapi_context parameter is required for GSSAPI authentication"); + return self::ERROR_BAD; + } + + putenv('KRB5CCNAME=' . $this->prefs['gssapi_cn']); + + try { + $ccache = new KRB5CCache(); + $ccache->open($this->prefs['gssapi_cn']); + $gssapicontext = new GSSAPIContext(); + $gssapicontext->acquireCredentials($ccache); + + $token = ''; + $success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], null, null, null, $token); + $token = base64_encode($token); + } + catch (Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + $this->setError(self::ERROR_BYE, "GSSAPI authentication failed"); + return self::ERROR_BAD; + } + + $this->putLine($this->nextTag() . " AUTHENTICATE GSSAPI " . $token); + $line = trim($this->readReply()); + + if ($line[0] != '+') { + return $this->parseResult($line); + } + + try { + $challenge = base64_decode(substr($line, 2)); + $gssapicontext->unwrap($challenge, $challenge); + $gssapicontext->wrap($challenge, $challenge, true); + } + catch (Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + $this->setError(self::ERROR_BYE, "GSSAPI authentication failed"); + return self::ERROR_BAD; + } + + $this->putLine(base64_encode($challenge)); + + $line = $this->readReply(); $result = $this->parseResult($line); } else { // PLAIN @@ -670,8 +739,6 @@ return ($this->prefs['delimiter'] = $delimiter); } } - - return NULL; } /** @@ -692,7 +759,8 @@ list($code, $response) = $this->execute('NAMESPACE'); if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) { - $data = $this->tokenizeResponse(substr($response, 11)); + $response = substr($response, 11); + $data = $this->tokenizeResponse($response); } if (!is_array($data)) { @@ -711,14 +779,14 @@ /** * Connects to IMAP server and authenticates. * - * @param string $host Server hostname or IP - * @param string $user User name - * @param string $password Password - * @param array $options Connection and class options + * @param string $host Server hostname or IP + * @param string $user User name + * @param string $password Password + * @param array $options Connection and class options * * @return bool True on success, False on failure */ - function connect($host, $user, $password, $options=null) + function connect($host, $user, $password, $options = null) { // configure $this->set_prefs($options); @@ -739,7 +807,7 @@ return false; } - if (empty($password)) { + if (empty($password) && empty($options['gssapi_cn'])) { $this->setError(self::ERROR_NO, "Empty password"); return false; } @@ -751,7 +819,7 @@ // Send ID info if (!empty($this->prefs['ident']) && $this->getCapability('ID')) { - $this->id($this->prefs['ident']); + $this->data['ID'] = $this->id($this->prefs['ident']); } $auth_method = $this->prefs['auth_type']; @@ -775,7 +843,8 @@ } // Use best (for security) supported authentication method - foreach (array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN') as $auth_method) { + $all_methods = array('GSSAPI', 'DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN'); + foreach ($all_methods as $auth_method) { if (in_array($auth_method, $auth_methods)) { break; } @@ -804,6 +873,7 @@ case 'CRAM-MD5': case 'DIGEST-MD5': case 'PLAIN': + case 'GSSAPI': $result = $this->authenticate($user, $password, $auth_method); break; case 'LOGIN': @@ -876,7 +946,7 @@ $line = trim(fgets($this->fp, 8192)); - if ($this->_debug) { + 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)); @@ -897,6 +967,8 @@ $this->closeConnection(); return false; } + + $this->data['GREETING'] = trim(preg_replace('/\[[^\]]+\]\s*/', '', $line)); // RFC3501 [7.1] optional CAPABILITY response if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { @@ -1013,14 +1085,18 @@ // 3. an optional parenthesized list of known sequence ranges and their // corresponding UIDs. if (!empty($qresync_data)) { - if (!empty($qresync_data[2])) + if (!empty($qresync_data[2])) { $qresync_data[2] = self::compressMessageSet($qresync_data[2]); + } + $params[] = array('QRESYNC', $qresync_data); } list($code, $response) = $this->execute('SELECT', $params); if ($code == self::ERROR_OK) { + $this->clear_mailbox_cache(); + $response = explode("\r\n", $response); foreach ($response as $line) { if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) { @@ -1063,8 +1139,8 @@ } $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY'; - $this->selected = $mailbox; + return true; } @@ -1082,7 +1158,7 @@ * @return array Status item-value hash * @since 0.5-beta */ - function status($mailbox, $items=array()) + function status($mailbox, $items = array()) { if (!strlen($mailbox)) { return false; @@ -1108,7 +1184,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; } @@ -1134,7 +1211,7 @@ * * @return boolean True on success, False on error */ - function expunge($mailbox, $messages=NULL) + function expunge($mailbox, $messages = null) { if (!$this->select($mailbox)) { return false; @@ -1146,7 +1223,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); @@ -1172,7 +1249,7 @@ */ function close() { - $result = $this->execute('CLOSE', NULL, self::COMMAND_NORESPONSE); + $result = $this->execute('CLOSE', null, self::COMMAND_NORESPONSE); if ($result == self::ERROR_OK) { $this->selected = null; @@ -1314,9 +1391,9 @@ * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response * is requested, False on error. */ - function listSubscribed($ref, $mailbox, $return_opts=array()) + function listSubscribed($ref, $mailbox, $return_opts = array()) { - return $this->_listMailboxes($ref, $mailbox, true, $return_opts, NULL); + return $this->_listMailboxes($ref, $mailbox, true, $return_opts, null); } /** @@ -1461,13 +1538,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']; } @@ -1495,14 +1568,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; @@ -1546,7 +1625,7 @@ * @return array Server identification information key/value hash * @since 0.6 */ - function id($items=array()) + function id($items = array()) { if (is_array($items) && !empty($items)) { foreach ($items as $key => $value) { @@ -1558,7 +1637,6 @@ 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 " @@ -1704,7 +1782,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)); @@ -1882,7 +1959,7 @@ if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { $id = $m[1]; - $flags = NULL; + $flags = null; if ($return_uid) { if (preg_match('/UID ([0-9]+)/', $line, $matches)) @@ -1959,7 +2036,6 @@ return (int) $arr[0]; } } - return null; } /** @@ -1980,14 +2056,20 @@ return null; } + if ($uid = $this->data['UID-MAP'][$id]) { + return $uid; + } + + if (isset($this->data['EXISTS']) && $id > $this->data['EXISTS']) { + return null; + } + $index = $this->search($mailbox, $id, true); if ($index->count() == 1) { $arr = $index->get(); - return (int) $arr[0]; + return $this->data['UID-MAP'][$id] = (int) $arr[0]; } - - return null; } /** @@ -2041,8 +2123,14 @@ $flag = $this->flags[strtoupper($flag)]; } - if (!$flag || !in_array($flag, (array) $this->data['PERMANENTFLAGS']) - || !in_array('\\*', (array) $this->data['PERMANENTFLAGS']) + 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; } @@ -2118,7 +2206,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)), @@ -2215,7 +2303,7 @@ while (strlen($out) < $bytes) { $out = $this->readBytes($bytes); - if ($out === NULL) + if ($out === null) break; $line .= $out; } @@ -2432,7 +2520,16 @@ return false; } - function sortHeaders($a, $field, $flag) + /** + * Sort messages by specified header field + * + * @param array $messages Array of rcube_message_header objects + * @param string $field Name of the property to sort by + * @param string $flag Sorting order (ASC|DESC) + * + * @return array Sorted input array + */ + public static function sortHeaders($messages, $field, $flag) { if (empty($field)) { $field = 'uid'; @@ -2441,57 +2538,65 @@ $field = strtolower($field); } - if ($field == 'date' || $field == 'internaldate') { - $field = 'timestamp'; - } - if (empty($flag)) { $flag = 'ASC'; - } else { + } + else { $flag = strtoupper($flag); } - $c = count($a); - if ($c > 0) { - // Strategy: - // First, we'll create an "index" array. - // Then, we'll use sort() on that array, - // and use that to sort the main array. + // Strategy: First, we'll create an "index" array. + // Then, we'll use sort() on that array, and use that to sort the main array. - // create "index" array - $index = array(); - reset($a); - while (list($key, $val) = each($a)) { - if ($field == 'timestamp') { - $data = $this->strToTime($val->date); - if (!$data) { - $data = $val->timestamp; - } - } else { - $data = $val->$field; - if (is_string($data)) { - $data = str_replace('"', '', $data); - if ($field == 'subject') { - $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data); - } - $data = strtoupper($data); - } + $index = array(); + $result = array(); + + reset($messages); + + while (list($key, $headers) = each($messages)) { + $value = null; + + switch ($field) { + case 'arrival': + $field = 'internaldate'; + case 'date': + case 'internaldate': + case 'timestamp': + $value = self::strToTime($headers->$field); + if (!$value && $field != 'timestamp') { + $value = $headers->timestamp; } - $index[$key] = $data; + + break; + + default: + // @TODO: decode header value, convert to UTF-8 + $value = $headers->$field; + if (is_string($value)) { + $value = str_replace('"', '', $value); + if ($field == 'subject') { + $value = preg_replace('/^(Re:\s*|Fwd:\s*|Fw:\s*)+/i', '', $value); + } + + $data = strtoupper($value); + } } + $index[$key] = $value; + } + + if (!empty($index)) { // sort index if ($flag == 'ASC') { asort($index); - } else { + } + else { arsort($index); } // form new array based on index - $result = array(); - reset($index); while (list($key, $val) = each($index)) { - $result[$key] = $a[$key]; + $result[$key] = $messages[$key]; } } @@ -2550,63 +2655,74 @@ return $result; } - function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL) + function fetchPartHeader($mailbox, $id, $is_uid = false, $part = null) { $part = empty($part) ? 'HEADER' : $part.'.MIME'; return $this->handlePartBody($mailbox, $id, $is_uid, $part); } - function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL, $formatted=false, $max_bytes=0) + function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=null, $print=null, $file=null, $formatted=false, $max_bytes=0) { if (!$this->select($mailbox)) { 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) @@ -2655,7 +2771,7 @@ else while ($bytes > 0) { $line = $this->readLine(8192); - if ($line === NULL) { + if ($line === null) { break; } @@ -2669,7 +2785,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); @@ -2714,7 +2830,7 @@ } } } - } while (!$this->startsWith($line, $key, true)); + } while (!$this->startsWith($line, $key, true) || !$initiated); if ($result !== false) { if ($file) { @@ -3023,10 +3139,7 @@ } $this->setError(self::ERROR_COMMAND, "Incomplete ACL response"); - return NULL; } - - return NULL; } /** @@ -3057,8 +3170,6 @@ 'optional' => explode(' ', $optional), ); } - - return NULL; } /** @@ -3082,8 +3193,6 @@ return str_split($rights); } - - return NULL; } /** @@ -3136,7 +3245,7 @@ } foreach ($entries as $entry) { - $data[$entry] = NULL; + $data[$entry] = null; } return $this->setMetadata($mailbox, $data); @@ -3199,9 +3308,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++) { + for ($x=0; $x<$size_sub; $x+=2) { if ($data[$i][$x+1] !== null) - $result[$mbox][$data[$i][$x]] = $data[$i][++$x]; + $result[$mbox][$data[$i][$x]] = $data[$i][$x+1]; } unset($data[$i]); } @@ -3219,8 +3328,8 @@ } } else if (isset($mbox)) { - if ($data[$i+1] !== null) - $result[$mbox][$data[$i]] = $data[++$i]; + if ($data[++$i] !== null) + $result[$mbox][$data[$i-1]] = $data[$i]; unset($data[$i]); unset($data[$i-1]); } @@ -3232,8 +3341,6 @@ return $result; } - - return NULL; } /** @@ -3254,11 +3361,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)); @@ -3380,8 +3482,6 @@ return $result; } - - return NULL; } /** @@ -3625,7 +3725,7 @@ // 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]; + $result[] = $m[1] == 'NIL' ? null : $m[1]; $str = substr($str, strlen($m[1])); } break; @@ -3751,6 +3851,35 @@ } /** + * Clear internal status cache + */ + protected function clear_status_cache($mailbox) + { + unset($this->data['STATUS:' . $mailbox]); + + $keys = array('EXISTS', 'RECENT', 'UNSEEN', 'UID-MAP'); + + foreach ($keys as $key) { + unset($this->data[$key]); + } + } + + /** + * Clear internal cache of the current mailbox + */ + protected function clear_mailbox_cache() + { + $this->clear_status_cache($this->selected); + + $keys = array('UIDNEXT', 'UIDVALIDITY', 'HIGHESTMODSEQ', 'NOMODSEQ', + 'PERMANENTFLAGS', 'QRESYNC', 'VANISHED', 'READ-WRITE'); + + foreach ($keys as $key) { + unset($this->data[$key]); + } + } + + /** * Converts flags array into string for inclusion in IMAP command * * @param array $flags Flags (see self::flags) @@ -3823,10 +3952,6 @@ $this->prefs['literal+'] = true; } - if (preg_match('/(\[| )MUPDATE=.*/', $str)) { - self::$mupdate = true; - } - if ($trusted) { $this->capability_readed = true; } @@ -3875,8 +4000,8 @@ */ function setDebug($debug, $handler = null) { - $this->_debug = $debug; - $this->_debug_handler = $handler; + $this->debug = $debug; + $this->debug_handler = $handler; } /** @@ -3898,11 +4023,10 @@ $message = sprintf('[%s] %s', $this->resourceid, $message); } - if ($this->_debug_handler) { - call_user_func_array($this->_debug_handler, array(&$this, $message)); + if ($this->debug_handler) { + call_user_func_array($this->debug_handler, array(&$this, $message)); } else { echo "DEBUG: $message\n"; } } - } -- Gitblit v1.9.1