diff --git a/lib/imap/imap_rcube.php b/lib/imap/imap_rcube.php index e4afe85..f8a860a 100644 --- a/lib/imap/imap_rcube.php +++ b/lib/imap/imap_rcube.php @@ -474,46 +474,6 @@ class imap_rcube return $result ?: false; } - /** - * Capabilities checker - * - * @param string $name Capability name - * - * @return mixed Capability values array for key=value pairs, true/false for others - */ - public function getCapability($name) - { - $result = $this->hasCapability($name); - - if (!empty($result)) { - return $result; - } - else if ($this->capability_readed) { - return false; - } - - // get capabilities (only once) because initial - // optional CAPABILITY response may differ - $result = $this->execute('CAPABILITY'); - - if ($result[0] == self::ERROR_OK) { - $this->parseCapability($result[1]); - } - - $this->capability_readed = true; - - return $this->hasCapability($name); - } - - /** - * Clears detected server capabilities - */ - public function clearCapability() - { - $this->capability = array(); - $this->capability_readed = false; - } - /** * DIGEST-MD5/CRAM-MD5/PLAIN Authentication * @@ -782,66 +742,6 @@ class imap_rcube return $code; } - /** - * Detects hierarchy delimiter - * - * @return string The delimiter - */ - public function getHierarchyDelimiter() - { - if ($this->prefs['delimiter']) { - return $this->prefs['delimiter']; - } - - // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8) - list($code, $response) = $this->execute('LIST', - array($this->escape(''), $this->escape(''))); - - if ($code == self::ERROR_OK) { - $args = $this->tokenizeResponse($response, 4); - $delimiter = $args[3]; - - if (strlen($delimiter) > 0) { - return ($this->prefs['delimiter'] = $delimiter); - } - } - } - - /** - * NAMESPACE handler (RFC 2342) - * - * @return array Namespace data hash (personal, other, shared) - */ - public function getNamespace() - { - if (array_key_exists('namespace', $this->prefs)) { - return $this->prefs['namespace']; - } - - if (!$this->getCapability('NAMESPACE')) { - return self::ERROR_BAD; - } - - list($code, $response) = $this->execute('NAMESPACE'); - - if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) { - $response = substr($response, 11); - $data = $this->tokenizeResponse($response); - } - - if (!is_array($data)) { - return $code; - } - - $this->prefs['namespace'] = array( - 'personal' => $data[0], - 'other' => $data[1], - 'shared' => $data[2], - ); - - return $this->prefs['namespace']; - } - /** * Connects to IMAP server and authenticates. * @@ -1234,100 +1134,6 @@ class imap_rcube return false; } - /** - * Executes STATUS command - * - * @param string $mailbox Mailbox name - * @param array $items Additional requested item names. By default - * MESSAGES and UNSEEN are requested. Other defined - * in RFC3501: UIDNEXT, UIDVALIDITY, RECENT - * - * @return array Status item-value hash - * @since 0.5-beta - */ - public function status($mailbox, $items = array()) - { - if (!strlen($mailbox)) { - return false; - } - - if (!in_array('MESSAGES', $items)) { - $items[] = 'MESSAGES'; - } - if (!in_array('UNSEEN', $items)) { - $items[] = 'UNSEEN'; - } - - list($code, $response) = $this->execute('STATUS', - array($this->escape($mailbox), '(' . implode(' ', $items) . ')'), 0, '/^\* STATUS /i'); - - if ($code == self::ERROR_OK && $response) { - $result = array(); - $response = substr($response, 9); // remove prefix "* STATUS " - - list($mbox, $items) = $this->tokenizeResponse($response, 2); - - // Fix for #1487859. Some buggy server returns not quoted - // 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); - } - - if (!is_array($items)) { - return $result; - } - - for ($i=0, $len=count($items); $i<$len; $i += 2) { - $result[$items[$i]] = $items[$i+1]; - } - - $this->data['STATUS:'.$mailbox] = $result; - - return $result; - } - - return false; - } - - /** - * Executes EXPUNGE command - * - * @param string $mailbox Mailbox name - * @param string|array $messages Message UIDs to expunge - * - * @return boolean True on success, False on error - */ - 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"); - return false; - } - - // Clear internal status cache - $this->clear_status_cache($mailbox); - - if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) { - $messages = self::compressMessageSet($messages); - $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE); - } - else { - $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE); - } - - if ($result == self::ERROR_OK) { - $this->selected = null; // state has changed, need to reselect - return true; - } - - return false; - } - /** * Executes CLOSE command * @@ -1346,884 +1152,6 @@ class imap_rcube return false; } - /** - * Folder subscription (SUBSCRIBE) - * - * @param string $mailbox Mailbox name - * - * @return boolean True on success, False on error - */ - public function subscribe($mailbox) - { - $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)), - self::COMMAND_NORESPONSE); - - return $result == self::ERROR_OK; - } - - /** - * Folder unsubscription (UNSUBSCRIBE) - * - * @param string $mailbox Mailbox name - * - * @return boolean True on success, False on error - */ - public function unsubscribe($mailbox) - { - $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)), - self::COMMAND_NORESPONSE); - - 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 - */ - public function createFolder($mailbox, $types = null) - { - $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; - } - - /** - * Folder renaming (RENAME) - * - * @param string $mailbox Mailbox name - * - * @return bool True on success, False on error - */ - public function renameFolder($from, $to) - { - $result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)), - self::COMMAND_NORESPONSE); - - return $result == self::ERROR_OK; - } - - /** - * Executes DELETE command - * - * @param string $mailbox Mailbox name - * - * @return boolean True on success, False on error - */ - public function deleteFolder($mailbox) - { - $result = $this->execute('DELETE', array($this->escape($mailbox)), - self::COMMAND_NORESPONSE); - - return $result == self::ERROR_OK; - } - - /** - * Removes all messages in a folder - * - * @param string $mailbox Mailbox name - * - * @return boolean True on success, False on error - */ - public function clearFolder($mailbox) - { - if ($this->countMessages($mailbox) > 0) { - $res = $this->flag($mailbox, '1:*', 'DELETED'); - } - - if ($res) { - if ($this->selected === $mailbox) { - $res = $this->close(); - } - else { - $res = $this->expunge($mailbox); - } - } - - return $res; - } - - /** - * Returns list of mailboxes - * - * @param string $ref Reference name - * @param string $mailbox Mailbox name - * @param array $return_opts (see self::_listMailboxes) - * @param array $select_opts (see self::_listMailboxes) - * - * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response - * is requested, False on error. - */ - public function listMailboxes($ref, $mailbox, $return_opts = array(), $select_opts = array()) - { - return $this->_listMailboxes($ref, $mailbox, false, $return_opts, $select_opts); - } - - /** - * Returns list of subscribed mailboxes - * - * @param string $ref Reference name - * @param string $mailbox Mailbox name - * @param array $return_opts (see self::_listMailboxes) - * - * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response - * is requested, False on error. - */ - public function listSubscribed($ref, $mailbox, $return_opts = array()) - { - return $this->_listMailboxes($ref, $mailbox, true, $return_opts, null); - } - - /** - * IMAP LIST/LSUB command - * - * @param string $ref Reference name - * @param string $mailbox Mailbox name - * @param bool $subscribed Enables returning subscribed mailboxes only - * @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, - * SPECIAL-USE (RFC6154) - * - * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response - * is requested, False on error. - */ - 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[] = '(' . implode(' ', $select_opts) . ')'; - } - - $args[] = $this->escape($ref); - $args[] = $this->escape($mailbox); - - 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); - } - - 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); - - if ($code == self::ERROR_OK) { - $folders = array(); - $last = 0; - $pos = 0; - $response .= "\r\n"; - - while ($pos = strpos($response, "\r\n", $pos+1)) { - // literal string, not real end-of-command-line - if ($response[$pos-1] == '}') { - continue; - } - - $line = substr($response, $last, $pos - $last); - $last = $pos + 2; - - if (!preg_match('/^\* (LIST|LSUB|STATUS|MYRIGHTS) /i', $line, $m)) { - continue; - } - - $cmd = strtoupper($m[1]); - $line = substr($line, strlen($m[0])); - - // * LIST () - if ($cmd == 'LIST' || $cmd == 'LSUB') { - list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3); - - // Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879) - if ($delim) { - $mailbox = rtrim($mailbox, $delim); - } - - // Add to result array - if (!$lstatus) { - $folders[] = $mailbox; - } - else { - $folders[$mailbox] = array(); - } - - // store folder options - if ($cmd == 'LIST') { - // Add to options array - if (empty($this->data['LIST'][$mailbox])) { - $this->data['LIST'][$mailbox] = $opts; - } - else if (!empty($opts)) { - $this->data['LIST'][$mailbox] = array_unique(array_merge( - $this->data['LIST'][$mailbox], $opts)); - } - } - } - else if ($lstatus) { - // * STATUS () - 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; - } - } - // * MYRIGHTS - else if ($cmd == 'MYRIGHTS') { - list($mailbox, $acl) = $this->tokenizeResponse($line, 2); - $folders[$mailbox]['MYRIGHTS'] = $acl; - } - } - } - - return $folders; - } - - return false; - } - - /** - * Returns count of all messages in a folder - * - * @param string $mailbox Mailbox name - * - * @return int Number of messages, False on error - */ - public function countMessages($mailbox) - { - if ($this->selected === $mailbox && isset($this->data['EXISTS'])) { - return $this->data['EXISTS']; - } - - // Check internal cache - $cache = $this->data['STATUS:'.$mailbox]; - if (!empty($cache) && isset($cache['MESSAGES'])) { - return (int) $cache['MESSAGES']; - } - - // Try STATUS (should be faster than SELECT) - $counts = $this->status($mailbox); - if (is_array($counts)) { - return (int) $counts['MESSAGES']; - } - - return false; - } - - /** - * Returns count of messages with \Recent flag in a folder - * - * @param string $mailbox Mailbox name - * - * @return int Number of messages, False on error - */ - public function countRecent($mailbox) - { - if ($this->selected === $mailbox && isset($this->data['RECENT'])) { - return $this->data['RECENT']; - } - - // Check internal cache - $cache = $this->data['STATUS:'.$mailbox]; - if (!empty($cache) && isset($cache['RECENT'])) { - return (int) $cache['RECENT']; - } - - // Try STATUS (should be faster than SELECT) - $counts = $this->status($mailbox, array('RECENT')); - if (is_array($counts)) { - return (int) $counts['RECENT']; - } - - return false; - } - - /** - * Returns count of messages without \Seen flag in a specified folder - * - * @param string $mailbox Mailbox name - * - * @return int Number of messages, False on error - */ - public function countUnseen($mailbox) - { - // Check internal cache - $cache = $this->data['STATUS:'.$mailbox]; - if (!empty($cache) && isset($cache['UNSEEN'])) { - return (int) $cache['UNSEEN']; - } - - // Try STATUS (should be faster than SELECT+SEARCH) - $counts = $this->status($mailbox); - if (is_array($counts)) { - return (int) $counts['UNSEEN']; - } - - // Invoke SEARCH as a fallback - $index = $this->search($mailbox, 'ALL UNSEEN', false, array('COUNT')); - if (!$index->is_error()) { - return $index->count(); - } - - return false; - } - - /** - * Executes ID command (RFC2971) - * - * @param array $items Client identification information key/value hash - * - * @return array Server identification information key/value hash - * @since 0.6 - */ - public function id($items = array()) - { - if (is_array($items) && !empty($items)) { - foreach ($items as $key => $value) { - $args[] = $this->escape($key, true); - $args[] = $this->escape($value, true); - } - } - - list($code, $response) = $this->execute('ID', - array(!empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)), - 0, '/^\* ID /i'); - - if ($code == self::ERROR_OK && $response) { - $response = substr($response, 5); // remove prefix "* ID " - $items = $this->tokenizeResponse($response, 1); - $result = null; - - for ($i=0, $len=count($items); $i<$len; $i += 2) { - $result[$items[$i]] = $items[$i+1]; - } - - return $result; - } - - return false; - } - - /** - * Executes ENABLE command (RFC5161) - * - * @param mixed $extension Extension name to enable (or array of names) - * - * @return array|bool List of enabled extensions, False on error - * @since 0.6 - */ - public function enable($extension) - { - if (empty($extension)) { - return false; - } - - if (!$this->hasCapability('ENABLE')) { - return false; - } - - if (!is_array($extension)) { - $extension = array($extension); - } - - if (!empty($this->extensions_enabled)) { - // check if all extensions are already enabled - $diff = array_diff($extension, $this->extensions_enabled); - - if (empty($diff)) { - return $extension; - } - - // Make sure the mailbox isn't selected, before enabling extension(s) - if ($this->selected !== null) { - $this->close(); - } - } - - list($code, $response) = $this->execute('ENABLE', $extension, 0, '/^\* ENABLED /i'); - - if ($code == self::ERROR_OK && $response) { - $response = substr($response, 10); // remove prefix "* ENABLED " - $result = (array) $this->tokenizeResponse($response); - - $this->extensions_enabled = array_unique(array_merge((array)$this->extensions_enabled, $result)); - - return $this->extensions_enabled; - } - - return false; - } - - /** - * Executes SORT command - * - * @param string $mailbox Mailbox name - * @param string $field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO) - * @param string $criteria Searching criteria - * @param bool $return_uid Enables UID SORT usage - * @param string $encoding Character set - * - * @return rcube_result_index Response data - */ - public function sort($mailbox, $field = 'ARRIVAL', $criteria = '', $return_uid = false, $encoding = 'US-ASCII') - { - $old_sel = $this->selected; - $supported = array('ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO'); - $field = strtoupper($field); - - if ($field == 'INTERNALDATE') { - $field = 'ARRIVAL'; - } - - if (!in_array($field, $supported)) { - return new rcube_result_index($mailbox); - } - - if (!$this->select($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; - } - - $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, $criteria)); - - if ($code != self::ERROR_OK) { - $response = null; - } - - return new rcube_result_index($mailbox, $response); - } - - /** - * Executes THREAD command - * - * @param string $mailbox Mailbox name - * @param string $algorithm Threading algorithm (ORDEREDSUBJECT, REFERENCES, REFS) - * @param string $criteria Searching criteria - * @param bool $return_uid Enables UIDs in result instead of sequence numbers - * @param string $encoding Character set - * - * @return rcube_result_thread Thread data - */ - public function thread($mailbox, $algorithm = 'REFERENCES', $criteria = '', $return_uid = false, $encoding = 'US-ASCII') - { - $old_sel = $this->selected; - - if (!$this->select($mailbox)) { - return new rcube_result_thread($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_thread($mailbox, '* THREAD'); - } - - $encoding = $encoding ? trim($encoding) : 'US-ASCII'; - $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; - $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; - - list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD', - array($algorithm, $encoding, $criteria)); - - if ($code != self::ERROR_OK) { - $response = null; - } - - return new rcube_result_thread($mailbox, $response); - } - - /** - * Executes SEARCH command - * - * @param string $mailbox Mailbox name - * @param string $criteria Searching criteria - * @param bool $return_uid Enable UID in result instead of sequence ID - * @param array $items Return items (MIN, MAX, COUNT, ALL) - * - * @return rcube_result_index Result data - */ - public function search($mailbox, $criteria, $return_uid = false, $items = array()) - { - $old_sel = $this->selected; - - if (!$this->select($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, '* SEARCH'); - } - - // If ESEARCH is supported always use ALL - // but not when items are specified or using simple id2uid search - if (empty($items) && preg_match('/[^0-9]/', $criteria)) { - $items = array('ALL'); - } - - $esearch = empty($items) ? false : $this->getCapability('ESEARCH'); - $criteria = trim($criteria); - $params = ''; - - // RFC4731: ESEARCH - if (!empty($items) && $esearch) { - $params .= 'RETURN (' . implode(' ', $items) . ')'; - } - - if (!empty($criteria)) { - $params .= ($params ? ' ' : '') . $criteria; - } - else { - $params .= 'ALL'; - } - - list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', - array($params)); - - if ($code != self::ERROR_OK) { - $response = null; - } - - return new rcube_result_index($mailbox, $response); - } - - /** - * Simulates SORT command by using FETCH and sorting. - * - * @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 rcube_result_index Response data - */ - public function index($mailbox, $message_set, $index_field='', $skip_deleted=true, - $uidfetch=false, $return_uid=false) - { - $msg_index = $this->fetchHeaderIndex($mailbox, $message_set, - $index_field, $skip_deleted, $uidfetch, $return_uid); - - if (!empty($msg_index)) { - asort($msg_index); // ASC - $msg_index = array_keys($msg_index); - $msg_index = '* SEARCH ' . implode(' ', $msg_index); - } - else { - $msg_index = is_array($msg_index) ? '* SEARCH' : null; - } - - return new rcube_result_index($mailbox, $msg_index); - } - - /** - * 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))) { - return false; - } - } - else { - list($from_idx, $to_idx) = explode(':', $message_set); - if (empty($message_set) || - (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx) - ) { - return false; - } - } - - $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); - - $fields_a['DATE'] = 1; - $fields_a['INTERNALDATE'] = 4; - $fields_a['ARRIVAL'] = 4; - $fields_a['FROM'] = 1; - $fields_a['REPLY-TO'] = 1; - $fields_a['SENDER'] = 1; - $fields_a['TO'] = 1; - $fields_a['CC'] = 1; - $fields_a['SUBJECT'] = 1; - $fields_a['UID'] = 2; - $fields_a['SIZE'] = 2; - $fields_a['SEEN'] = 3; - $fields_a['RECENT'] = 3; - $fields_a['DELETED'] = 3; - - if (!($mode = $fields_a[$index_field])) { - return false; - } - - // Select the mailbox - if (!$this->select($mailbox)) { - return false; - } - - // build FETCH command string - $key = $this->nextTag(); - $cmd = $uidfetch ? 'UID FETCH' : 'FETCH'; - $fields = array(); - - if ($return_uid) { - $fields[] = 'UID'; - } - if ($skip_deleted) { - $fields[] = 'FLAGS'; - } - - if ($mode == 1) { - if ($index_field == 'DATE') { - $fields[] = 'INTERNALDATE'; - } - $fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]"; - } - else if ($mode == 2) { - if ($index_field == 'SIZE') { - $fields[] = 'RFC822.SIZE'; - } - else if (!$return_uid || $index_field != 'UID') { - $fields[] = $index_field; - } - } - else if ($mode == 3 && !$skip_deleted) { - $fields[] = 'FLAGS'; - } - else if ($mode == 4) { - $fields[] = 'INTERNALDATE'; - } - - $request = "$key $cmd $message_set (" . implode(' ', $fields) . ")"; - - if (!$this->putLine($request)) { - $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command"); - return false; - } - - $result = array(); - - do { - $line = rtrim($this->readLine(200)); - $line = $this->multLine($line); - - if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { - $id = $m[1]; - $flags = null; - - if ($return_uid) { - if (preg_match('/UID ([0-9]+)/', $line, $matches)) { - $id = (int) $matches[1]; - } - else { - continue; - } - } - if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { - $flags = explode(' ', strtoupper($matches[1])); - if (in_array('\\DELETED', $flags)) { - continue; - } - } - - if ($mode == 1 && $index_field == 'DATE') { - if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) { - $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]); - $value = trim($value); - $result[$id] = rcube_utils::strtotime($value); - } - // non-existent/empty Date: header, use INTERNALDATE - if (empty($result[$id])) { - if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { - $result[$id] = rcube_utils::strtotime($matches[1]); - } - else { - $result[$id] = 0; - } - } - } - 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 { - $result[$id] = ''; - } - } - 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) { - if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { - $flags = explode(' ', $matches[1]); - } - $result[$id] = in_array("\\".$index_field, (array) $flags) ? 1 : 0; - } - else if ($mode == 4) { - if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { - $result[$id] = rcube_utils::strtotime($matches[1]); - } - else { - $result[$id] = 0; - } - } - } - } - while (!$this->startsWith($line, $key, true, true)); - - return $result; - } - - /** - * Returns message sequence identifier - * - * @param string $mailbox Mailbox name - * @param int $uid Message unique identifier (UID) - * - * @return int Message sequence identifier - */ - public function UID2ID($mailbox, $uid) - { - if ($uid > 0) { - $index = $this->search($mailbox, "UID $uid"); - - if ($index->count() == 1) { - $arr = $index->get(); - return (int) $arr[0]; - } - } - } - - /** - * Returns message unique identifier (UID) - * - * @param string $mailbox Mailbox name - * @param int $uid Message sequence identifier - * - * @return int Message unique identifier - */ - public function ID2UID($mailbox, $id) - { - if (empty($id) || $id < 0) { - return null; - } - - if (!$this->select($mailbox)) { - 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 $this->data['UID-MAP'][$id] = (int) $arr[0]; - } - } - - /** - * Sets flag of the message(s) - * - * @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 - */ - 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 - * - * @return bool True on success, False on failure - */ - public function unflag($mailbox, $messages, $flag) - { - return $this->modFlag($mailbox, $messages, $flag, '-'); - } - /** * Changes flag of the message(s) * @@ -2277,1427 +1205,6 @@ class imap_rcube 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 - * - * @return bool True on success, False on failure - */ - public function copy($messages, $from, $to) - { - // Clear last COPYUID data - unset($this->data['COPYUID']); - - if (!$this->select($from)) { - return false; - } - - // Clear internal status cache - unset($this->data['STATUS:'.$to]); - - $result = $this->execute('UID COPY', array( - $this->compressMessageSet($messages), $this->escape($to)), - self::COMMAND_NORESPONSE); - - 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 - * - * @return bool True on success, False on failure - */ - 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"); - return false; - } - - // use MOVE command (RFC 6851) - if ($this->hasCapability('MOVE')) { - // Clear last COPYUID data - unset($this->data['COPYUID']); - - // Clear internal status cache - unset($this->data['STATUS:'.$to]); - $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; - } - - // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE - $result = $this->copy($messages, $from, $to); - - if ($result) { - // Clear internal status cache - unset($this->data['STATUS:'.$from]); - - $result = $this->flag($from, $messages, 'DELETED'); - - if ($messages == '*') { - // CLOSE+SELECT should be faster than EXPUNGE - $this->close(); - } - else { - $this->expunge($from, $messages); - } - } - - return $result; - } - - /** - * FETCH command (RFC3501) - * - * @param string $mailbox Mailbox name - * @param mixed $message_set Message(s) sequence identifier(s) or UID(s) - * @param bool $is_uid True if $message_set contains UIDs - * @param array $query_items FETCH command data items - * @param string $mod_seq Modification sequence for CHANGEDSINCE (RFC4551) query - * @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query - * - * @return array List of rcube_message_header elements, False on error - * @since 0.6 - */ - public function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(), - $mod_seq = null, $vanished = false) - { - if (!$this->select($mailbox)) { - return false; - } - - $message_set = $this->compressMessageSet($message_set); - $result = array(); - - $key = $this->nextTag(); - $cmd = ($is_uid ? 'UID ' : '') . 'FETCH'; - $request = "$key $cmd $message_set (" . implode(' ', $query_items) . ")"; - - if ($mod_seq !== null && $this->hasCapability('CONDSTORE')) { - $request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? " VANISHED" : '') .")"; - } - - if (!$this->putLine($request)) { - $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command"); - return false; - } - - do { - $line = $this->readLine(4096); - - if (!$line) { - break; - } - - // Sample reply line: - // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) - // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) - // BODY[HEADER.FIELDS ... - - if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { - $id = intval($m[1]); - - $result[$id] = new rcube_message_header; - $result[$id]->id = $id; - $result[$id]->subject = ''; - $result[$id]->messageID = 'mid:' . $id; - - $headers = null; - $lines = array(); - $line = substr($line, strlen($m[0]) + 2); - $ln = 0; - - // get complete entry - while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) { - $bytes = $m[1]; - $out = ''; - - while (strlen($out) < $bytes) { - $out = $this->readBytes($bytes); - if ($out === null) { - break; - } - $line .= $out; - } - - $str = $this->readLine(4096); - if ($str === false) { - break; - } - - $line .= $str; - } - - // Tokenize response and assign to object properties - while (list($name, $value) = $this->tokenizeResponse($line, 2)) { - if ($name == 'UID') { - $result[$id]->uid = intval($value); - } - else if ($name == 'RFC822.SIZE') { - $result[$id]->size = intval($value); - } - else if ($name == 'RFC822.TEXT') { - $result[$id]->body = $value; - } - else if ($name == 'INTERNALDATE') { - $result[$id]->internaldate = $value; - $result[$id]->date = $value; - $result[$id]->timestamp = rcube_utils::strtotime($value); - } - else if ($name == 'FLAGS') { - if (!empty($value)) { - foreach ((array)$value as $flag) { - $flag = str_replace(array('$', "\\"), '', $flag); - $flag = strtoupper($flag); - - $result[$id]->flags[$flag] = true; - } - } - } - else if ($name == 'MODSEQ') { - $result[$id]->modseq = $value[0]; - } - else if ($name == 'ENVELOPE') { - $result[$id]->envelope = $value; - } - else if ($name == 'BODYSTRUCTURE' || ($name == 'BODY' && count($value) > 2)) { - if (!is_array($value[0]) && (strtolower($value[0]) == 'message' && strtolower($value[1]) == 'rfc822')) { - $value = array($value); - } - $result[$id]->bodystructure = $value; - } - else if ($name == 'RFC822') { - $result[$id]->body = $value; - } - 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 $resln) { - if (ord($resln[0]) <= 32) { - $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln); - } - else { - $lines[++$ln] = trim($resln); - } - } - - foreach ($lines as $str) { - list($field, $string) = explode(':', $str, 2); - - $field = strtolower($field); - $string = preg_replace('/\n[\t\s]*/', ' ', trim($string)); - - switch ($field) { - case 'date'; - $string = substr($string, 0, 128); - $result[$id]->date = $string; - $result[$id]->timestamp = rcube_utils::strtotime($string); - break; - case 'to': - $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); - break; - case 'from': - case 'subject': - $string = substr($string, 0, 2048); - case 'cc': - case 'bcc': - case 'references': - $result[$id]->{$field} = $string; - break; - case 'reply-to': - $result[$id]->replyto = $string; - break; - case 'content-transfer-encoding': - $result[$id]->encoding = substr($string, 0, 32); - break; - case 'content-type': - $ctype_parts = preg_split('/[; ]+/', $string); - $result[$id]->ctype = strtolower(array_shift($ctype_parts)); - if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) { - $result[$id]->charset = $regs[1]; - } - break; - case 'in-reply-to': - $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); - break; - case 'return-receipt-to': - case 'disposition-notification-to': - case 'x-confirm-reading-to': - $result[$id]->mdn_to = substr($string, 0, 2048); - break; - case 'message-id': - $result[$id]->messageID = substr($string, 0, 2048); - break; - case 'x-priority': - if (preg_match('/^(\d+)/', $string, $matches)) { - $result[$id]->priority = intval($matches[1]); - } - break; - default: - if (strlen($field) < 3) { - break; - } - if ($result[$id]->others[$field]) { - $string = array_merge((array)$result[$id]->others[$field], (array)$string); - } - $result[$id]->others[$field] = $string; - } - } - } - } - // VANISHED response (QRESYNC RFC5162) - // Sample: * VANISHED (EARLIER) 300:310,405,411 - else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) { - $line = substr($line, strlen($match[0])); - $v_data = $this->tokenizeResponse($line, 1); - - $this->data['VANISHED'] = $v_data; - } - } - while (!$this->startsWith($line, $key, true)); - - return $result; - } - - /** - * Returns message(s) data (flags, headers, etc.) - * - * @param string $mailbox Mailbox name - * @param mixed $message_set Message(s) sequence identifier(s) or UID(s) - * @param bool $is_uid True if $message_set contains UIDs - * @param bool $bodystr Enable to add BODYSTRUCTURE data to the result - * @param array $add_headers List of additional headers - * - * @return bool|array List of rcube_message_header elements, False on error - */ - 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', - 'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY'); - - if (!empty($add_headers)) { - $add_headers = array_map('strtoupper', $add_headers); - $headers = array_unique(array_merge($headers, $add_headers)); - } - - if ($bodystr) { - $query_items[] = 'BODYSTRUCTURE'; - } - - $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]'; - - return $this->fetch($mailbox, $message_set, $is_uid, $query_items); - } - - /** - * Returns message data (flags, headers, etc.) - * - * @param string $mailbox Mailbox name - * @param int $id Message sequence identifier or UID - * @param bool $is_uid True if $id is an UID - * @param bool $bodystr Enable to add BODYSTRUCTURE data to the result - * @param array $add_headers List of additional headers - * - * @return bool|rcube_message_header Message data, False on error - */ - 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; - } - - /** - * 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) - { - // Strategy: First, we'll create an "index" array. - // Then, we'll use sort() on that array, and use that to sort the main array. - - $field = empty($field) ? 'uid' : strtolower($field); - $flag = empty($flag) ? 'ASC' : strtoupper($flag); - $index = array(); - $result = array(); - - reset($messages); - - foreach ($messages as $key => $headers) { - $value = null; - - switch ($field) { - case 'arrival': - $field = 'internaldate'; - case 'date': - case 'internaldate': - case 'timestamp': - $value = rcube_utils::strtotime($headers->$field); - if (!$value && $field != 'timestamp') { - $value = $headers->timestamp; - } - - 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 { - arsort($index); - } - - // form new array based on index - foreach ($index as $key => $val) { - $result[$key] = $messages[$key]; - } - } - - return $result; - } - - /** - * 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; - } - - $result = false; - $parts = (array) $parts; - $key = $this->nextTag(); - $peeks = array(); - $type = $mime ? 'MIME' : 'HEADER'; - - // format request - foreach ($parts as $part) { - $peeks[] = "BODY.PEEK[$part.$type]"; - } - - $request = "$key UID FETCH $uid (" . implode(' ', $peeks) . ')'; - - // send request - if (!$this->putLine($request)) { - $this->setError(self::ERROR_COMMAND, "Failed to send UID FETCH command"); - return false; - } - - do { - $line = $this->readLine(1024); - - 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); - } - } - } - while (!$this->startsWith($line, $key, true)); - - return $result; - } - - /** - * 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); - } - - /** - * 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; - } - - $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(); - $cmd = ($is_uid ? 'UID ' : '') . 'FETCH'; - $request = "$key $cmd $id ($fetch_mode.PEEK[$part]$partial)"; - $result = false; - $found = false; - $initiated = true; - - // send request - if (!$this->putLine($request)) { - $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command"); - 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|PARSE)\]/i', $line)) { - $binary = $initiated = false; - continue; - } - - // skip irrelevant untagged responses (we have a result already) - if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { - continue; - } - - $line = $m[2]; - - // 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 0) { - $line = $this->readLine(8192); - - if ($line === null) { - break; - } - - $len = strlen($line); - - if ($len > $bytes) { - $line = substr($line, 0, $bytes); - $len = strlen($line); - } - $bytes -= $len; - - // BASE64 - if ($mode == 1) { - $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line); - // create chunks with proper length for base64 decoding - $line = $prev.$line; - $length = strlen($line); - if ($length % 4) { - $length = floor($length / 4) * 4; - $prev = substr($line, $length); - $line = substr($line, 0, $length); - } - else { - $prev = ''; - } - $line = base64_decode($line); - } - // QUOTED-PRINTABLE - else if ($mode == 2) { - $line = rtrim($line, "\t\r\0\x0B"); - $line = quoted_printable_decode($line); - } - // UUENCODE - else if ($mode == 3) { - $line = rtrim($line, "\t\r\n\0\x0B"); - if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) { - continue; - } - $line = convert_uudecode($line); - } - // default - else if ($formatted) { - $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; - } - - if ($file) { - if (fwrite($file, $line) === false) { - break; - } - } - else if ($print) { - echo $line; - } - else { - $result .= $line; - } - } - } - } - while (!$this->startsWith($line, $key, true) || !$initiated); - - if ($result !== false) { - if ($file) { - return fwrite($file, $result); - } - else if ($print) { - echo $result; - return true; - } - - return $result; - } - - return false; - } - - /** - * Handler for IMAP APPEND command - * - * @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 - */ - public function append($mailbox, &$message, $flags = array(), $date = null, $binary = false) - { - unset($this->data['APPENDUID']); - - if ($mailbox === null || $mailbox === '') { - return false; - } - - $binary = $binary && $this->getCapability('BINARY'); - $literal_plus = !$binary && $this->prefs['literal+']; - $len = 0; - $msg = is_array($message) ? $message : array(&$message); - $chunk_size = 512000; - - 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]); - } - } - - if (!$len) { - return false; - } - - // 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)) { - $this->setError(self::ERROR_COMMAND, "Failed to send APPEND command"); - return false; - } - - // Do not wait when LITERAL+ is supported - if (!$literal_plus) { - $line = $this->readReply(); - - if ($line[0] != '+') { - $this->parseResult($line, 'APPEND: '); - return false; - } - } - - 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; - } - - 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; - } - - if (!empty($this->data['APPENDUID'])) { - return $this->data['APPENDUID']; - } - - return true; - } - - /** - * Handler for IMAP APPEND command. - * - * @param string $mailbox Mailbox name - * @param string $path Path to the file with message body - * @param string $headers Message headers - * @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 - */ - public function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false) - { - // open message file - if (file_exists(realpath($path))) { - $fp = fopen($path, 'r'); - } - - if (!$fp) { - $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading"); - return false; - } - - $message = array(); - if ($headers) { - $message[] = trim($headers, "\r\n") . "\r\n\r\n"; - } - $message[] = $fp; - - return $this->append($mailbox, $message, $flags, $date, $binary); - } - - /** - * Returns QUOTA information - * - * @param string $mailbox Mailbox name - * - * @return array Quota information - */ - public function getQuota($mailbox = null) - { - if ($mailbox === null || $mailbox === '') { - $mailbox = 'INBOX'; - } - - // 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)), 0, '/^\* QUOTA /i'); - - $result = false; - $min_free = PHP_INT_MAX; - $all = array(); - - if ($code == self::ERROR_OK) { - foreach (explode("\n", $response) as $line) { - list(, , $quota_root) = $this->tokenizeResponse($line, 3); - - $quotas = $this->tokenizeResponse($line, 1); - - if (empty($quotas)) { - continue; - } - - foreach (array_chunk($quotas, 3) as $quota) { - list($type, $used, $total) = $quota; - $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']; - } - } - } - - if (!empty($result)) { - $result['all'] = $all; - } - - return $result; - } - - /** - * Send the SETACL command (RFC4314) - * - * @param string $mailbox Mailbox name - * @param string $user User name - * @param mixed $acl ACL string or array - * - * @return boolean True on success, False on failure - * - * @since 0.5-beta - */ - public function setACL($mailbox, $user, $acl) - { - if (is_array($acl)) { - $acl = implode('', $acl); - } - - $result = $this->execute('SETACL', array( - $this->escape($mailbox), $this->escape($user), strtolower($acl)), - self::COMMAND_NORESPONSE); - - return ($result == self::ERROR_OK); - } - - /** - * Send the DELETEACL command (RFC4314) - * - * @param string $mailbox Mailbox name - * @param string $user User name - * - * @return boolean True on success, False on failure - * - * @since 0.5-beta - */ - public function deleteACL($mailbox, $user) - { - $result = $this->execute('DELETEACL', array( - $this->escape($mailbox), $this->escape($user)), - self::COMMAND_NORESPONSE); - - return ($result == self::ERROR_OK); - } - - /** - * Send the GETACL command (RFC4314) - * - * @param string $mailbox Mailbox name - * - * @return array User-rights array on success, NULL on error - * @since 0.5-beta - */ - public function getACL($mailbox) - { - list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox)), 0, '/^\* ACL /i'); - - if ($code == self::ERROR_OK && $response) { - // Parse server response (remove "* ACL ") - $response = substr($response, 6); - $ret = $this->tokenizeResponse($response); - $mbox = array_shift($ret); - $size = count($ret); - - // Create user-rights hash array - // @TODO: consider implementing fixACL() method according to RFC4314.2.1.1 - // so we could return only standard rights defined in RFC4314, - // excluding 'c' and 'd' defined in RFC2086. - if ($size % 2 == 0) { - for ($i=0; $i<$size; $i++) { - $ret[$ret[$i]] = str_split($ret[++$i]); - unset($ret[$i-1]); - unset($ret[$i]); - } - return $ret; - } - - $this->setError(self::ERROR_COMMAND, "Incomplete ACL response"); - } - } - - /** - * Send the LISTRIGHTS command (RFC4314) - * - * @param string $mailbox Mailbox name - * @param string $user User name - * - * @return array List of user rights - * @since 0.5-beta - */ - public function listRights($mailbox, $user) - { - list($code, $response) = $this->execute('LISTRIGHTS', - array($this->escape($mailbox), $this->escape($user)), 0, '/^\* LISTRIGHTS /i'); - - if ($code == self::ERROR_OK && $response) { - // Parse server response (remove "* LISTRIGHTS ") - $response = substr($response, 13); - - $ret_mbox = $this->tokenizeResponse($response, 1); - $ret_user = $this->tokenizeResponse($response, 1); - $granted = $this->tokenizeResponse($response, 1); - $optional = trim($response); - - return array( - 'granted' => str_split($granted), - 'optional' => explode(' ', $optional), - ); - } - } - - /** - * Send the MYRIGHTS command (RFC4314) - * - * @param string $mailbox Mailbox name - * - * @return array MYRIGHTS response on success, NULL on error - * @since 0.5-beta - */ - public function myRights($mailbox) - { - list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox)), 0, '/^\* MYRIGHTS /i'); - - if ($code == self::ERROR_OK && $response) { - // Parse server response (remove "* MYRIGHTS ") - $response = substr($response, 11); - - $ret_mbox = $this->tokenizeResponse($response, 1); - $rights = $this->tokenizeResponse($response, 1); - - return str_split($rights); - } - } - - /** - * Send the SETMETADATA command (RFC5464) - * - * @param string $mailbox Mailbox name - * @param array $entries Entry-value array (use NULL value as NIL) - * - * @return boolean True on success, False on failure - * @since 0.5-beta - */ - public function setMetadata($mailbox, $entries) - { - if (!is_array($entries) || empty($entries)) { - $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command"); - return false; - } - - foreach ($entries as $name => $value) { - $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true); - } - - $entries = implode(' ', $entries); - $result = $this->execute('SETMETADATA', array( - $this->escape($mailbox), '(' . $entries . ')'), - self::COMMAND_NORESPONSE); - - return ($result == self::ERROR_OK); - } - - /** - * Send the SETMETADATA command with NIL values (RFC5464) - * - * @param string $mailbox Mailbox name - * @param array $entries Entry names array - * - * @return boolean True on success, False on failure - * - * @since 0.5-beta - */ - public function deleteMetadata($mailbox, $entries) - { - if (!is_array($entries) && !empty($entries)) { - $entries = explode(' ', $entries); - } - - if (empty($entries)) { - $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command"); - return false; - } - - foreach ($entries as $entry) { - $data[$entry] = null; - } - - return $this->setMetadata($mailbox, $data); - } - - /** - * Send the GETMETADATA command (RFC5464) - * - * @param string $mailbox Mailbox name - * @param array $entries Entries - * @param array $options Command options (with MAXSIZE and DEPTH keys) - * - * @return array GETMETADATA result on success, NULL on error - * - * @since 0.5-beta - */ - public function getMetadata($mailbox, $entries, $options=array()) - { - if (!is_array($entries)) { - $entries = array($entries); - } - - // create entries string - foreach ($entries as $idx => $name) { - $entries[$idx] = $this->escape($name); - } - - $optlist = ''; - $entlist = '(' . implode(' ', $entries) . ')'; - - // create options string - if (is_array($options)) { - $options = array_change_key_case($options, CASE_UPPER); - $opts = array(); - - if (!empty($options['MAXSIZE'])) { - $opts[] = 'MAXSIZE '.intval($options['MAXSIZE']); - } - if (!empty($options['DEPTH'])) { - $opts[] = 'DEPTH '.intval($options['DEPTH']); - } - - if ($opts) { - $optlist = '(' . implode(' ', $opts) . ')'; - } - } - - $optlist .= ($optlist ? ' ' : '') . $entlist; - - list($code, $response) = $this->execute('GETMETADATA', array( - $this->escape($mailbox), $optlist)); - - if ($code == self::ERROR_OK) { - $result = array(); - $data = $this->tokenizeResponse($response); - - // The METADATA response can contain multiple entries in a single - // response or multiple responses for each entry or group of entries - for ($i = 0, $size = count($data); $i < $size; $i++) { - if ($data[$i] === '*' - && $data[++$i] === 'METADATA' - && is_string($mbox = $data[++$i]) - && is_array($data[++$i]) - ) { - for ($x = 0, $size2 = count($data[$i]); $x < $size2; $x += 2) { - if ($data[$i][$x+1] !== null) { - $result[$mbox][$data[$i][$x]] = $data[$i][$x+1]; - } - } - } - } - - return $result; - } - } - - /** - * Send the SETANNOTATION command (draft-daboo-imap-annotatemore) - * - * @param string $mailbox Mailbox name - * @param array $data Data array where each item is an array with - * three elements: entry name, attribute name, value - * - * @return boolean True on success, False on failure - * @since 0.5-beta - */ - public function setAnnotation($mailbox, $data) - { - if (!is_array($data) || empty($data)) { - $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command"); - return false; - } - - foreach ($data as $entry) { - // 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)); - } - - $entries = implode(' ', $entries); - $result = $this->execute('SETANNOTATION', array( - $this->escape($mailbox), $entries), self::COMMAND_NORESPONSE); - - return ($result == self::ERROR_OK); - } - - /** - * Send the SETANNOTATION command with NIL values (draft-daboo-imap-annotatemore) - * - * @param string $mailbox Mailbox name - * @param array $data Data array where each item is an array with - * two elements: entry name and attribute name - * - * @return boolean True on success, False on failure - * - * @since 0.5-beta - */ - public function deleteAnnotation($mailbox, $data) - { - if (!is_array($data) || empty($data)) { - $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command"); - return false; - } - - return $this->setAnnotation($mailbox, $data); - } - - /** - * Send the GETANNOTATION command (draft-daboo-imap-annotatemore) - * - * @param string $mailbox Mailbox name - * @param array $entries Entries names - * @param array $attribs Attribs names - * - * @return array Annotations result on success, NULL on error - * - * @since 0.5-beta - */ - public function getAnnotation($mailbox, $entries, $attribs) - { - if (!is_array($entries)) { - $entries = array($entries); - } - - // create entries string - // ANNOTATEMORE drafts before version 08 require quoted parameters - foreach ($entries as $idx => $name) { - $entries[$idx] = $this->escape($name, true); - } - $entries = '(' . implode(' ', $entries) . ')'; - - if (!is_array($attribs)) { - $attribs = array($attribs); - } - - // create attributes string - foreach ($attribs as $idx => $name) { - $attribs[$idx] = $this->escape($name, true); - } - $attribs = '(' . implode(' ', $attribs) . ')'; - - list($code, $response) = $this->execute('GETANNOTATION', array( - $this->escape($mailbox), $entries, $attribs)); - - if ($code == self::ERROR_OK) { - $result = array(); - $data = $this->tokenizeResponse($response); - - // Here we returns only data compatible with METADATA result format - if (!empty($data) && ($size = count($data))) { - for ($i=0; $i<$size; $i++) { - $entry = $data[$i]; - if (isset($mbox) && is_array($entry)) { - $attribs = $entry; - $entry = $last_entry; - } - else if ($entry == '*') { - if ($data[$i+1] == 'ANNOTATION') { - $mbox = $data[$i+2]; - unset($data[$i]); // "*" - unset($data[++$i]); // "ANNOTATION" - unset($data[++$i]); // Mailbox - } - // get rid of other untagged responses - else { - unset($mbox); - unset($data[$i]); - } - continue; - } - else if (isset($mbox)) { - $attribs = $data[++$i]; - } - else { - unset($data[$i]); - continue; - } - - if (!empty($attribs)) { - for ($x=0, $len=count($attribs); $x<$len;) { - $attr = $attribs[$x++]; - $value = $attribs[$x++]; - if ($attr == 'value.priv' && $value !== null) { - $result[$mbox]['/private' . $entry] = $value; - } - else if ($attr == 'value.shared' && $value !== null) { - $result[$mbox]['/shared' . $entry] = $value; - } - } - } - $last_entry = $entry; - unset($data[$i]); - } - } - - return $result; - } - } - - /** - * Returns BODYSTRUCTURE for the specified message. - * - * @param string $mailbox Folder name - * @param int $id Message sequence number or UID - * @param bool $is_uid True if $id is an UID - * - * @return array/bool Body structure array or False on error. - * @since 0.6 - */ - 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; - } - - /** - * Returns data of a message part according to specified structure. - * - * @param array $structure Message structure (getStructure() result) - * @param string $part Message part identifier - * - * @return array Part data as hash array (type, encoding, charset, size) - */ - public static function getStructurePartData($structure, $part) - { - $part_a = self::getStructurePartArray($structure, $part); - $data = array(); - - if (empty($part_a)) { - return $data; - } - - // content-type - if (is_array($part_a[0])) { - $data['type'] = 'multipart'; - } - else { - $data['type'] = strtolower($part_a[0]); - $data['encoding'] = strtolower($part_a[5]); - - // charset - if (is_array($part_a[2])) { - foreach ($part_a[2] as $key => $val) { - if (strcasecmp($val, 'charset') == 0) { - $data['charset'] = $part_a[2][$key+1]; - break; - } - } - } - } - - // size - $data['size'] = intval($part_a[6]); - - return $data; - } - - public static function getStructurePartArray($a, $part) - { - if (!is_array($a)) { - return false; - } - - if (empty($part)) { - return $a; - } - - $ctype = is_string($a[0]) && is_string($a[1]) ? $a[0] . '/' . $a[1] : ''; - - if (strcasecmp($ctype, 'message/rfc822') == 0) { - $a = $a[8]; - } - - if (strpos($part, '.') > 0) { - $orig_part = $part; - $pos = strpos($part, '.'); - $rest = substr($orig_part, $pos+1); - $part = substr($orig_part, 0, $pos); - - return self::getStructurePartArray($a[$part-1], $rest); - } - else if ($part > 0) { - return (is_array($a[$part-1])) ? $a[$part-1] : $a; - } - } - - /** - * Creates next command identifier (tag) - * - * @return string Command identifier - * @since 0.5-beta - */ - public function nextTag() - { - $this->cmd_num++; - $this->cmd_tag = sprintf('A%04d', $this->cmd_num); - - return $this->cmd_tag; - } - /** * Sends IMAP command and parses result * @@ -4071,20 +1578,6 @@ class imap_rcube return sprintf("{%d}\r\n%s", strlen($string), $string); } - /** - * Set the value of the debugging flag. - * - * @param boolean $debug New value for the debugging flag. - * @param callback $handler Logging handler function - * - * @since 0.5-stable - */ - public function setDebug($debug, $handler = null) - { - $this->debug = $debug; - $this->debug_handler = $handler; - } - /** * Write the given debug text to the current debug output handler. *