| | |
| | | /** |
| | | +-----------------------------------------------------------------------+ |
| | | | 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 | |
| | |
| | | '*' => '\\*', |
| | | ); |
| | | |
| | | private $fp; |
| | | private $host; |
| | | private $logged = false; |
| | | private $capability = array(); |
| | | private $capability_readed = false; |
| | | private $prefs; |
| | | private $cmd_tag; |
| | | private $cmd_num = 0; |
| | | private $resourceid; |
| | | private $_debug = false; |
| | | private $_debug_handler = false; |
| | | protected $fp; |
| | | protected $host; |
| | | protected $prefs; |
| | | protected $cmd_tag; |
| | | protected $cmd_num = 0; |
| | | protected $resourceid; |
| | | 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; |
| | | const COMMAND_CAPABILITY = 2; |
| | | const COMMAND_LASTLINE = 4; |
| | | const COMMAND_ANONYMIZED = 8; |
| | | |
| | | /** |
| | | * Object constructor |
| | | */ |
| | | function __construct() |
| | | { |
| | | } |
| | | const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n |
| | | |
| | | |
| | | /** |
| | | * 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) |
| | | protected function putLine($string, $endln = true, $anonymized = false) |
| | | { |
| | | if (!$this->fp) |
| | | if (!$this->fp) { |
| | | return false; |
| | | } |
| | | |
| | | if ($this->_debug) { |
| | | $this->debug('C: '. rtrim($string)); |
| | | if ($this->debug) { |
| | | // 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" : '')); |
| | |
| | | * 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 |
| | | */ |
| | | function putLineC($string, $endln=true) |
| | | protected function putLineC($string, $endln=true, $anonymized=false) |
| | | { |
| | | if (!$this->fp) { |
| | | return false; |
| | |
| | | $parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]); |
| | | } |
| | | |
| | | $bytes = $this->putLine($parts[$i].$parts[$i+1], false); |
| | | if ($bytes === false) |
| | | $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized); |
| | | 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); |
| | | if ($bytes === false) |
| | | $bytes = $this->putLine($parts[$i], false, $anonymized); |
| | | if ($bytes === false) { |
| | | return false; |
| | | } |
| | | |
| | | $res += $bytes; |
| | | } |
| | | } |
| | |
| | | /** |
| | | * 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) |
| | | protected function readLine($size = 1024) |
| | | { |
| | | $line = ''; |
| | | |
| | |
| | | |
| | | do { |
| | | if ($this->eof()) { |
| | | return $line ? $line : NULL; |
| | | return $line ?: null; |
| | | } |
| | | |
| | | $buffer = fgets($this->fp, $size); |
| | |
| | | $this->closeSocket(); |
| | | break; |
| | | } |
| | | if ($this->_debug) { |
| | | |
| | | if ($this->debug) { |
| | | $this->debug('S: '. rtrim($buffer)); |
| | | } |
| | | |
| | | $line .= $buffer; |
| | | } while (substr($buffer, -1) != "\n"); |
| | | } |
| | | while (substr($buffer, -1) != "\n"); |
| | | |
| | | return $line; |
| | | } |
| | |
| | | * |
| | | * @return string Line of text response |
| | | */ |
| | | function multLine($line, $escape = false) |
| | | protected function multLine($line, $escape = false) |
| | | { |
| | | $line = rtrim($line); |
| | | if (preg_match('/\{([0-9]+)\}$/', $line, $m)) { |
| | |
| | | |
| | | while (strlen($out) < $bytes) { |
| | | $line = $this->readBytes($bytes); |
| | | if ($line === NULL) |
| | | if ($line === null) { |
| | | break; |
| | | } |
| | | |
| | | $out .= $line; |
| | | } |
| | | |
| | |
| | | /** |
| | | * 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 |
| | | */ |
| | | function readBytes($bytes) |
| | | protected function readBytes($bytes) |
| | | { |
| | | $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; |
| | |
| | | /** |
| | | * 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) |
| | | protected 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] == '*'); |
| | | } |
| | | } |
| | | while ($line[0] == '*'); |
| | | |
| | | if ($untagged) |
| | | if ($untagged) { |
| | | $untagged = join("\n", $untagged); |
| | | } |
| | | |
| | | return $line; |
| | | } |
| | |
| | | /** |
| | | * Response parser. |
| | | * |
| | | * @param string $string Response text |
| | | * @param string $err_prefix Error message prefix |
| | | * @param string $string Response text |
| | | * @param string $err_prefix Error message prefix |
| | | * |
| | | * @return int Response status |
| | | */ |
| | | function parseResult($string, $err_prefix='') |
| | | protected function parseResult($string, $err_prefix = '') |
| | | { |
| | | if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) { |
| | | $res = strtoupper($matches[1]); |
| | |
| | | |
| | | if ($res == 'OK') { |
| | | $this->errornum = self::ERROR_OK; |
| | | } else if ($res == 'NO') { |
| | | } |
| | | else if ($res == 'NO') { |
| | | $this->errornum = self::ERROR_NO; |
| | | } else if ($res == 'BAD') { |
| | | } |
| | | else if ($res == 'BAD') { |
| | | $this->errornum = self::ERROR_BAD; |
| | | } else if ($res == 'BYE') { |
| | | } |
| | | else if ($res == 'BYE') { |
| | | $this->closeSocket(); |
| | | $this->errornum = self::ERROR_BYE; |
| | | } |
| | |
| | | $this->data['COPYUID'] = array($m[1], $m[2]); |
| | | } |
| | | } |
| | | |
| | | $this->result = $str; |
| | | |
| | | if ($this->errornum != self::ERROR_OK) { |
| | |
| | | |
| | | return $this->errornum; |
| | | } |
| | | |
| | | return self::ERROR_UNKNOWN; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return bool True if connection is closed |
| | | */ |
| | | private function eof() |
| | | protected function eof() |
| | | { |
| | | if (!is_resource($this->fp)) { |
| | | return true; |
| | |
| | | /** |
| | | * Closes connection stream. |
| | | */ |
| | | private function closeSocket() |
| | | protected function closeSocket() |
| | | { |
| | | @fclose($this->fp); |
| | | $this->fp = null; |
| | |
| | | /** |
| | | * Error code/message setter. |
| | | */ |
| | | function setError($code, $msg='') |
| | | protected function setError($code, $msg = '') |
| | | { |
| | | $this->errornum = $code; |
| | | $this->error = $msg; |
| | |
| | | * |
| | | * @return bool True any check is true or connection is closed. |
| | | */ |
| | | function startsWith($string, $match, $error=false, $nonempty=false) |
| | | protected 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; |
| | | } |
| | | |
| | | private function hasCapability($name) |
| | | /** |
| | | * Capabilities checker |
| | | */ |
| | | protected function hasCapability($name) |
| | | { |
| | | if (empty($this->capability) || $name == '') { |
| | | return false; |
| | |
| | | } |
| | | } |
| | | |
| | | return !empty($result) ? $result : false; |
| | | return $result ?: false; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return mixed Capability values array for key=value pairs, true/false for others |
| | | */ |
| | | function getCapability($name) |
| | | public function getCapability($name) |
| | | { |
| | | $result = $this->hasCapability($name); |
| | | |
| | |
| | | return $this->hasCapability($name); |
| | | } |
| | | |
| | | function clearCapability() |
| | | /** |
| | | * Clears detected server capabilities |
| | | */ |
| | | public 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') |
| | | protected function authenticate($user, $pass, $type = 'PLAIN') |
| | | { |
| | | if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') { |
| | | if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) { |
| | |
| | | $reply = base64_encode($user . ' ' . $hash); |
| | | |
| | | // send result |
| | | $this->putLine($reply); |
| | | $this->putLine($reply, true, true); |
| | | } |
| | | else { |
| | | // RFC2831: DIGEST-MD5 |
| | |
| | | $authc = $user; |
| | | $user = ''; |
| | | } |
| | | |
| | | $auth_sasl = Auth_SASL::factory('digestmd5'); |
| | | $reply = base64_encode($auth_sasl->getResponse($authc, $pass, |
| | | $reply = base64_encode($auth_sasl->getResponse($authc, $pass, |
| | | base64_decode($challenge), $this->host, 'imap', $user)); |
| | | |
| | | // send result |
| | | $this->putLine($reply); |
| | | $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, |
| | |
| | | $this->putLine(''); |
| | | } |
| | | |
| | | $line = $this->readReply(); |
| | | $line = $this->readReply(); |
| | | $result = $this->parseResult($line); |
| | | } |
| | | else if ($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 |
| | |
| | | // 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"); |
| | |
| | | } |
| | | |
| | | // send result, get reply and process it |
| | | $this->putLine($reply); |
| | | $line = $this->readReply(); |
| | | $this->putLine($reply, true, true); |
| | | $line = $this->readReply(); |
| | | $result = $this->parseResult($line); |
| | | } |
| | | } |
| | |
| | | /** |
| | | * LOGIN Authentication |
| | | * |
| | | * @param string $user |
| | | * @param string $pass |
| | | * @param string $user Username |
| | | * @param string $pass Password |
| | | * |
| | | * @return resource Connection resourse on success, error code on error |
| | | */ |
| | | function login($user, $password) |
| | | protected 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)) { |
| | |
| | | * |
| | | * @return string The delimiter |
| | | */ |
| | | function getHierarchyDelimiter() |
| | | public function getHierarchyDelimiter() |
| | | { |
| | | if ($this->prefs['delimiter']) { |
| | | return $this->prefs['delimiter']; |
| | |
| | | return ($this->prefs['delimiter'] = $delimiter); |
| | | } |
| | | } |
| | | |
| | | return NULL; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return array Namespace data hash (personal, other, shared) |
| | | */ |
| | | function getNamespace() |
| | | public function getNamespace() |
| | | { |
| | | if (array_key_exists('namespace', $this->prefs)) { |
| | | return $this->prefs['namespace']; |
| | |
| | | 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)) { |
| | |
| | | /** |
| | | * 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) |
| | | public 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)) { |
| | | |
| | | if (empty($password) && empty($options['gssapi_cn'])) { |
| | | $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 |
| | | 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']; |
| | | $auth_methods = array(); |
| | | $result = null; |
| | | |
| | |
| | | } |
| | | |
| | | // 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; |
| | | } |
| | |
| | | case 'CRAM-MD5': |
| | | case 'DIGEST-MD5': |
| | | case 'PLAIN': |
| | | case 'GSSAPI': |
| | | $result = $this->authenticate($user, $password, $auth_method); |
| | | break; |
| | | case 'LOGIN': |
| | |
| | | } |
| | | |
| | | /** |
| | | * Connects to IMAP server. |
| | | * |
| | | * @param string $host Server hostname or IP |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | protected function _connect($host) |
| | | { |
| | | // initialize connection |
| | | $this->error = ''; |
| | | $this->errornum = self::ERROR_OK; |
| | | |
| | | if (!$this->prefs['port']) { |
| | | $this->prefs['port'] = 143; |
| | | } |
| | | |
| | | // check for SSL |
| | | if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { |
| | | $host = $this->prefs['ssl_mode'] . '://' . $host; |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] <= 0) { |
| | | $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout'))); |
| | | } |
| | | |
| | | if (!empty($this->prefs['socket_options'])) { |
| | | $context = stream_context_create($this->prefs['socket_options']); |
| | | $this->fp = stream_socket_client($host . ':' . $this->prefs['port'], $errno, $errstr, |
| | | $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context); |
| | | } |
| | | else { |
| | | $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); |
| | | } |
| | | |
| | | if (!$this->fp) { |
| | | $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", |
| | | $host, $this->prefs['port'], $errstr ?: "Unknown reason")); |
| | | |
| | | return false; |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] > 0) { |
| | | stream_set_timeout($this->fp, $this->prefs['timeout']); |
| | | } |
| | | |
| | | $line = trim(fgets($this->fp, 8192)); |
| | | |
| | | if ($this->debug) { |
| | | // set connection identifier for debug output |
| | | preg_match('/#([0-9]+)/', (string) $this->fp, $m); |
| | | $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4)); |
| | | |
| | | if ($line) { |
| | | $this->debug('S: '. $line); |
| | | } |
| | | } |
| | | |
| | | // Connected to wrong port or connection error? |
| | | if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { |
| | | if ($line) |
| | | $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); |
| | | else |
| | | $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); |
| | | |
| | | $this->setError(self::ERROR_BAD, $error); |
| | | $this->closeConnection(); |
| | | return false; |
| | | } |
| | | |
| | | $this->data['GREETING'] = trim(preg_replace('/\[[^\]]+\]\s*/', '', $line)); |
| | | |
| | | // 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']); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Checks connection status |
| | | * |
| | | * @return bool True if connection is active and user is logged in, False otherwise. |
| | | */ |
| | | function connected() |
| | | public function connected() |
| | | { |
| | | return ($this->fp && $this->logged) ? true : false; |
| | | } |
| | |
| | | /** |
| | | * Closes connection with logout. |
| | | */ |
| | | function closeConnection() |
| | | public function closeConnection() |
| | | { |
| | | if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) { |
| | | $this->readReply(); |
| | |
| | | * |
| | | * @return boolean True on success, false on error |
| | | */ |
| | | function select($mailbox, $qresync_data = null) |
| | | public function select($mailbox, $qresync_data = null) |
| | | { |
| | | if (!strlen($mailbox)) { |
| | | return false; |
| | |
| | | // 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)) { |
| | |
| | | } |
| | | |
| | | $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY'; |
| | | |
| | | $this->selected = $mailbox; |
| | | |
| | | return true; |
| | | } |
| | | |
| | |
| | | * @return array Status item-value hash |
| | | * @since 0.5-beta |
| | | */ |
| | | function status($mailbox, $items=array()) |
| | | public function status($mailbox, $items = array()) |
| | | { |
| | | if (!strlen($mailbox)) { |
| | | return false; |
| | |
| | | list($code, $response) = $this->execute('STATUS', array($this->escape($mailbox), |
| | | '(' . implode(' ', (array) $items) . ')')); |
| | | |
| | | if ($code == self::ERROR_OK && preg_match('/\* STATUS /i', $response)) { |
| | | if ($code == self::ERROR_OK && preg_match('/^\* STATUS /i', $response)) { |
| | | $result = array(); |
| | | $response = substr($response, 9); // remove prefix "* STATUS " |
| | | |
| | |
| | | // 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; |
| | | } |
| | |
| | | * |
| | | * @return boolean True on success, False on error |
| | | */ |
| | | function expunge($mailbox, $messages=NULL) |
| | | public function expunge($mailbox, $messages = null) |
| | | { |
| | | if (!$this->select($mailbox)) { |
| | | return false; |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | // 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); |
| | |
| | | * @return boolean True on success, False on error |
| | | * @since 0.5 |
| | | */ |
| | | function close() |
| | | public 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; |
| | |
| | | * |
| | | * @return boolean True on success, False on error |
| | | */ |
| | | function subscribe($mailbox) |
| | | public function subscribe($mailbox) |
| | | { |
| | | $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)), |
| | | self::COMMAND_NORESPONSE); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return boolean True on success, False on error |
| | | */ |
| | | function unsubscribe($mailbox) |
| | | public function unsubscribe($mailbox) |
| | | { |
| | | $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)), |
| | | self::COMMAND_NORESPONSE); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | | * Folder creation (CREATE) |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param array $types Optional folder types (RFC 6154) |
| | | * |
| | | * @return bool True on success, False on error |
| | | */ |
| | | function createFolder($mailbox) |
| | | public function createFolder($mailbox, $types = null) |
| | | { |
| | | $result = $this->execute('CREATE', array($this->escape($mailbox)), |
| | | self::COMMAND_NORESPONSE); |
| | | $args = array($this->escape($mailbox)); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | // RFC 6154: CREATE-SPECIAL-USE |
| | | if (!empty($types) && $this->getCapability('CREATE-SPECIAL-USE')) { |
| | | $args[] = '(USE (' . implode(' ', $types) . '))'; |
| | | } |
| | | |
| | | $result = $this->execute('CREATE', $args, self::COMMAND_NORESPONSE); |
| | | |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return bool True on success, False on error |
| | | */ |
| | | function renameFolder($from, $to) |
| | | public function renameFolder($from, $to) |
| | | { |
| | | $result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)), |
| | | self::COMMAND_NORESPONSE); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return boolean True on success, False on error |
| | | */ |
| | | function deleteFolder($mailbox) |
| | | public function deleteFolder($mailbox) |
| | | { |
| | | $result = $this->execute('DELETE', array($this->escape($mailbox)), |
| | | self::COMMAND_NORESPONSE); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return boolean True on success, False on error |
| | | */ |
| | | function clearFolder($mailbox) |
| | | public function clearFolder($mailbox) |
| | | { |
| | | $num_in_trash = $this->countMessages($mailbox); |
| | | if ($num_in_trash > 0) { |
| | | if ($this->countMessages($mailbox) > 0) { |
| | | $res = $this->flag($mailbox, '1:*', 'DELETED'); |
| | | } |
| | | |
| | | if ($res) { |
| | | if ($this->selected === $mailbox) |
| | | if ($this->selected === $mailbox) { |
| | | $res = $this->close(); |
| | | else |
| | | } |
| | | else { |
| | | $res = $this->expunge($mailbox); |
| | | } |
| | | } |
| | | |
| | | return $res; |
| | |
| | | * |
| | | * @param string $ref Reference name |
| | | * @param string $mailbox Mailbox name |
| | | * @param array $status_opts (see self::_listMailboxes) |
| | | * @param array $return_opts (see self::_listMailboxes) |
| | | * @param array $select_opts (see self::_listMailboxes) |
| | | * |
| | | * @return array List of mailboxes or hash of options if $status_opts argument |
| | | * is non-empty. |
| | | * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response |
| | | * is requested, False on error. |
| | | */ |
| | | function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array()) |
| | | public function listMailboxes($ref, $mailbox, $return_opts = array(), $select_opts = array()) |
| | | { |
| | | return $this->_listMailboxes($ref, $mailbox, false, $status_opts, $select_opts); |
| | | return $this->_listMailboxes($ref, $mailbox, false, $return_opts, $select_opts); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @param string $ref Reference name |
| | | * @param string $mailbox Mailbox name |
| | | * @param array $status_opts (see self::_listMailboxes) |
| | | * @param array $return_opts (see self::_listMailboxes) |
| | | * |
| | | * @return array List of mailboxes or hash of options if $status_opts argument |
| | | * is non-empty. |
| | | * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response |
| | | * is requested, False on error. |
| | | */ |
| | | function listSubscribed($ref, $mailbox, $status_opts=array()) |
| | | public function listSubscribed($ref, $mailbox, $return_opts = array()) |
| | | { |
| | | return $this->_listMailboxes($ref, $mailbox, true, $status_opts, NULL); |
| | | return $this->_listMailboxes($ref, $mailbox, true, $return_opts, null); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param string $ref Reference name |
| | | * @param string $mailbox Mailbox name |
| | | * @param bool $subscribed Enables returning subscribed mailboxes only |
| | | * @param array $status_opts List of STATUS options (RFC5819: LIST-STATUS) |
| | | * Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN |
| | | * @param array $return_opts List of RETURN options (RFC5819: LIST-STATUS, RFC5258: LIST-EXTENDED) |
| | | * Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN, |
| | | * MYRIGHTS, SUBSCRIBED, CHILDREN |
| | | * @param array $select_opts List of selection options (RFC5258: LIST-EXTENDED) |
| | | * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE |
| | | * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE, |
| | | * SPECIAL-USE (RFC6154) |
| | | * |
| | | * @return array List of mailboxes or hash of options if $status_ops argument |
| | | * is non-empty. |
| | | * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response |
| | | * is requested, False on error. |
| | | */ |
| | | private function _listMailboxes($ref, $mailbox, $subscribed=false, |
| | | $status_opts=array(), $select_opts=array()) |
| | | protected function _listMailboxes($ref, $mailbox, $subscribed=false, |
| | | $return_opts=array(), $select_opts=array()) |
| | | { |
| | | if (!strlen($mailbox)) { |
| | | $mailbox = '*'; |
| | | } |
| | | |
| | | $args = array(); |
| | | $rets = array(); |
| | | |
| | | if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) { |
| | | $select_opts = (array) $select_opts; |
| | |
| | | $args[] = $this->escape($ref); |
| | | $args[] = $this->escape($mailbox); |
| | | |
| | | if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) { |
| | | $status_opts = (array) $status_opts; |
| | | $lstatus = true; |
| | | if (!empty($return_opts) && $this->getCapability('LIST-EXTENDED')) { |
| | | $ext_opts = array('SUBSCRIBED', 'CHILDREN'); |
| | | $rets = array_intersect($return_opts, $ext_opts); |
| | | $return_opts = array_diff($return_opts, $rets); |
| | | } |
| | | |
| | | $args[] = 'RETURN (STATUS (' . implode(' ', $status_opts) . '))'; |
| | | if (!empty($return_opts) && $this->getCapability('LIST-STATUS')) { |
| | | $lstatus = true; |
| | | $status_opts = array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'); |
| | | $opts = array_diff($return_opts, $status_opts); |
| | | $status_opts = array_diff($return_opts, $opts); |
| | | |
| | | if (!empty($status_opts)) { |
| | | $rets[] = 'STATUS (' . implode(' ', $status_opts) . ')'; |
| | | } |
| | | |
| | | if (!empty($opts)) { |
| | | $rets = array_merge($rets, $opts); |
| | | } |
| | | } |
| | | |
| | | if (!empty($rets)) { |
| | | $args[] = 'RETURN (' . implode(' ', $rets) . ')'; |
| | | } |
| | | |
| | | list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args); |
| | |
| | | $line = substr($response, $last, $pos - $last); |
| | | $last = $pos + 2; |
| | | |
| | | if (!preg_match('/^\* (LIST|LSUB|STATUS) /i', $line, $m)) { |
| | | if (!preg_match('/^\* (LIST|LSUB|STATUS|MYRIGHTS) /i', $line, $m)) { |
| | | continue; |
| | | } |
| | | |
| | | $cmd = strtoupper($m[1]); |
| | | $line = substr($line, strlen($m[0])); |
| | | |
| | |
| | | $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])) |
| | | if (empty($this->data['LIST'][$mailbox])) { |
| | | $this->data['LIST'][$mailbox] = $opts; |
| | | else if (!empty($opts)) |
| | | } |
| | | else if (!empty($opts)) { |
| | | $this->data['LIST'][$mailbox] = array_unique(array_merge( |
| | | $this->data['LIST'][$mailbox], $opts)); |
| | | } |
| | | } |
| | | } |
| | | // * STATUS <mailbox> (<result>) |
| | | else if ($cmd == 'STATUS') { |
| | | list($mailbox, $status) = $this->tokenizeResponse($line, 2); |
| | | else if ($lstatus) { |
| | | // * STATUS <mailbox> (<result>) |
| | | if ($cmd == 'STATUS') { |
| | | list($mailbox, $status) = $this->tokenizeResponse($line, 2); |
| | | |
| | | for ($i=0, $len=count($status); $i<$len; $i += 2) { |
| | | list($name, $value) = $this->tokenizeResponse($status, 2); |
| | | $folders[$mailbox][$name] = $value; |
| | | for ($i=0, $len=count($status); $i<$len; $i += 2) { |
| | | list($name, $value) = $this->tokenizeResponse($status, 2); |
| | | $folders[$mailbox][$name] = $value; |
| | | } |
| | | } |
| | | // * MYRIGHTS <mailbox> <acl> |
| | | else if ($cmd == 'MYRIGHTS') { |
| | | list($mailbox, $acl) = $this->tokenizeResponse($line, 2); |
| | | $folders[$mailbox]['MYRIGHTS'] = $acl; |
| | | } |
| | | } |
| | | } |
| | |
| | | * |
| | | * @return int Number of messages, False on error |
| | | */ |
| | | function countMessages($mailbox, $refresh = false) |
| | | public function countMessages($mailbox) |
| | | { |
| | | if ($refresh) { |
| | | $this->selected = null; |
| | | } |
| | | |
| | | if ($this->selected === $mailbox) { |
| | | if ($this->selected === $mailbox && isset($this->data['EXISTS'])) { |
| | | return $this->data['EXISTS']; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return int Number of messages, False on error |
| | | */ |
| | | function countRecent($mailbox) |
| | | public 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; |
| | |
| | | * |
| | | * @return int Number of messages, False on error |
| | | */ |
| | | function countUnseen($mailbox) |
| | | public function countUnseen($mailbox) |
| | | { |
| | | // Check internal cache |
| | | $cache = $this->data['STATUS:'.$mailbox]; |
| | |
| | | * @return array Server identification information key/value hash |
| | | * @since 0.6 |
| | | */ |
| | | function id($items=array()) |
| | | public function id($items = array()) |
| | | { |
| | | if (is_array($items) && !empty($items)) { |
| | | foreach ($items as $key => $value) { |
| | |
| | | !empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null) |
| | | )); |
| | | |
| | | |
| | | if ($code == self::ERROR_OK && preg_match('/\* ID /i', $response)) { |
| | | if ($code == self::ERROR_OK && preg_match('/^\* ID /i', $response)) { |
| | | $response = substr($response, 5); // remove prefix "* ID " |
| | | $items = $this->tokenizeResponse($response, 1); |
| | | $result = null; |
| | |
| | | * @return array|bool List of enabled extensions, False on error |
| | | * @since 0.6 |
| | | */ |
| | | function enable($extension) |
| | | public function enable($extension) |
| | | { |
| | | if (empty($extension)) { |
| | | return false; |
| | |
| | | |
| | | list($code, $response) = $this->execute('ENABLE', $extension); |
| | | |
| | | if ($code == self::ERROR_OK && preg_match('/\* ENABLED /i', $response)) { |
| | | if ($code == self::ERROR_OK && preg_match('/^\* ENABLED /i', $response)) { |
| | | $response = substr($response, 10); // remove prefix "* ENABLED " |
| | | $result = (array) $this->tokenizeResponse($response); |
| | | |
| | |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param string $field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO) |
| | | * @param string $add Searching criteria |
| | | * @param string $criteria Searching criteria |
| | | * @param bool $return_uid Enables UID SORT usage |
| | | * @param string $encoding Character set |
| | | * |
| | | * @return rcube_result_index Response data |
| | | */ |
| | | function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII') |
| | | public function sort($mailbox, $field = 'ARRIVAL', $criteria = '', $return_uid = false, $encoding = 'US-ASCII') |
| | | { |
| | | $field = strtoupper($field); |
| | | $old_sel = $this->selected; |
| | | $supported = array('ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO'); |
| | | $field = strtoupper($field); |
| | | |
| | | if ($field == 'INTERNALDATE') { |
| | | $field = 'ARRIVAL'; |
| | | } |
| | | |
| | | $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, |
| | | 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); |
| | | |
| | | if (!$fields[$field]) { |
| | | if (!in_array($field, $supported)) { |
| | | return new rcube_result_index($mailbox); |
| | | } |
| | | |
| | |
| | | return new rcube_result_index($mailbox); |
| | | } |
| | | |
| | | // return empty result when folder is empty and we're just after SELECT |
| | | if ($old_sel != $mailbox && !$this->data['EXISTS']) { |
| | | return new rcube_result_index($mailbox, '* SORT'); |
| | | } |
| | | |
| | | // RFC 5957: SORT=DISPLAY |
| | | if (($field == 'FROM' || $field == 'TO') && $this->getCapability('SORT=DISPLAY')) { |
| | | $field = 'DISPLAY' . $field; |
| | | } |
| | | |
| | | // message IDs |
| | | if (!empty($add)) |
| | | $add = $this->compressMessageSet($add); |
| | | $encoding = $encoding ? trim($encoding) : 'US-ASCII'; |
| | | $criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL'; |
| | | |
| | | list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT', |
| | | array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : ''))); |
| | | array("($field)", $encoding, $criteria)); |
| | | |
| | | if ($code != self::ERROR_OK) { |
| | | $response = null; |
| | |
| | | * |
| | | * @return rcube_result_thread Thread data |
| | | */ |
| | | function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII') |
| | | public function thread($mailbox, $algorithm = 'REFERENCES', $criteria = '', $return_uid = false, $encoding = 'US-ASCII') |
| | | { |
| | | $old_sel = $this->selected; |
| | | |
| | |
| | | |
| | | // return empty result when folder is empty and we're just after SELECT |
| | | if ($old_sel != $mailbox && !$this->data['EXISTS']) { |
| | | return new rcube_result_thread($mailbox); |
| | | return new rcube_result_thread($mailbox, '* THREAD'); |
| | | } |
| | | |
| | | $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)); |
| | |
| | | * |
| | | * @return rcube_result_index Result data |
| | | */ |
| | | function search($mailbox, $criteria, $return_uid=false, $items=array()) |
| | | public function search($mailbox, $criteria, $return_uid = false, $items = array()) |
| | | { |
| | | $old_sel = $this->selected; |
| | | |
| | |
| | | } |
| | | |
| | | if (!empty($criteria)) { |
| | | $modseq = stripos($criteria, 'MODSEQ') !== false; |
| | | $params .= ($params ? ' ' : '') . $criteria; |
| | | } |
| | | else { |
| | |
| | | * |
| | | * @return rcube_result_index Response data |
| | | */ |
| | | function index($mailbox, $message_set, $index_field='', $skip_deleted=true, |
| | | public function index($mailbox, $message_set, $index_field='', $skip_deleted=true, |
| | | $uidfetch=false, $return_uid=false) |
| | | { |
| | | $msg_index = $this->fetchHeaderIndex($mailbox, $message_set, |
| | |
| | | return new rcube_result_index($mailbox, $msg_index); |
| | | } |
| | | |
| | | function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true, |
| | | $uidfetch=false, $return_uid=false) |
| | | /** |
| | | * Fetches specified header/data value for a set of messages. |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $message_set Searching criteria (list of messages to return) |
| | | * @param string $index_field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO) |
| | | * @param bool $skip_deleted Makes that DELETED messages will be skipped |
| | | * @param bool $uidfetch Enables UID FETCH usage |
| | | * @param bool $return_uid Enables returning UIDs instead of IDs |
| | | * |
| | | * @return array|bool List of header values or False on failure |
| | | */ |
| | | public function fetchHeaderIndex($mailbox, $message_set, $index_field = '', $skip_deleted = true, |
| | | $uidfetch = false, $return_uid = false) |
| | | { |
| | | if (is_array($message_set)) { |
| | | if (!($message_set = $this->compressMessageSet($message_set))) |
| | | if (!($message_set = $this->compressMessageSet($message_set))) { |
| | | return false; |
| | | } else { |
| | | } |
| | | } |
| | | else { |
| | | list($from_idx, $to_idx) = explode(':', $message_set); |
| | | if (empty($message_set) || |
| | | (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { |
| | | (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx) |
| | | ) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | return false; |
| | | } |
| | | |
| | | /* Do "SELECT" command */ |
| | | // Select the mailbox |
| | | if (!$this->select($mailbox)) { |
| | | return false; |
| | | } |
| | |
| | | $cmd = $uidfetch ? 'UID FETCH' : 'FETCH'; |
| | | $fields = array(); |
| | | |
| | | if ($return_uid) |
| | | if ($return_uid) { |
| | | $fields[] = 'UID'; |
| | | if ($skip_deleted) |
| | | } |
| | | if ($skip_deleted) { |
| | | $fields[] = 'FLAGS'; |
| | | } |
| | | |
| | | if ($mode == 1) { |
| | | if ($index_field == 'DATE') |
| | | if ($index_field == 'DATE') { |
| | | $fields[] = 'INTERNALDATE'; |
| | | } |
| | | $fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]"; |
| | | } |
| | | else if ($mode == 2) { |
| | | if ($index_field == 'SIZE') |
| | | if ($index_field == 'SIZE') { |
| | | $fields[] = 'RFC822.SIZE'; |
| | | else if (!$return_uid || $index_field != 'UID') |
| | | } |
| | | else if (!$return_uid || $index_field != 'UID') { |
| | | $fields[] = $index_field; |
| | | } |
| | | } |
| | | else if ($mode == 3 && !$skip_deleted) |
| | | else if ($mode == 3 && !$skip_deleted) { |
| | | $fields[] = 'FLAGS'; |
| | | else if ($mode == 4) |
| | | } |
| | | else if ($mode == 4) { |
| | | $fields[] = 'INTERNALDATE'; |
| | | } |
| | | |
| | | $request = "$key $cmd $message_set (" . implode(' ', $fields) . ")"; |
| | | |
| | |
| | | |
| | | 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)) |
| | | if (preg_match('/UID ([0-9]+)/', $line, $matches)) { |
| | | $id = (int) $matches[1]; |
| | | else |
| | | } |
| | | else { |
| | | continue; |
| | | } |
| | | } |
| | | if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { |
| | | $flags = explode(' ', strtoupper($matches[1])); |
| | | if (in_array('\\DELETED', $flags)) { |
| | | $deleted[$id] = $id; |
| | | continue; |
| | | } |
| | | } |
| | |
| | | } |
| | | // non-existent/empty Date: header, use INTERNALDATE |
| | | if (empty($result[$id])) { |
| | | if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) |
| | | if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { |
| | | $result[$id] = $this->strToTime($matches[1]); |
| | | else |
| | | } |
| | | else { |
| | | $result[$id] = 0; |
| | | } |
| | | } |
| | | } else if ($mode == 1) { |
| | | } |
| | | else if ($mode == 1) { |
| | | if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) { |
| | | $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]); |
| | | $result[$id] = trim($value); |
| | | } else { |
| | | } |
| | | else { |
| | | $result[$id] = ''; |
| | | } |
| | | } else if ($mode == 2) { |
| | | if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { |
| | | $result[$id] = trim($matches[2]); |
| | | } else { |
| | | } |
| | | else if ($mode == 2) { |
| | | if (preg_match('/' . $index_field . ' ([0-9]+)/', $line, $matches)) { |
| | | $result[$id] = trim($matches[1]); |
| | | } |
| | | else { |
| | | $result[$id] = 0; |
| | | } |
| | | } else if ($mode == 3) { |
| | | } |
| | | else if ($mode == 3) { |
| | | if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { |
| | | $flags = explode(' ', $matches[1]); |
| | | } |
| | | $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0; |
| | | } else if ($mode == 4) { |
| | | $result[$id] = in_array("\\".$index_field, (array) $flags) ? 1 : 0; |
| | | } |
| | | else if ($mode == 4) { |
| | | if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { |
| | | $result[$id] = $this->strToTime($matches[1]); |
| | | } else { |
| | | } |
| | | else { |
| | | $result[$id] = 0; |
| | | } |
| | | } |
| | | } |
| | | } while (!$this->startsWith($line, $key, true, true)); |
| | | } |
| | | while (!$this->startsWith($line, $key, true, true)); |
| | | |
| | | return $result; |
| | | } |
| | |
| | | * |
| | | * @return int Message sequence identifier |
| | | */ |
| | | function UID2ID($mailbox, $uid) |
| | | public function UID2ID($mailbox, $uid) |
| | | { |
| | | if ($uid > 0) { |
| | | $index = $this->search($mailbox, "UID $uid"); |
| | |
| | | return (int) $arr[0]; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return int Message unique identifier |
| | | */ |
| | | function ID2UID($mailbox, $id) |
| | | public function ID2UID($mailbox, $id) |
| | | { |
| | | if (empty($id) || $id < 0) { |
| | | return null; |
| | |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | | * Sets flag of the message(s) |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $flag Flag name |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $flag Flag name |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | function flag($mailbox, $messages, $flag) { |
| | | public function flag($mailbox, $messages, $flag) |
| | | { |
| | | return $this->modFlag($mailbox, $messages, $flag, '+'); |
| | | } |
| | | |
| | | /** |
| | | * Unsets flag of the message(s) |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $flag Flag name |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $flag Flag name |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | function unflag($mailbox, $messages, $flag) { |
| | | public function unflag($mailbox, $messages, $flag) |
| | | { |
| | | return $this->modFlag($mailbox, $messages, $flag, '-'); |
| | | } |
| | | |
| | | /** |
| | | * Changes flag of the message(s) |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $flag Flag name |
| | | * @param string $mod Modifier [+|-]. Default: "+". |
| | | * @param string $mailbox Mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $flag Flag name |
| | | * @param string $mod Modifier [+|-]. Default: "+". |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | private function modFlag($mailbox, $messages, $flag, $mod = '+') |
| | | protected function modFlag($mailbox, $messages, $flag, $mod = '+') |
| | | { |
| | | if ($mod != '+' && $mod != '-') { |
| | | $mod = '+'; |
| | | if (!$flag) { |
| | | return false; |
| | | } |
| | | |
| | | if (!$this->select($mailbox)) { |
| | |
| | | } |
| | | |
| | | 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 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; |
| | | } |
| | | |
| | |
| | | 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); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | | * Copies message(s) from one folder to another |
| | | * |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $from Mailbox name |
| | | * @param string $to Destination mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $from Mailbox name |
| | | * @param string $to Destination mailbox name |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | function copy($messages, $from, $to) |
| | | public function copy($messages, $from, $to) |
| | | { |
| | | // Clear last COPYUID data |
| | | unset($this->data['COPYUID']); |
| | |
| | | $this->compressMessageSet($messages), $this->escape($to)), |
| | | self::COMMAND_NORESPONSE); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | /** |
| | | * Moves message(s) from one folder to another. |
| | | * |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $from Mailbox name |
| | | * @param string $to Destination mailbox name |
| | | * @param string|array $messages Message UID(s) |
| | | * @param string $from Mailbox name |
| | | * @param string $to Destination mailbox name |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | function move($messages, $from, $to) |
| | | public function move($messages, $from, $to) |
| | | { |
| | | if (!$this->select($from)) { |
| | | 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; |
| | | } |
| | | |
| | |
| | | |
| | | // 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)), |
| | | self::COMMAND_NORESPONSE); |
| | | |
| | | return ($result == self::ERROR_OK); |
| | | return $result == self::ERROR_OK; |
| | | } |
| | | |
| | | // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE |
| | |
| | | * @return array List of rcube_message_header elements, False on error |
| | | * @since 0.6 |
| | | */ |
| | | function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(), |
| | | public function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(), |
| | | $mod_seq = null, $vanished = false) |
| | | { |
| | | if (!$this->select($mailbox)) { |
| | |
| | | do { |
| | | $line = $this->readLine(4096); |
| | | |
| | | if (!$line) |
| | | if (!$line) { |
| | | break; |
| | | } |
| | | |
| | | // Sample reply line: |
| | | // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) |
| | |
| | | |
| | | while (strlen($out) < $bytes) { |
| | | $out = $this->readBytes($bytes); |
| | | if ($out === NULL) |
| | | if ($out === null) { |
| | | break; |
| | | } |
| | | $line .= $out; |
| | | } |
| | | |
| | | $str = $this->readLine(4096); |
| | | if ($str === false) |
| | | if ($str === false) { |
| | | break; |
| | | } |
| | | |
| | | $line .= $str; |
| | | } |
| | |
| | | else if ($name == 'FLAGS') { |
| | | if (!empty($value)) { |
| | | foreach ((array)$value as $flag) { |
| | | $flag = str_replace(array('$', '\\'), '', $flag); |
| | | $flag = str_replace(array('$', "\\"), '', $flag); |
| | | $flag = strtoupper($flag); |
| | | |
| | | $result[$id]->flags[$flag] = true; |
| | |
| | | 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 |
| | | $result[$id]->body = $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 = $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 { |
| | | } |
| | | else { |
| | | $lines[++$ln] = trim($resln); |
| | | } |
| | | } |
| | | |
| | | while (list($lines_key, $str) = each($lines)) { |
| | | foreach ($lines as $str) { |
| | | list($field, $string) = explode(':', $str, 2); |
| | | |
| | | $field = strtolower($field); |
| | |
| | | $result[$id]->date = $string; |
| | | $result[$id]->timestamp = $this->strToTime($string); |
| | | break; |
| | | case 'from': |
| | | $result[$id]->from = $string; |
| | | break; |
| | | case 'to': |
| | | $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); |
| | | break; |
| | | case 'from': |
| | | case 'subject': |
| | | $result[$id]->subject = $string; |
| | | case 'cc': |
| | | case 'bcc': |
| | | case 'references': |
| | | $result[$id]->{$field} = $string; |
| | | break; |
| | | case 'reply-to': |
| | | $result[$id]->replyto = $string; |
| | | break; |
| | | case 'cc': |
| | | $result[$id]->cc = $string; |
| | | break; |
| | | case 'bcc': |
| | | $result[$id]->bcc = $string; |
| | | break; |
| | | case 'content-transfer-encoding': |
| | | $result[$id]->encoding = $string; |
| | |
| | | break; |
| | | case 'in-reply-to': |
| | | $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); |
| | | break; |
| | | case 'references': |
| | | $result[$id]->references = $string; |
| | | break; |
| | | case 'return-receipt-to': |
| | | case 'disposition-notification-to': |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | // VANISHED response (QRESYNC RFC5162) |
| | | // Sample: * VANISHED (EARLIER) 300:310,405,411 |
| | | else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) { |
| | |
| | | |
| | | $this->data['VANISHED'] = $v_data; |
| | | } |
| | | |
| | | } while (!$this->startsWith($line, $key, true)); |
| | | } |
| | | while (!$this->startsWith($line, $key, true)); |
| | | |
| | | return $result; |
| | | } |
| | |
| | | * |
| | | * @return bool|array List of rcube_message_header elements, False on error |
| | | */ |
| | | function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array()) |
| | | public function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array()) |
| | | { |
| | | $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'); |
| | | $headers = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO', |
| | |
| | | |
| | | $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]'; |
| | | |
| | | $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items); |
| | | |
| | | return $result; |
| | | return $this->fetch($mailbox, $message_set, $is_uid, $query_items); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return bool|rcube_message_header Message data, False on error |
| | | */ |
| | | function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array()) |
| | | public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array()) |
| | | { |
| | | $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers); |
| | | if (is_array($a)) { |
| | | return array_shift($a); |
| | | } |
| | | |
| | | 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'; |
| | | } |
| | | else { |
| | | $field = strtolower($field); |
| | | } |
| | | // Strategy: First, we'll create an "index" array. |
| | | // Then, we'll use sort() on that array, and use that to sort the main array. |
| | | |
| | | if ($field == 'date' || $field == 'internaldate') { |
| | | $field = 'timestamp'; |
| | | } |
| | | $field = empty($field) ? 'uid' : strtolower($field); |
| | | $flag = empty($flag) ? 'ASC' : strtoupper($flag); |
| | | $index = array(); |
| | | $result = array(); |
| | | |
| | | if (empty($flag)) { |
| | | $flag = 'ASC'; |
| | | } else { |
| | | $flag = strtoupper($flag); |
| | | } |
| | | reset($messages); |
| | | |
| | | $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. |
| | | while (list($key, $headers) = each($messages)) { |
| | | $value = null; |
| | | |
| | | // 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); |
| | | } |
| | | 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]; |
| | | } |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | function fetchMIMEHeaders($mailbox, $uid, $parts, $mime=true) |
| | | /** |
| | | * Fetch MIME headers of specified message parts |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param int $uid Message UID |
| | | * @param array $parts Message part identifiers |
| | | * @param bool $mime Use MIME instad of HEADER |
| | | * |
| | | * @return array|bool Array containing headers string for each specified body |
| | | * False on failure. |
| | | */ |
| | | public function fetchMIMEHeaders($mailbox, $uid, $parts, $mime = true) |
| | | { |
| | | if (!$this->select($mailbox)) { |
| | | return false; |
| | |
| | | do { |
| | | $line = $this->readLine(1024); |
| | | |
| | | if (preg_match('/^\* [0-9]+ FETCH [0-9UID( ]+BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { |
| | | $idx = $matches[1]; |
| | | $headers = ''; |
| | | |
| | | // get complete entry |
| | | if (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) { |
| | | $bytes = $m[1]; |
| | | $out = ''; |
| | | |
| | | while (strlen($out) < $bytes) { |
| | | $out = $this->readBytes($bytes); |
| | | if ($out === null) |
| | | break; |
| | | $headers .= $out; |
| | | } |
| | | if (preg_match('/^\* [0-9]+ FETCH [0-9UID( ]+/', $line, $m)) { |
| | | $line = ltrim(substr($line, strlen($m[0]))); |
| | | while (preg_match('/^BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { |
| | | $line = substr($line, strlen($matches[0])); |
| | | $result[$matches[1]] = trim($this->multLine($line)); |
| | | $line = $this->readLine(1024); |
| | | } |
| | | |
| | | $result[$idx] = trim($headers); |
| | | } |
| | | } while (!$this->startsWith($line, $key, true)); |
| | | } |
| | | while (!$this->startsWith($line, $key, true)); |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL) |
| | | /** |
| | | * Fetches message part header |
| | | */ |
| | | public 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) |
| | | /** |
| | | * Fetches body of the specified message part |
| | | */ |
| | | public 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 $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) |
| | |
| | | // 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; |
| | |
| | | $prev = ''; |
| | | $found = true; |
| | | |
| | | while ($bytes > 0) { |
| | | // empty body |
| | | if (!$bytes) { |
| | | $result = ''; |
| | | } |
| | | else while ($bytes > 0) { |
| | | $line = $this->readLine(8192); |
| | | |
| | | if ($line === NULL) { |
| | | if ($line === null) { |
| | | break; |
| | | } |
| | | |
| | |
| | | |
| | | // 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); |
| | |
| | | } |
| | | } |
| | | } |
| | | } while (!$this->startsWith($line, $key, true)); |
| | | } |
| | | while (!$this->startsWith($line, $key, true) || !$initiated); |
| | | |
| | | if ($result !== false) { |
| | | if ($file) { |
| | |
| | | /** |
| | | * 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 |
| | | */ |
| | | function append($mailbox, &$message, $flags = array(), $date = null, $binary = false) |
| | | public function append($mailbox, &$message, $flags = array(), $date = null, $binary = false) |
| | | { |
| | | unset($this->data['APPENDUID']); |
| | | |
| | |
| | | |
| | | $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; |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return string|bool On success APPENDUID response (if available) or True, False on failure |
| | | */ |
| | | function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false) |
| | | public 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); |
| | | } |
| | | |
| | | /** |
| | | * Returns QUOTA information |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * |
| | | * @return array Quota information |
| | | */ |
| | | function getQuota() |
| | | public function getQuota($mailbox = null) |
| | | { |
| | | /* |
| | | * GETQUOTAROOT "INBOX" |
| | | * QUOTAROOT INBOX user/rchijiiwa1 |
| | | * QUOTA user/rchijiiwa1 (STORAGE 654 9765) |
| | | * OK Completed |
| | | */ |
| | | $result = false; |
| | | $quota_lines = array(); |
| | | $key = $this->nextTag(); |
| | | $command = $key . ' GETQUOTAROOT INBOX'; |
| | | |
| | | // get line(s) containing quota info |
| | | if ($this->putLine($command)) { |
| | | do { |
| | | $line = rtrim($this->readLine(5000)); |
| | | if (preg_match('/^\* QUOTA /', $line)) { |
| | | $quota_lines[] = $line; |
| | | } |
| | | } while (!$this->startsWith($line, $key, true, true)); |
| | | } |
| | | else { |
| | | $this->setError(self::ERROR_COMMAND, "Unable to send command: $command"); |
| | | if ($mailbox === null || $mailbox === '') { |
| | | $mailbox = 'INBOX'; |
| | | } |
| | | |
| | | // return false if not found, parse if found |
| | | // a0001 GETQUOTAROOT INBOX |
| | | // * QUOTAROOT INBOX user/sample |
| | | // * QUOTA user/sample (STORAGE 654 9765) |
| | | // a0001 OK Completed |
| | | |
| | | list($code, $response) = $this->execute('GETQUOTAROOT', array($this->escape($mailbox))); |
| | | |
| | | $result = false; |
| | | $min_free = PHP_INT_MAX; |
| | | foreach ($quota_lines as $key => $quota_line) { |
| | | $quota_line = str_replace(array('(', ')'), '', $quota_line); |
| | | $parts = explode(' ', $quota_line); |
| | | $storage_part = array_search('STORAGE', $parts); |
| | | $all = array(); |
| | | |
| | | if (!$storage_part) { |
| | | continue; |
| | | if ($code == self::ERROR_OK) { |
| | | foreach (explode("\n", $response) as $line) { |
| | | if (preg_match('/^\* QUOTA /', $line)) { |
| | | list(, , $quota_root) = $this->tokenizeResponse($line, 3); |
| | | |
| | | while ($line) { |
| | | list($type, $used, $total) = $this->tokenizeResponse($line, 1); |
| | | $type = strtolower($type); |
| | | |
| | | if ($type && $total) { |
| | | $all[$quota_root][$type]['used'] = intval($used); |
| | | $all[$quota_root][$type]['total'] = intval($total); |
| | | } |
| | | } |
| | | |
| | | if (empty($all[$quota_root]['storage'])) { |
| | | continue; |
| | | } |
| | | |
| | | $used = $all[$quota_root]['storage']['used']; |
| | | $total = $all[$quota_root]['storage']['total']; |
| | | $free = $total - $used; |
| | | |
| | | // calculate lowest available space from all storage quotas |
| | | if ($free < $min_free) { |
| | | $min_free = $free; |
| | | $result['used'] = $used; |
| | | $result['total'] = $total; |
| | | $result['percent'] = min(100, round(($used/max(1,$total))*100)); |
| | | $result['free'] = 100 - $result['percent']; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | $used = intval($parts[$storage_part+1]); |
| | | $total = intval($parts[$storage_part+2]); |
| | | $free = $total - $used; |
| | | |
| | | // return lowest available space from all quotas |
| | | if ($free < $min_free) { |
| | | $min_free = $free; |
| | | $result['used'] = $used; |
| | | $result['total'] = $total; |
| | | $result['percent'] = min(100, round(($used/max(1,$total))*100)); |
| | | $result['free'] = 100 - $result['percent']; |
| | | } |
| | | if (!empty($result)) { |
| | | $result['all'] = $all; |
| | | } |
| | | |
| | | return $result; |
| | |
| | | * |
| | | * @since 0.5-beta |
| | | */ |
| | | function setACL($mailbox, $user, $acl) |
| | | public function setACL($mailbox, $user, $acl) |
| | | { |
| | | if (is_array($acl)) { |
| | | $acl = implode('', $acl); |
| | |
| | | * |
| | | * @since 0.5-beta |
| | | */ |
| | | function deleteACL($mailbox, $user) |
| | | public function deleteACL($mailbox, $user) |
| | | { |
| | | $result = $this->execute('DELETEACL', array( |
| | | $this->escape($mailbox), $this->escape($user)), |
| | |
| | | * @return array User-rights array on success, NULL on error |
| | | * @since 0.5-beta |
| | | */ |
| | | function getACL($mailbox) |
| | | public function getACL($mailbox) |
| | | { |
| | | list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox))); |
| | | |
| | |
| | | } |
| | | |
| | | $this->setError(self::ERROR_COMMAND, "Incomplete ACL response"); |
| | | return NULL; |
| | | } |
| | | |
| | | return NULL; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return array List of user rights |
| | | * @since 0.5-beta |
| | | */ |
| | | function listRights($mailbox, $user) |
| | | public function listRights($mailbox, $user) |
| | | { |
| | | list($code, $response) = $this->execute('LISTRIGHTS', array( |
| | | $this->escape($mailbox), $this->escape($user))); |
| | |
| | | 'optional' => explode(' ', $optional), |
| | | ); |
| | | } |
| | | |
| | | return NULL; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return array MYRIGHTS response on success, NULL on error |
| | | * @since 0.5-beta |
| | | */ |
| | | function myRights($mailbox) |
| | | public function myRights($mailbox) |
| | | { |
| | | list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox))); |
| | | |
| | |
| | | |
| | | return str_split($rights); |
| | | } |
| | | |
| | | return NULL; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return boolean True on success, False on failure |
| | | * @since 0.5-beta |
| | | */ |
| | | function setMetadata($mailbox, $entries) |
| | | public function setMetadata($mailbox, $entries) |
| | | { |
| | | if (!is_array($entries) || empty($entries)) { |
| | | $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command"); |
| | |
| | | } |
| | | |
| | | 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); |
| | |
| | | * |
| | | * @since 0.5-beta |
| | | */ |
| | | function deleteMetadata($mailbox, $entries) |
| | | public function deleteMetadata($mailbox, $entries) |
| | | { |
| | | if (!is_array($entries) && !empty($entries)) { |
| | | $entries = explode(' ', $entries); |
| | |
| | | } |
| | | |
| | | foreach ($entries as $entry) { |
| | | $data[$entry] = NULL; |
| | | $data[$entry] = null; |
| | | } |
| | | |
| | | return $this->setMetadata($mailbox, $data); |
| | |
| | | * |
| | | * @since 0.5-beta |
| | | */ |
| | | function getMetadata($mailbox, $entries, $options=array()) |
| | | public function getMetadata($mailbox, $entries, $options=array()) |
| | | { |
| | | if (!is_array($entries)) { |
| | | $entries = array($entries); |
| | |
| | | for ($i=0; $i<$size; $i++) { |
| | | if (isset($mbox) && is_array($data[$i])) { |
| | | $size_sub = count($data[$i]); |
| | | for ($x=0; $x<$size_sub; $x++) { |
| | | $result[$mbox][$data[$i][$x]] = $data[$i][++$x]; |
| | | for ($x=0; $x<$size_sub; $x+=2) { |
| | | if ($data[$i][$x+1] !== null) |
| | | $result[$mbox][$data[$i][$x]] = $data[$i][$x+1]; |
| | | } |
| | | unset($data[$i]); |
| | | } |
| | |
| | | } |
| | | } |
| | | else if (isset($mbox)) { |
| | | $result[$mbox][$data[$i]] = $data[++$i]; |
| | | if ($data[++$i] !== null) |
| | | $result[$mbox][$data[$i-1]] = $data[$i]; |
| | | unset($data[$i]); |
| | | unset($data[$i-1]); |
| | | } |
| | |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | return NULL; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return boolean True on success, False on failure |
| | | * @since 0.5-beta |
| | | */ |
| | | function setAnnotation($mailbox, $data) |
| | | public function setAnnotation($mailbox, $data) |
| | | { |
| | | if (!is_array($data) || empty($data)) { |
| | | $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command"); |
| | |
| | | * |
| | | * @since 0.5-beta |
| | | */ |
| | | function deleteAnnotation($mailbox, $data) |
| | | public function deleteAnnotation($mailbox, $data) |
| | | { |
| | | if (!is_array($data) || empty($data)) { |
| | | $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command"); |
| | |
| | | * |
| | | * @since 0.5-beta |
| | | */ |
| | | function getAnnotation($mailbox, $entries, $attribs) |
| | | public function getAnnotation($mailbox, $entries, $attribs) |
| | | { |
| | | if (!is_array($entries)) { |
| | | $entries = array($entries); |
| | |
| | | for ($x=0, $len=count($attribs); $x<$len;) { |
| | | $attr = $attribs[$x++]; |
| | | $value = $attribs[$x++]; |
| | | if ($attr == 'value.priv') { |
| | | if ($attr == 'value.priv' && $value !== null) { |
| | | $result[$mbox]['/private' . $entry] = $value; |
| | | } |
| | | else if ($attr == 'value.shared') { |
| | | else if ($attr == 'value.shared' && $value !== null) { |
| | | $result[$mbox]['/shared' . $entry] = $value; |
| | | } |
| | | } |
| | |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | return NULL; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return array/bool Body structure array or False on error. |
| | | * @since 0.6 |
| | | */ |
| | | function getStructure($mailbox, $id, $is_uid = false) |
| | | public function getStructure($mailbox, $id, $is_uid = false) |
| | | { |
| | | $result = $this->fetch($mailbox, $id, $is_uid, array('BODYSTRUCTURE')); |
| | | |
| | | if (is_array($result)) { |
| | | $result = array_shift($result); |
| | | return $result->bodystructure; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return array Part data as hash array (type, encoding, charset, size) |
| | | */ |
| | | static function getStructurePartData($structure, $part) |
| | | public static function getStructurePartData($structure, $part) |
| | | { |
| | | $part_a = self::getStructurePartArray($structure, $part); |
| | | $data = array(); |
| | |
| | | return $data; |
| | | } |
| | | |
| | | static function getStructurePartArray($a, $part) |
| | | public static function getStructurePartArray($a, $part) |
| | | { |
| | | if (!is_array($a)) { |
| | | return false; |
| | |
| | | * @return string Command identifier |
| | | * @since 0.5-beta |
| | | */ |
| | | function nextTag() |
| | | public function nextTag() |
| | | { |
| | | $this->cmd_num++; |
| | | $this->cmd_tag = sprintf('A%04d', $this->cmd_num); |
| | |
| | | * @return mixed Response code or list of response code and data |
| | | * @since 0.5-beta |
| | | */ |
| | | function execute($command, $arguments=array(), $options=0) |
| | | public function execute($command, $arguments=array(), $options=0) |
| | | { |
| | | $tag = $this->nextTag(); |
| | | $query = $tag . ' ' . $command; |
| | |
| | | } |
| | | |
| | | // 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, ''); |
| | | } |
| | |
| | | if ($response !== null) { |
| | | $response .= $line; |
| | | } |
| | | } while (!$this->startsWith($line, $tag . ' ', true, true)); |
| | | } |
| | | while (!$this->startsWith($line, $tag . ' ', true, true)); |
| | | |
| | | $code = $this->parseResult($line, $command . ': '); |
| | | |
| | |
| | | * @return mixed Tokens array or string if $num=1 |
| | | * @since 0.5-beta |
| | | */ |
| | | static function tokenizeResponse(&$str, $num=0) |
| | | public static function tokenizeResponse(&$str, $num=0) |
| | | { |
| | | $result = array(); |
| | | |
| | |
| | | |
| | | // 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)) { |
| | | $result[] = $m[1] == 'NIL' ? NULL : $m[1]; |
| | | // 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])); |
| | | } |
| | | break; |
| | |
| | | return $num == 1 ? $result[0] : $result; |
| | | } |
| | | |
| | | static function r_implode($element) |
| | | protected static function r_implode($element) |
| | | { |
| | | $string = ''; |
| | | |
| | | if (is_array($element)) { |
| | | reset($element); |
| | | while (list($key, $value) = each($element)) { |
| | | foreach ($element as $value) { |
| | | $string .= ' ' . self::r_implode($value); |
| | | } |
| | | } |
| | |
| | | * |
| | | * @return string Compressed sequence-set |
| | | */ |
| | | static function compressMessageSet($messages, $force=false) |
| | | public static function compressMessageSet($messages, $force=false) |
| | | { |
| | | // given a comma delimited list of independent mid's, |
| | | // compresses by grouping sequences together |
| | |
| | | if ($incr > 1) { // found a gap |
| | | if ($start == $prev) { |
| | | $result[] = $prev; // push single id |
| | | } else { |
| | | } |
| | | else { |
| | | $result[] = $start . ':' . $prev; // push sequence as start_id:end_id |
| | | } |
| | | $start = $id; // start of new sequence |
| | |
| | | // handle the last sequence/id |
| | | if ($start == $prev) { |
| | | $result[] = $prev; |
| | | } else { |
| | | } |
| | | else { |
| | | $result[] = $start.':'.$prev; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return array List of message identifiers |
| | | */ |
| | | static function uncompressMessageSet($messages) |
| | | public static function uncompressMessageSet($messages) |
| | | { |
| | | if (empty($messages)) { |
| | | return array(); |
| | |
| | | return $result; |
| | | } |
| | | |
| | | private function _xor($string, $string2) |
| | | protected function _xor($string, $string2) |
| | | { |
| | | $result = ''; |
| | | $size = strlen($string); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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) |
| | | * |
| | | * @return string Space-separated list of flags |
| | | */ |
| | | private function flagsToStr($flags) |
| | | protected function flagsToStr($flags) |
| | | { |
| | | foreach ((array)$flags as $idx => $flag) { |
| | | if ($flag = $this->flags[strtoupper($flag)]) { |
| | |
| | | * |
| | | * @return int Unix timestamp |
| | | */ |
| | | static function strToTime($date) |
| | | protected static function strToTime($date) |
| | | { |
| | | // Clean malformed data |
| | | $date = preg_replace( |
| | |
| | | /** |
| | | * CAPABILITY response parser |
| | | */ |
| | | private function parseCapability($str, $trusted=false) |
| | | protected function parseCapability($str, $trusted=false) |
| | | { |
| | | $str = preg_replace('/^\* CAPABILITY /i', '', $str); |
| | | |
| | | $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; |
| | |
| | | * @return string String atom, quoted-string or string literal |
| | | * @todo lists |
| | | */ |
| | | static function escape($string, $force_quotes=false) |
| | | public static function escape($string, $force_quotes=false) |
| | | { |
| | | if ($string === null) { |
| | | return 'NIL'; |
| | |
| | | /** |
| | | * 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) |
| | | public function setDebug($debug, $handler = null) |
| | | { |
| | | $this->_debug = $debug; |
| | | $this->_debug_handler = $handler; |
| | | $this->debug = $debug; |
| | | $this->debug_handler = $handler; |
| | | } |
| | | |
| | | /** |
| | | * 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) |
| | | protected 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); |
| | | } |
| | | |
| | | if ($this->_debug_handler) { |
| | | call_user_func_array($this->_debug_handler, array(&$this, $message)); |
| | | } else { |
| | | if ($this->debug_handler) { |
| | | call_user_func_array($this->debug_handler, array(&$this, $message)); |
| | | } |
| | | else { |
| | | echo "DEBUG: $message\n"; |
| | | } |
| | | } |
| | | |
| | | } |