| | |
| | | | program/include/rcube_imap.php | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland | |
| | | | Copyright (C) 2005-2010, The Roundcube Dev Team | |
| | | | Licensed under the GNU GPL | |
| | | | | |
| | | | PURPOSE: | |
| | |
| | | 'RETURN-PATH', |
| | | ); |
| | | |
| | | const UNKNOWN = 0; |
| | | const NOPERM = 1; |
| | | const READONLY = 2; |
| | | const TRYCREATE = 3; |
| | | const INUSE = 4; |
| | | const OVERQUOTA = 5; |
| | | const ALREADYEXISTS = 6; |
| | | const NONEXISTENT = 7; |
| | | const CONTACTADMIN = 8; |
| | | |
| | | |
| | | /** |
| | | * Object constructor |
| | |
| | | |
| | | $this->options['port'] = $port; |
| | | |
| | | if ($this->options['debug']) { |
| | | $this->conn->setDebug(true, array($this, 'debug_handler')); |
| | | |
| | | $this->options['ident'] = array( |
| | | 'name' => 'Roundcube Webmail', |
| | | 'version' => RCMAIL_VERSION, |
| | | 'php' => PHP_VERSION, |
| | | 'os' => PHP_OS, |
| | | 'command' => $_SERVER['REQUEST_URI'], |
| | | ); |
| | | } |
| | | |
| | | $attempt = 0; |
| | | do { |
| | | $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', |
| | |
| | | $this->ssl = $use_ssl; |
| | | |
| | | if ($this->conn->connected()) { |
| | | // print trace messages |
| | | if ($this->conn->message && ($this->debug_level & 8)) { |
| | | console($this->conn->message); |
| | | } |
| | | // get namespace and delimiter |
| | | $this->set_env(); |
| | | |
| | | return true; |
| | | } |
| | | // write error log |
| | | else if ($this->conn->error) { |
| | | if ($pass && $user) |
| | | if ($pass && $user) { |
| | | $message = sprintf("Login failed for %s from %s. %s", |
| | | $user, rcmail_remote_ip(), $this->conn->error); |
| | | |
| | | raise_error(array('code' => 403, 'type' => 'imap', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => $this->conn->error), true, false); |
| | | 'message' => $message), true, false); |
| | | } |
| | | } |
| | | |
| | | return false; |
| | |
| | | */ |
| | | function close() |
| | | { |
| | | $this->conn->close(); |
| | | $this->conn->closeConnection(); |
| | | $this->write_cache(); |
| | | } |
| | | |
| | |
| | | */ |
| | | function reconnect() |
| | | { |
| | | $this->close(); |
| | | $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); |
| | | $this->conn->closeConnection(); |
| | | $connected = $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); |
| | | |
| | | // issue SELECT command to restore connection status |
| | | if ($this->mailbox) |
| | | if ($connected && strlen($this->mailbox)) |
| | | $this->conn->select($this->mailbox); |
| | | } |
| | | |
| | |
| | | */ |
| | | function get_error_code() |
| | | { |
| | | return ($this->conn) ? $this->conn->errornum : 0; |
| | | return $this->conn->errornum; |
| | | } |
| | | |
| | | |
| | |
| | | */ |
| | | function get_error_str() |
| | | { |
| | | return ($this->conn) ? $this->conn->error : ''; |
| | | return $this->conn->error; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns code of last command response |
| | | * |
| | | * @return int Response code |
| | | */ |
| | | function get_response_code() |
| | | { |
| | | switch ($this->conn->resultcode) { |
| | | case 'NOPERM': |
| | | return self::NOPERM; |
| | | case 'READ-ONLY': |
| | | return self::READONLY; |
| | | case 'TRYCREATE': |
| | | return self::TRYCREATE; |
| | | case 'INUSE': |
| | | return self::INUSE; |
| | | case 'OVERQUOTA': |
| | | return self::OVERQUOTA; |
| | | case 'ALREADYEXISTS': |
| | | return self::ALREADYEXISTS; |
| | | case 'NONEXISTENT': |
| | | return self::NONEXISTENT; |
| | | case 'CONTACTADMIN': |
| | | return self::CONTACTADMIN; |
| | | default: |
| | | return self::UNKNOWN; |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns last command response |
| | | * |
| | | * @return string Response |
| | | */ |
| | | function get_response_str() |
| | | { |
| | | return $this->conn->result; |
| | | } |
| | | |
| | | |
| | |
| | | * @param string $mailbox Mailbox/Folder name |
| | | * @access public |
| | | */ |
| | | function select_mailbox($mailbox) |
| | | function select_mailbox($mailbox=null) |
| | | { |
| | | $mailbox = $this->mod_mailbox($mailbox); |
| | | $mailbox = strlen($mailbox) ? $this->mod_mailbox($mailbox) : $this->mailbox; |
| | | |
| | | $selected = $this->conn->select($mailbox); |
| | | |
| | |
| | | { |
| | | $this->threading = false; |
| | | |
| | | if ($enable) { |
| | | if ($this->get_capability('THREAD=REFS')) |
| | | if ($enable && ($caps = $this->get_capability('THREAD'))) { |
| | | if (in_array('REFS', $caps)) |
| | | $this->threading = 'REFS'; |
| | | else if ($this->get_capability('THREAD=REFERENCES')) |
| | | else if (in_array('REFERENCES', $caps)) |
| | | $this->threading = 'REFERENCES'; |
| | | else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) |
| | | else if (in_array('ORDEREDSUBJECT', $caps)) |
| | | $this->threading = 'ORDEREDSUBJECT'; |
| | | } |
| | | |
| | |
| | | $imap_shared = $config->get('imap_ns_shared'); |
| | | $imap_delimiter = $config->get('imap_delimiter'); |
| | | |
| | | if ($imap_delimiter) { |
| | | $this->delimiter = $imap_delimiter; |
| | | } |
| | | |
| | | if (!$this->conn) |
| | | if (!$this->conn->connected()) |
| | | return; |
| | | |
| | | $ns = $this->conn->getNamespace(); |
| | | |
| | | // NAMESPACE supported |
| | | // Set namespaces (NAMESPACE supported) |
| | | if (is_array($ns)) { |
| | | $this->namespace = $ns; |
| | | |
| | | if (empty($this->delimiter)) |
| | | $this->delimiter = $ns['personal'][0][1]; |
| | | if (empty($this->delimiter)) |
| | | $this->delimiter = $this->conn->getHierarchyDelimiter(); |
| | | if (empty($this->delimiter)) |
| | | $this->delimiter = '/'; |
| | | } |
| | | // not supported, get namespace from config |
| | | else if ($imap_personal !== null || $imap_shared !== null || $imap_other !== null) { |
| | | if (empty($this->delimiter)) |
| | | $this->delimiter = $this->conn->getHierarchyDelimiter(); |
| | | if (empty($this->delimiter)) |
| | | $this->delimiter = '/'; |
| | | |
| | | else { |
| | | $this->namespace = array( |
| | | 'personal' => NULL, |
| | | 'other' => NULL, |
| | | 'shared' => NULL, |
| | | ); |
| | | } |
| | | |
| | | if ($imap_personal !== null) { |
| | | foreach ((array)$imap_personal as $dir) { |
| | | $this->namespace['personal'][] = array($dir, $this->delimiter); |
| | | if ($imap_delimiter) { |
| | | $this->delimiter = $imap_delimiter; |
| | | } |
| | | if (empty($this->delimiter)) { |
| | | $this->delimiter = $this->namespace['personal'][0][1]; |
| | | } |
| | | if (empty($this->delimiter)) { |
| | | $this->delimiter = $this->conn->getHierarchyDelimiter(); |
| | | } |
| | | if (empty($this->delimiter)) { |
| | | $this->delimiter = '/'; |
| | | } |
| | | |
| | | // Overwrite namespaces |
| | | if ($imap_personal !== null) { |
| | | $this->namespace['personal'] = NULL; |
| | | foreach ((array)$imap_personal as $dir) { |
| | | $this->namespace['personal'][] = array($dir, $this->delimiter); |
| | | } |
| | | } |
| | | if ($imap_other !== null) { |
| | | $this->namespace['other'] = NULL; |
| | | foreach ((array)$imap_other as $dir) { |
| | | if ($dir) { |
| | | $this->namespace['other'][] = array($dir, $this->delimiter); |
| | | } |
| | | } |
| | | if ($imap_other !== null) { |
| | | foreach ((array)$imap_other as $dir) { |
| | | if ($dir) { |
| | | $this->namespace['other'][] = array($dir, $this->delimiter); |
| | | } |
| | | } |
| | | } |
| | | if ($imap_shared !== null) { |
| | | foreach ((array)$imap_shared as $dir) { |
| | | if ($dir) { |
| | | $this->namespace['shared'][] = array($dir, $this->delimiter); |
| | | } |
| | | } |
| | | if ($imap_shared !== null) { |
| | | $this->namespace['shared'] = NULL; |
| | | foreach ((array)$imap_shared as $dir) { |
| | | if ($dir) { |
| | | $this->namespace['shared'][] = array($dir, $this->delimiter); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | // RECENT count is fetched a bit different |
| | | else if ($mode == 'RECENT') { |
| | | $count = $this->conn->checkForRecent($mailbox); |
| | | $count = $this->conn->countRecent($mailbox); |
| | | } |
| | | // use SEARCH for message counting |
| | | else if ($this->skip_deleted) { |
| | |
| | | return false; |
| | | } |
| | | |
| | | $struct = &$this->_structure_part($structure); |
| | | $struct = &$this->_structure_part($structure, 0, '', $headers); |
| | | $struct->headers = get_object_vars($headers); |
| | | |
| | | // don't trust given content-type |
| | |
| | | $struct->charset = $struct->ctype_parameters['charset']; |
| | | } |
| | | |
| | | // #1487700: workaround for lack of charset in malformed structure |
| | | if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) { |
| | | $struct->charset = $mime_headers->charset; |
| | | } |
| | | |
| | | // read content encoding |
| | | if (!empty($part[5]) && $part[5]!='NIL') { |
| | | $struct->encoding = strtolower($part[5]); |
| | |
| | | $mime_headers = $this->conn->fetchPartHeader( |
| | | $this->mailbox, $this->_msg_id, false, $struct->mime_id); |
| | | } |
| | | $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; |
| | | |
| | | if (is_string($mime_headers)) |
| | | $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; |
| | | else if (is_object($mime_headers)) |
| | | $struct->headers = get_object_vars($mime_headers) + $struct->headers; |
| | | |
| | | // get real content-type of message/rfc822 |
| | | if ($struct->mimetype == 'message/rfc822') { |
| | |
| | | |
| | | // TODO: Add caching for message parts |
| | | |
| | | if (!$part) $part = 'TEXT'; |
| | | if (!$part) { |
| | | $part = 'TEXT'; |
| | | } |
| | | |
| | | $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part, |
| | | $o_part->encoding, $print, $fp); |
| | | |
| | | if ($fp || $print) |
| | | if ($fp || $print) { |
| | | return true; |
| | | } |
| | | |
| | | // convert charset (if text or message part) |
| | | if ($body && ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message')) { |
| | | // assume default if no charset specified |
| | | if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') |
| | | $o_part->charset = $this->default_charset; |
| | | |
| | | // convert charset (if text or message part) and part's charset is specified |
| | | if ($body && $o_part->charset |
| | | && preg_match('/^(text|message)$/', $o_part->ctype_primary) |
| | | ) { |
| | | $body = rcube_charset_convert($body, $o_part->charset); |
| | | } |
| | | |
| | |
| | | else |
| | | $a_uids = NULL; |
| | | |
| | | $result = $this->conn->expunge($mailbox, $a_uids); |
| | | // force mailbox selection and check if mailbox is writeable |
| | | // to prevent a situation when CLOSE is executed on closed |
| | | // or EXPUNGE on read-only mailbox |
| | | $result = $this->conn->select($mailbox); |
| | | if (!$result) { |
| | | return false; |
| | | } |
| | | if (!$this->conn->data['READ-WRITE']) { |
| | | $this->conn->setError(rcube_imap_generic::ERROR_READONLY, "Mailbox is read-only"); |
| | | return false; |
| | | } |
| | | |
| | | // CLOSE(+SELECT) should be faster than EXPUNGE |
| | | if (empty($a_uids) || $a_uids == '1:*') |
| | | $result = $this->conn->close(); |
| | | else |
| | | $result = $this->conn->expunge($mailbox, $a_uids); |
| | | |
| | | if ($result && $clear_cache) { |
| | | $this->clear_message_cache($mailbox.'.msg'); |
| | |
| | | /** |
| | | * Get mailbox size (size of all messages in a mailbox) |
| | | * |
| | | * @param string $name Mailbox name |
| | | * @param string $name Mailbox name |
| | | * @return int Mailbox size in bytes, False on error |
| | | */ |
| | | function get_mailbox_size($name) |
| | |
| | | * |
| | | * @param string $mbox_name Mailbox to rename |
| | | * @param string $new_name New mailbox name |
| | | * |
| | | * @return boolean True on success |
| | | */ |
| | | function rename_mailbox($mbox_name, $new_name) |
| | |
| | | // make absolute path |
| | | $mailbox = $this->mod_mailbox($mbox_name); |
| | | $abs_name = $this->mod_mailbox($new_name); |
| | | $delm = $this->get_hierarchy_delimiter(); |
| | | |
| | | // check if mailbox is subscribed |
| | | $a_subscribed = $this->_list_mailboxes(); |
| | | $subscribed = in_array($mailbox, $a_subscribed); |
| | | |
| | | // unsubscribe folder |
| | | if ($subscribed) |
| | | $this->conn->unsubscribe($mailbox); |
| | | // get list of subscribed folders |
| | | if ((strpos($mailbox, '%') === false) && (strpos($mailbox, '*') === false)) { |
| | | $a_subscribed = $this->_list_mailboxes('', $mbox_name . $delm . '*'); |
| | | $subscribed = $this->mailbox_exists($mbox_name, true); |
| | | } |
| | | else { |
| | | $a_subscribed = $this->_list_mailboxes(); |
| | | $subscribed = in_array($mailbox, $a_subscribed); |
| | | } |
| | | |
| | | if (strlen($abs_name)) |
| | | $result = $this->conn->renameFolder($mailbox, $abs_name); |
| | | |
| | | if ($result) { |
| | | $delm = $this->get_hierarchy_delimiter(); |
| | | // unsubscribe the old folder, subscribe the new one |
| | | if ($subscribed) { |
| | | $this->conn->unsubscribe($mailbox); |
| | | $this->conn->subscribe($abs_name); |
| | | } |
| | | |
| | | // check if mailbox children are subscribed |
| | | foreach ($a_subscribed as $c_subscribed) |
| | | foreach ($a_subscribed as $c_subscribed) { |
| | | if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) { |
| | | $this->conn->unsubscribe($c_subscribed); |
| | | $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/', |
| | | $abs_name, $c_subscribed)); |
| | | } |
| | | } |
| | | |
| | | // clear cache |
| | | $this->clear_message_cache($mailbox.'.msg'); |
| | | $this->clear_cache('mailboxes'); |
| | | } |
| | | |
| | | // try to subscribe it |
| | | if ($result && $subscribed) |
| | | $this->conn->subscribe($abs_name); |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Remove mailboxes from server |
| | | * Remove mailbox from server |
| | | * |
| | | * @param string|array $mbox_name Mailbox name(s) string/array |
| | | * @param string $mbox_name Mailbox name |
| | | * |
| | | * @return boolean True on success |
| | | */ |
| | | function delete_mailbox($mbox_name) |
| | | { |
| | | $deleted = false; |
| | | $result = false; |
| | | $mailbox = $this->mod_mailbox($mbox_name); |
| | | $delm = $this->get_hierarchy_delimiter(); |
| | | |
| | | if (is_array($mbox_name)) |
| | | $a_mboxes = $mbox_name; |
| | | else if (is_string($mbox_name) && strlen($mbox_name)) |
| | | $a_mboxes = explode(',', $mbox_name); |
| | | // get list of folders |
| | | if ((strpos($mailbox, '%') === false) && (strpos($mailbox, '*') === false)) |
| | | $sub_mboxes = $this->list_unsubscribed('', $mailbox . $delm . '*'); |
| | | else |
| | | $sub_mboxes = $this->list_unsubscribed(); |
| | | |
| | | if (is_array($a_mboxes)) { |
| | | $delimiter = $this->get_hierarchy_delimiter(); |
| | | |
| | | foreach ($a_mboxes as $mbox_name) { |
| | | $mailbox = $this->mod_mailbox($mbox_name); |
| | | $sub_mboxes = $this->conn->listMailboxes('', $mbox_name . $delimiter . '*'); |
| | | // send delete command to server |
| | | $result = $this->conn->deleteFolder($mailbox); |
| | | |
| | | // unsubscribe mailbox before deleting |
| | | $this->conn->unsubscribe($mailbox); |
| | | if ($result) { |
| | | // unsubscribe mailbox |
| | | $this->conn->unsubscribe($mailbox); |
| | | |
| | | // send delete command to server |
| | | $result = $this->conn->deleteFolder($mailbox); |
| | | if ($result) { |
| | | $deleted = true; |
| | | $this->clear_message_cache($mailbox.'.msg'); |
| | | } |
| | | |
| | | foreach ($sub_mboxes as $c_mbox) { |
| | | if ($c_mbox != 'INBOX') { |
| | | $this->conn->unsubscribe($c_mbox); |
| | | $result = $this->conn->deleteFolder($c_mbox); |
| | | if ($result) { |
| | | $deleted = true; |
| | | $this->clear_message_cache($c_mbox.'.msg'); |
| | | } |
| | | foreach ($sub_mboxes as $c_mbox) { |
| | | if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_mbox)) { |
| | | $this->conn->unsubscribe($c_mbox); |
| | | if ($this->conn->deleteFolder($c_mbox)) { |
| | | $this->clear_message_cache($c_mbox.'.msg'); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // clear mailbox-related cache |
| | | $this->clear_message_cache($mailbox.'.msg'); |
| | | $this->clear_cache('mailboxes'); |
| | | } |
| | | |
| | | // clear mailboxlist cache |
| | | if ($deleted) |
| | | $this->clear_cache('mailboxes'); |
| | | |
| | | return $deleted; |
| | | return $result; |
| | | } |
| | | |
| | | |
| | |
| | | // If folder contains namespace prefix, don't modify it |
| | | if (is_array($this->namespace['shared'])) { |
| | | foreach ($this->namespace['shared'] as $ns) { |
| | | foreach ((array)$ns as $root) { |
| | | if ($root[0] && strpos($mbox_name, $root[0]) === 0) { |
| | | return $mbox_name; |
| | | } |
| | | if ($ns[0] && strpos($mbox_name, $ns[0]) === 0) { |
| | | return $mbox_name; |
| | | } |
| | | } |
| | | } |
| | | if (is_array($this->namespace['other'])) { |
| | | foreach ($this->namespace['other'] as $ns) { |
| | | foreach ((array)$ns as $root) { |
| | | if ($root[0] && strpos($mbox_name, $root[0]) === 0) { |
| | | return $mbox_name; |
| | | } |
| | | if ($ns[0] && strpos($mbox_name, $ns[0]) === 0) { |
| | | return $mbox_name; |
| | | } |
| | | } |
| | | } |
| | | if (is_array($this->namespace['personal'])) { |
| | | foreach ($this->namespace['personal'] as $ns) { |
| | | foreach ((array)$ns as $root) { |
| | | if ($root[0] && strpos($mbox_name, $root[0]) === 0) { |
| | | return $mbox_name; |
| | | } |
| | | if ($ns[0] && strpos($mbox_name, $ns[0]) === 0) { |
| | | return $mbox_name; |
| | | } |
| | | } |
| | | // Add prefix if first personal namespace is non-empty |
| | | if ($this->namespace['personal'][0][0]) { |
| | | if ($mbox_name != 'INBOX' && $this->namespace['personal'][0][0]) { |
| | | return $this->namespace['personal'][0][0].$mbox_name; |
| | | } |
| | | } |
| | |
| | | private function _parse_address_list($str, $decode=true) |
| | | { |
| | | // remove any newlines and carriage returns before |
| | | $a = rcube_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str)); |
| | | $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str); |
| | | |
| | | // extract list items, remove comments |
| | | $str = self::explode_header_string(',;', $str, true); |
| | | $result = array(); |
| | | |
| | | foreach ($a as $key => $val) { |
| | | foreach ($str as $key => $val) { |
| | | $name = ''; |
| | | $address = ''; |
| | | $val = trim($val); |
| | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Explodes header (e.g. address-list) string into array of strings |
| | | * using specified separator characters with proper handling |
| | | * of quoted-strings and comments (RFC2822) |
| | | * |
| | | * @param string $separator String containing separator characters |
| | | * @param string $str Header string |
| | | * @param bool $remove_comments Enable to remove comments |
| | | * |
| | | * @return array Header items |
| | | */ |
| | | static function explode_header_string($separator, $str, $remove_comments=false) |
| | | { |
| | | $length = strlen($str); |
| | | $result = array(); |
| | | $quoted = false; |
| | | $comment = 0; |
| | | $out = ''; |
| | | |
| | | for ($i=0; $i<$length; $i++) { |
| | | // we're inside a quoted string |
| | | if ($quoted) { |
| | | if ($str[$i] == '"') { |
| | | $quoted = false; |
| | | } |
| | | else if ($str[$i] == '\\') { |
| | | if ($comment <= 0) { |
| | | $out .= '\\'; |
| | | } |
| | | $i++; |
| | | } |
| | | } |
| | | // we're inside a comment string |
| | | else if ($comment > 0) { |
| | | if ($str[$i] == ')') { |
| | | $comment--; |
| | | } |
| | | else if ($str[$i] == '(') { |
| | | $comment++; |
| | | } |
| | | else if ($str[$i] == '\\') { |
| | | $i++; |
| | | } |
| | | continue; |
| | | } |
| | | // separator, add to result array |
| | | else if (strpos($separator, $str[$i]) !== false) { |
| | | if ($out) { |
| | | $result[] = $out; |
| | | } |
| | | $out = ''; |
| | | continue; |
| | | } |
| | | // start of quoted string |
| | | else if ($str[$i] == '"') { |
| | | $quoted = true; |
| | | } |
| | | // start of comment |
| | | else if ($remove_comments && $str[$i] == '(') { |
| | | $comment++; |
| | | } |
| | | |
| | | if ($comment <= 0) { |
| | | $out .= $str[$i]; |
| | | } |
| | | } |
| | | |
| | | if ($out && $comment <= 0) { |
| | | $result[] = $out; |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This is our own debug handler for the IMAP connection |
| | | * @access public |
| | | */ |
| | | public function debug_handler(&$imap, $message) |
| | | { |
| | | write_log('imap', $message); |
| | | } |
| | | |
| | | } // end class rcube_imap |
| | | |
| | | |