| | |
| | | '*' => '\\*', |
| | | ); |
| | | |
| | | 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; |
| | | public static $mupdate; |
| | | |
| | | protected $fp; |
| | | protected $host; |
| | | protected $logged = false; |
| | | protected $capability = array(); |
| | | protected $capability_readed = false; |
| | | protected $prefs; |
| | | protected $cmd_tag; |
| | | protected $cmd_num = 0; |
| | | protected $resourceid; |
| | | protected $_debug = false; |
| | | protected $_debug_handler = false; |
| | | |
| | | const ERROR_OK = 0; |
| | | const ERROR_NO = -1; |
| | |
| | | const COMMAND_NORESPONSE = 1; |
| | | const COMMAND_CAPABILITY = 2; |
| | | const COMMAND_LASTLINE = 4; |
| | | const COMMAND_ANONYMIZED = 8; |
| | | |
| | | const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n |
| | | |
| | |
| | | * |
| | | * @param string $string Command string |
| | | * @param bool $endln True if CRLF need to be added at the end of command |
| | | * @param bool $anonymized Don't write the given data to log but a placeholder |
| | | * |
| | | * @param int Number of bytes sent, False on error |
| | | */ |
| | | function putLine($string, $endln=true) |
| | | function putLine($string, $endln=true, $anonymized=false) |
| | | { |
| | | if (!$this->fp) |
| | | return false; |
| | | |
| | | if ($this->_debug) { |
| | | $this->debug('C: '. rtrim($string)); |
| | | // anonymize the sent command for logging |
| | | $cut = $endln ? 2 : 0; |
| | | if ($anonymized && preg_match('/^(A\d+ (?:[A-Z]+ )+)(.+)/', $string, $m)) { |
| | | $log = $m[1] . sprintf('****** [%d]', strlen($m[2]) - $cut); |
| | | } |
| | | else if ($anonymized) { |
| | | $log = sprintf('****** [%d]', strlen($string) - $cut); |
| | | } |
| | | else { |
| | | $log = rtrim($string); |
| | | } |
| | | $this->debug('C: ' . $log); |
| | | } |
| | | |
| | | $res = fwrite($this->fp, $string . ($endln ? "\r\n" : '')); |
| | |
| | | * |
| | | * @param string $string Command string |
| | | * @param bool $endln True if CRLF need to be added at the end of command |
| | | * @param bool $anonymized Don't write the given data to log but a placeholder |
| | | * |
| | | * @return int|bool Number of bytes sent, False on error |
| | | */ |
| | | function putLineC($string, $endln=true) |
| | | function putLineC($string, $endln=true, $anonymized=false) |
| | | { |
| | | if (!$this->fp) { |
| | | return false; |
| | |
| | | $parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]); |
| | | } |
| | | |
| | | $bytes = $this->putLine($parts[$i].$parts[$i+1], false); |
| | | $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized); |
| | | if ($bytes === false) |
| | | return false; |
| | | $res += $bytes; |
| | |
| | | $i++; |
| | | } |
| | | else { |
| | | $bytes = $this->putLine($parts[$i], false); |
| | | $bytes = $this->putLine($parts[$i], false, $anonymized); |
| | | if ($bytes === false) |
| | | return false; |
| | | $res += $bytes; |
| | |
| | | * |
| | | * @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; |
| | |
| | | return false; |
| | | } |
| | | |
| | | private function hasCapability($name) |
| | | protected function hasCapability($name) |
| | | { |
| | | if (empty($this->capability) || $name == '') { |
| | | return false; |
| | |
| | | $reply = base64_encode($user . ' ' . $hash); |
| | | |
| | | // send result |
| | | $this->putLine($reply); |
| | | $this->putLine($reply, true, true); |
| | | } |
| | | else { |
| | | // RFC2831: DIGEST-MD5 |
| | |
| | | base64_decode($challenge), $this->host, 'imap', $user)); |
| | | |
| | | // send result |
| | | $this->putLine($reply); |
| | | $this->putLine($reply, true, true); |
| | | $line = trim($this->readReply()); |
| | | |
| | | if ($line[0] == '+') { |
| | |
| | | // 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); |
| | | $this->putLine($reply, true, true); |
| | | $line = $this->readReply(); |
| | | $result = $this->parseResult($line); |
| | | } |
| | |
| | | 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)) { |
| | |
| | | */ |
| | | function connect($host, $user, $password, $options=null) |
| | | { |
| | | // set options |
| | | if (is_array($options)) { |
| | | $this->prefs = $options; |
| | | } |
| | | // set auth method |
| | | if (!empty($this->prefs['auth_type'])) { |
| | | $auth_method = strtoupper($this->prefs['auth_type']); |
| | | } else { |
| | | $auth_method = 'CHECK'; |
| | | } |
| | | // configure |
| | | $this->set_prefs($options); |
| | | |
| | | $result = false; |
| | | |
| | | // initialize connection |
| | | $this->error = ''; |
| | | $this->errornum = self::ERROR_OK; |
| | | $this->selected = null; |
| | | $this->user = $user; |
| | | $this->host = $host; |
| | | $this->user = $user; |
| | | $this->logged = false; |
| | | $this->selected = null; |
| | | |
| | | // check input |
| | | if (empty($host)) { |
| | | $this->setError(self::ERROR_BAD, "Empty host"); |
| | | return false; |
| | | } |
| | | |
| | | if (empty($user)) { |
| | | $this->setError(self::ERROR_NO, "Empty user"); |
| | | return false; |
| | | } |
| | | |
| | | if (empty($password)) { |
| | | $this->setError(self::ERROR_NO, "Empty password"); |
| | | return false; |
| | | } |
| | | |
| | | if (!$this->prefs['port']) { |
| | | $this->prefs['port'] = 143; |
| | | } |
| | | // check for SSL |
| | | if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { |
| | | $host = $this->prefs['ssl_mode'] . '://' . $host; |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] <= 0) { |
| | | $this->prefs['timeout'] = max(0, intval(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 |
| | |
| | | $this->id($this->prefs['ident']); |
| | | } |
| | | |
| | | $auth_method = $this->prefs['auth_type']; |
| | | $auth_methods = array(); |
| | | $result = null; |
| | | |
| | |
| | | $this->closeConnection(); |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Connects to IMAP server. |
| | | * |
| | | * @param string $host Server hostname or IP |
| | | * |
| | | * @return bool True on success, False on failure |
| | | */ |
| | | protected function _connect($host) |
| | | { |
| | | // initialize connection |
| | | $this->error = ''; |
| | | $this->errornum = self::ERROR_OK; |
| | | |
| | | if (!$this->prefs['port']) { |
| | | $this->prefs['port'] = 143; |
| | | } |
| | | |
| | | // check for SSL |
| | | if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { |
| | | $host = $this->prefs['ssl_mode'] . '://' . $host; |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] <= 0) { |
| | | $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout'))); |
| | | } |
| | | |
| | | if (!empty($this->prefs['socket_options'])) { |
| | | $context = stream_context_create($this->prefs['socket_options']); |
| | | $this->fp = stream_socket_client($host . ':' . $this->prefs['port'], $errno, $errstr, |
| | | $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context); |
| | | } |
| | | else { |
| | | $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); |
| | | } |
| | | |
| | | if (!$this->fp) { |
| | | $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", |
| | | $host, $this->prefs['port'], $errstr ?: "Unknown reason")); |
| | | |
| | | return false; |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] > 0) { |
| | | stream_set_timeout($this->fp, $this->prefs['timeout']); |
| | | } |
| | | |
| | | $line = trim(fgets($this->fp, 8192)); |
| | | |
| | | if ($this->_debug) { |
| | | // set connection identifier for debug output |
| | | preg_match('/#([0-9]+)/', (string) $this->fp, $m); |
| | | $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4)); |
| | | |
| | | if ($line) { |
| | | $this->debug('S: '. $line); |
| | | } |
| | | } |
| | | |
| | | // Connected to wrong port or connection error? |
| | | if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { |
| | | if ($line) |
| | | $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); |
| | | else |
| | | $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); |
| | | |
| | | $this->setError(self::ERROR_BAD, $error); |
| | | $this->closeConnection(); |
| | | return false; |
| | | } |
| | | |
| | | // RFC3501 [7.1] optional CAPABILITY response |
| | | if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { |
| | | $this->parseCapability($matches[1], true); |
| | | } |
| | | |
| | | // TLS connection |
| | | if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { |
| | | $res = $this->execute('STARTTLS'); |
| | | |
| | | if ($res[0] != self::ERROR_OK) { |
| | | $this->closeConnection(); |
| | | return false; |
| | | } |
| | | |
| | | if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { |
| | | $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); |
| | | $this->closeConnection(); |
| | | return false; |
| | | } |
| | | |
| | | // Now we're secure, capabilities need to be reread |
| | | $this->clearCapability(); |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Initializes environment |
| | | */ |
| | | protected function set_prefs($prefs) |
| | | { |
| | | // set preferences |
| | | if (is_array($prefs)) { |
| | | $this->prefs = $prefs; |
| | | } |
| | | |
| | | // set auth method |
| | | if (!empty($this->prefs['auth_type'])) { |
| | | $this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']); |
| | | } |
| | | else { |
| | | $this->prefs['auth_type'] = 'CHECK'; |
| | | } |
| | | |
| | | // disabled capabilities |
| | | if (!empty($this->prefs['disabled_caps'])) { |
| | | $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']); |
| | | } |
| | | |
| | | // additional message flags |
| | | if (!empty($this->prefs['message_flags'])) { |
| | | $this->flags = array_merge($this->flags, $this->prefs['message_flags']); |
| | | unset($this->prefs['message_flags']); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * 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) |
| | | function createFolder($mailbox, $types = null) |
| | | { |
| | | $result = $this->execute('CREATE', array($this->escape($mailbox)), |
| | | self::COMMAND_NORESPONSE); |
| | | $args = array($this->escape($mailbox)); |
| | | |
| | | // 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); |
| | | } |
| | |
| | | * |
| | | * @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()) |
| | | 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()) |
| | | 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])) |
| | | $this->data['LIST'][$mailbox] = $opts; |
| | |
| | | $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; |
| | | } |
| | | } |
| | | } |
| | |
| | | * |
| | | * @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') |
| | | 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, !empty($add) ? $add : 'ALL')); |
| | | array("($field)", $encoding, $criteria)); |
| | | |
| | | if ($code != self::ERROR_OK) { |
| | | $response = null; |
| | |
| | | |
| | | // 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'; |
| | |
| | | $result[$id] = ''; |
| | | } |
| | | } else if ($mode == 2) { |
| | | if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { |
| | | $result[$id] = trim($matches[2]); |
| | | if (preg_match('/' . $index_field . ' ([0-9]+)/', $line, $matches)) { |
| | | $result[$id] = trim($matches[1]); |
| | | } else { |
| | | $result[$id] = 0; |
| | | } |
| | |
| | | * |
| | | * @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 (!$this->select($mailbox)) { |
| | | return false; |
| | | } |
| | |
| | | return false; |
| | | } |
| | | |
| | | if ($this->flags[strtoupper($flag)]) { |
| | | $flag = $this->flags[strtoupper($flag)]; |
| | | } |
| | | |
| | | if (!$flag || (!in_array($flag, (array) $this->data['PERMANENTFLAGS']) |
| | | && !in_array('\\*', (array) $this->data['PERMANENTFLAGS'])) |
| | | ) { |
| | | return false; |
| | | } |
| | | |
| | | // Clear internal status cache |
| | | if ($flag == 'SEEN') { |
| | | 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); |
| | |
| | | else if ($name == 'RFC822') { |
| | | $result[$id]->body = $value; |
| | | } |
| | | else if ($name == 'BODY') { |
| | | $body = $this->tokenizeResponse($line, 1); |
| | | if ($value[0] == 'HEADER.FIELDS') |
| | | $headers = $body; |
| | | else if (!empty($value)) |
| | | $result[$id]->bodypart[$value[0]] = $body; |
| | | else if (stripos($name, 'BODY[') === 0) { |
| | | $name = str_replace(']', '', substr($name, 5)); |
| | | |
| | | if ($name == 'HEADER.FIELDS') { |
| | | // skip ']' after headers list |
| | | $this->tokenizeResponse($line, 1); |
| | | $headers = $this->tokenizeResponse($line, 1); |
| | | } |
| | | else if (strlen($name)) |
| | | $result[$id]->bodypart[$name] = $value; |
| | | else |
| | | $result[$id]->body = $body; |
| | | $result[$id]->body = $value; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if ($binary) { |
| | | // WARNING: Use $formatting argument with care, this may break binary data stream |
| | | // WARNING: Use $formatted argument with care, this may break binary data stream |
| | | $mode = -1; |
| | | } |
| | | |
| | |
| | | // 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) { |
| | |
| | | $prev = ''; |
| | | $found = true; |
| | | |
| | | while ($bytes > 0) { |
| | | // empty body |
| | | if (!$bytes) { |
| | | $result = ''; |
| | | } |
| | | else while ($bytes > 0) { |
| | | $line = $this->readLine(8192); |
| | | |
| | | if ($line === NULL) { |
| | |
| | | /** |
| | | * Returns QUOTA information |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * |
| | | * @return array Quota information |
| | | */ |
| | | function getQuota() |
| | | 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; |
| | |
| | | } |
| | | |
| | | 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); |
| | |
| | | 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]; |
| | | if ($data[$i][$x+1] !== null) |
| | | $result[$mbox][$data[$i][$x]] = $data[$i][++$x]; |
| | | } |
| | | unset($data[$i]); |
| | | } |
| | |
| | | } |
| | | } |
| | | else if (isset($mbox)) { |
| | | $result[$mbox][$data[$i]] = $data[++$i]; |
| | | if ($data[$i+1] !== null) |
| | | $result[$mbox][$data[$i]] = $data[++$i]; |
| | | unset($data[$i]); |
| | | unset($data[$i-1]); |
| | | } |
| | |
| | | } |
| | | |
| | | foreach ($data as $entry) { |
| | | // Workaround cyrus-murder bug, the entry[2] string needs to be escaped |
| | | if (self::$mupdate) { |
| | | $entry[2] = addcslashes($entry[2], '\\"'); |
| | | } |
| | | |
| | | // ANNOTATEMORE drafts before version 08 require quoted parameters |
| | | $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true), |
| | | $this->escape($entry[1], true), $this->escape($entry[2], true)); |
| | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | // 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, ''); |
| | | } |
| | |
| | | |
| | | // Parenthesized list |
| | | case '(': |
| | | case '[': |
| | | $str = substr($str, 1); |
| | | $result[] = self::tokenizeResponse($str); |
| | | break; |
| | | case ')': |
| | | case ']': |
| | | $str = substr($str, 1); |
| | | return $result; |
| | | break; |
| | | |
| | | // String atom, number, NIL, *, % |
| | | // String atom, number, astring, NIL, *, % |
| | | default: |
| | | // empty string |
| | | if ($str === '' || $str === null) { |
| | | break 2; |
| | | } |
| | | |
| | | // excluded chars: SP, CTL, ), [, ] |
| | | if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) { |
| | | // excluded chars: SP, CTL, ), DEL |
| | | // we do not exclude [ and ] (#1489223) |
| | | if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) { |
| | | $result[] = $m[1] == 'NIL' ? NULL : $m[1]; |
| | | $str = substr($str, strlen($m[1])); |
| | | } |
| | |
| | | return $result; |
| | | } |
| | | |
| | | private function _xor($string, $string2) |
| | | protected function _xor($string, $string2) |
| | | { |
| | | $result = ''; |
| | | $size = strlen($string); |
| | |
| | | * |
| | | * @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)]) { |
| | |
| | | /** |
| | | * 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; |
| | | } |
| | | |
| | | if (preg_match('/(\[| )MUPDATE=.*/', $str)) { |
| | | self::$mupdate = true; |
| | | } |
| | | |
| | | if ($trusted) { |
| | |
| | | * |
| | | * @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; |