diff options
Diffstat (limited to 'program/lib')
20 files changed, 404 insertions, 165 deletions
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index 6e5143382..65ef98ebd 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube PHP suite | - | Copyright (C) 2005-2013, The Roundcube Dev Team | + | Copyright (C) 2005-2014, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -54,7 +54,7 @@ foreach ($config as $optname => $optval) { } // framework constants -define('RCUBE_VERSION', '1.0-git'); +define('RCUBE_VERSION', '1.1-git'); define('RCUBE_CHARSET', 'UTF-8'); if (!defined('RCUBE_LIB_DIR')) { diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 33517fbcd..64324dd8e 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -269,19 +269,28 @@ class html return ''; } - $allowed_f = array_flip((array)$allowed); + $allowed_f = array_flip((array)$allowed); $attrib_arr = array(); + foreach ($attrib as $key => $value) { // skip size if not numeric if ($key == 'size' && !is_numeric($value)) { continue; } - // ignore "internal" or not allowed attributes - if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) { + // ignore "internal" or empty attributes + if ($key == 'nl' || $value === null) { continue; } + // ignore not allowed attributes + if (!empty($allowed)) { + $is_data_attr = substr_compare($key, 'data-', 0, 5) === 0; + if (!isset($allowed_f[$key]) && (!$is_data_attr || !isset($allowed_f['data-*']))) { + continue; + } + } + // skip empty eventhandlers if (preg_match('/^on[a-z]+/', $key) && !$value) { continue; diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index d58eb087b..4ff0a00d7 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -3,8 +3,8 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2012, The Roundcube Dev Team | - | Copyright (C) 2011-2012, Kolab Systems AG | + | Copyright (C) 2008-2014, The Roundcube Dev Team | + | Copyright (C) 2011-2014, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -94,6 +94,13 @@ class rcube */ public $plugins; + /** + * Instance of rcube_user class. + * + * @var rcube_user + */ + public $user; + /* private/protected vars */ protected $texts; @@ -413,9 +420,6 @@ class rcube $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET)); - if ($default_folders = $this->config->get('default_folders')) { - $storage->set_default_folders($default_folders); - } if (isset($_SESSION['mbox'])) { $storage->set_folder($_SESSION['mbox']); } @@ -426,6 +430,36 @@ class rcube /** + * Set special folders type association. + * This must be done AFTER connecting to the server! + */ + protected function set_special_folders() + { + $storage = $this->get_storage(); + $folders = $storage->get_special_folders(true); + $prefs = array(); + + // check SPECIAL-USE flags on IMAP folders + foreach ($folders as $type => $folder) { + $idx = $type . '_mbox'; + if ($folder !== $this->config->get($idx)) { + $prefs[$idx] = $folder; + } + } + + // Some special folders differ, update user preferences + if (!empty($prefs) && $this->user) { + $this->user->save_prefs($prefs); + } + + // create default folders (on login) + if ($this->config->get('create_default_folders')) { + $storage->create_default_folders(); + } + } + + + /** * Create session object and start the session. */ public function session_init() @@ -1302,6 +1336,20 @@ class rcube self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); } + /** + * Setter for system user object + * + * @param rcube_user Current user instance + */ + public function set_user($user) + { + if (is_object($user)) { + $this->user = $user; + + // overwrite config with user preferences + $this->config->set_user_prefs((array)$this->user->get_prefs()); + } + } /** * Getter for logged user ID. diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php index e53e31200..b9642d8f9 100644 --- a/program/lib/Roundcube/rcube_browser.php +++ b/program/lib/Roundcube/rcube_browser.php @@ -34,14 +34,20 @@ class rcube_browser $this->linux = strpos($HTTP_USER_AGENT, 'linux') != false; $this->unix = strpos($HTTP_USER_AGENT, 'unix') != false; - $this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false; + $this->webkit = strpos($HTTP_USER_AGENT, 'applewebkit') !== false; + $this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false || ($this->webkit && strpos($HTTP_USER_AGENT, 'opr/') !== false); $this->ns = strpos($HTTP_USER_AGENT, 'netscape') !== false; - $this->chrome = strpos($HTTP_USER_AGENT, 'chrome') !== false; + $this->chrome = !$this->opera && strpos($HTTP_USER_AGENT, 'chrome') !== false; $this->ie = !$this->opera && (strpos($HTTP_USER_AGENT, 'compatible; msie') !== false || strpos($HTTP_USER_AGENT, 'trident/') !== false); - $this->safari = !$this->chrome && (strpos($HTTP_USER_AGENT, 'safari') !== false || strpos($HTTP_USER_AGENT, 'applewebkit') !== false); - $this->mz = !$this->ie && !$this->safari && !$this->chrome && !$this->ns && strpos($HTTP_USER_AGENT, 'mozilla') !== false; + $this->safari = !$this->opera && !$this->chrome && ($this->webkit || strpos($HTTP_USER_AGENT, 'safari') !== false); + $this->mz = !$this->ie && !$this->safari && !$this->chrome && !$this->ns && !$this->opera && strpos($HTTP_USER_AGENT, 'mozilla') !== false; - if (preg_match('/(chrome|msie|opera|version|khtml)(\s*|\/)([0-9.]+)/', $HTTP_USER_AGENT, $regs)) { + if ($this->opera) { + if (preg_match('/(opera|opr)\/([0-9.]+)/', $HTTP_USER_AGENT, $regs)) { + $this->ver = (float) $regs[2]; + } + } + else if (preg_match('/(chrome|msie|version|khtml)(\s*|\/)([0-9.]+)/', $HTTP_USER_AGENT, $regs)) { $this->ver = (float) $regs[3]; } else if (preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs)) { diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php index 0352e4772..afe13e879 100644 --- a/program/lib/Roundcube/rcube_config.php +++ b/program/lib/Roundcube/rcube_config.php @@ -63,7 +63,7 @@ class rcube_config $this->paths = explode(PATH_SEPARATOR, $paths); // make all paths absolute foreach ($this->paths as $i => $path) { - if (!$this->_is_absolute($path)) { + if (!rcube_utils::is_absolute_path($path)) { if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) { $this->paths[$i] = unslashify($realpath) . '/'; } @@ -243,8 +243,8 @@ class rcube_config */ public function resolve_paths($file, $use_env = true) { - $files = array(); - $abs_path = $this->_is_absolute($file); + $files = array(); + $abs_path = rcube_utils::is_absolute_path($file); foreach ($this->paths as $basepath) { $realpath = $abs_path ? $file : realpath($basepath . '/' . $file); @@ -270,14 +270,6 @@ class rcube_config } /** - * Determine whether the given file path is absolute or relative - */ - private function _is_absolute($path) - { - return $path[0] == DIRECTORY_SEPARATOR || preg_match('!^[a-z]:[\\\\/]!i', $path); - } - - /** * Getter for a specific config parameter * * @param string $name Parameter name diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php index d3d0ac5c8..400813dcc 100644 --- a/program/lib/Roundcube/rcube_db_mysql.php +++ b/program/lib/Roundcube/rcube_db_mysql.php @@ -38,13 +38,6 @@ class rcube_db_mysql extends rcube_db */ public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - rcube::raise_error(array('code' => 600, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => "MySQL driver requires PHP >= 5.3, current version is " . PHP_VERSION), - true, true); - } - parent::__construct($db_dsnw, $db_dsnr, $pconn); // SQL identifiers quoting @@ -128,11 +121,11 @@ class rcube_db_mysql extends rcube_db $result = array(); if (!empty($dsn['key'])) { - $result[PDO::MYSQL_ATTR_KEY] = $dsn['key']; + $result[PDO::MYSQL_ATTR_SSL_KEY] = $dsn['key']; } if (!empty($dsn['cipher'])) { - $result[PDO::MYSQL_ATTR_CIPHER] = $dsn['cipher']; + $result[PDO::MYSQL_ATTR_SSL_CIPHER] = $dsn['cipher']; } if (!empty($dsn['cert'])) { diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php index 3b4508da9..8628371d7 100644 --- a/program/lib/Roundcube/rcube_html2text.php +++ b/program/lib/Roundcube/rcube_html2text.php @@ -473,6 +473,9 @@ class rcube_html2text // Replace known html entities $text = html_entity_decode($text, ENT_QUOTES, $this->charset); + // Replace unicode nbsp to regular spaces + $text = preg_replace('/\xC2\xA0/', ' ', $text); + // Remove unknown/unhandled entities (this cannot be done in search-and-replace block) $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text); diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 6bb922d90..f60be620c 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -2457,19 +2457,7 @@ class rcube_imap extends rcube_storage return false; } - // make sure folder exists - if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) { - if (in_array($to_mbox, $this->default_folders)) { - if (!$this->create_folder($to_mbox, true)) { - return false; - } - } - else { - return false; - } - } - - $config = rcube::get_instance()->config; + $config = rcube::get_instance()->config; $to_trash = $to_mbox == $config->get('trash_mbox'); // flag messages as read before moving them @@ -2540,18 +2528,6 @@ class rcube_imap extends rcube_storage return false; } - // make sure folder exists - if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) { - if (in_array($to_mbox, $this->default_folders)) { - if (!$this->create_folder($to_mbox, true)) { - return false; - } - } - else { - return false; - } - } - // copy messages $copied = $this->conn->copy($uids, $from_mbox, $to_mbox); @@ -3067,16 +3043,17 @@ class rcube_imap extends rcube_storage * * @param string $folder New folder name * @param boolean $subscribe True if the new folder should be subscribed + * @param string $type Optional folder type (junk, trash, drafts, sent, archive) * * @return boolean True on success */ - public function create_folder($folder, $subscribe=false) + public function create_folder($folder, $subscribe = false, $type = null) { if (!$this->check_connection()) { return false; } - $result = $this->conn->createFolder($folder); + $result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null); // try to subscribe it if ($result) { @@ -3201,19 +3178,84 @@ class rcube_imap extends rcube_storage /** - * Create all folders specified as default + * Detect special folder associations stored in storage backend */ - public function create_default_folders() + public function get_special_folders($forced = false) { - // create default folders if they do not exist - foreach ($this->default_folders as $folder) { - if (!$this->folder_exists($folder)) { - $this->create_folder($folder, true); + $result = parent::get_special_folders(); + + if (isset($this->icache['special-use'])) { + return array_merge($result, $this->icache['special-use']); + } + + if (!$forced || !$this->get_capability('SPECIAL-USE')) { + return $result; + } + + if (!$this->check_connection()) { + return $result; + } + + $types = array_map(function($value) { return "\\" . ucfirst($value); }, rcube_storage::$folder_types); + $special = array(); + + // request \Subscribed flag in LIST response as performance improvement for folder_exists() + $folders = $this->conn->listMailboxes('', '*', array('SUBSCRIBED'), array('SPECIAL-USE')); + + foreach ($folders as $folder) { + if ($flags = $this->conn->data['LIST'][$folder]) { + foreach ($types as $type) { + if (in_array($type, $flags)) { + $type = strtolower(substr($type, 1)); + $special[$type] = $folder; + } + } } - else if (!$this->folder_exists($folder, true)) { - $this->subscribe($folder); + } + + $this->icache['special-use'] = $special; + unset($this->icache['special-folders']); + + return array_merge($result, $special); + } + + + /** + * Set special folder associations stored in storage backend + */ + public function set_special_folders($specials) + { + if (!$this->get_capability('SPECIAL-USE') || !$this->get_capability('METADATA')) { + return false; + } + + if (!$this->check_connection()) { + return false; + } + + $folders = $this->get_special_folders(true); + $old = (array) $this->icache['special-use']; + + foreach ($specials as $type => $folder) { + if (in_array($type, rcube_storage::$folder_types)) { + $old_folder = $old[$type]; + if ($old_folder !== $folder) { + // unset old-folder metadata + if ($old_folder !== null) { + $this->delete_metadata($old_folder, array('/private/specialuse')); + } + // set new folder metadata + if ($folder) { + $this->set_metadata($folder, array('/private/specialuse' => "\\" . ucfirst($type))); + } + } } } + + $this->icache['special-use'] = $specials; + unset($this->icache['special-folders']); + + return true; } @@ -3225,13 +3267,13 @@ class rcube_imap extends rcube_storage * * @return boolean TRUE or FALSE */ - public function folder_exists($folder, $subscription=false) + public function folder_exists($folder, $subscription = false) { if ($folder == 'INBOX') { return true; } - $key = $subscription ? 'subscribed' : 'existing'; + $key = $subscription ? 'subscribed' : 'existing'; if (is_array($this->icache[$key]) && in_array($folder, $this->icache[$key])) { return true; @@ -3242,10 +3284,24 @@ class rcube_imap extends rcube_storage } if ($subscription) { - $a_folders = $this->conn->listSubscribed('', $folder); + // It's possible we already called LIST command, check LIST data + if (!empty($this->conn->data['LIST']) && !empty($this->conn->data['LIST'][$folder]) + && in_array('\\Subscribed', $this->conn->data['LIST'][$folder]) + ) { + $a_folders = array($folder); + } + else { + $a_folders = $this->conn->listSubscribed('', $folder); + } } else { - $a_folders = $this->conn->listMailboxes('', $folder); + // It's possible we already called LIST command, check LIST data + if (!empty($this->conn->data['LIST']) && isset($this->conn->data['LIST'][$folder])) { + $a_folders = array($folder); + } + else { + $a_folders = $this->conn->listMailboxes('', $folder); + } } if (is_array($a_folders) && in_array($folder, $a_folders)) { @@ -3456,7 +3512,7 @@ class rcube_imap extends rcube_storage $options['name'] = $folder; $options['attributes'] = $this->folder_attributes($folder, true); $options['namespace'] = $this->folder_namespace($folder); - $options['special'] = in_array($folder, $this->default_folders); + $options['special'] = $this->is_special_folder($folder); // Set 'noselect' flag if (is_array($options['attributes'])) { @@ -3994,6 +4050,7 @@ class rcube_imap extends rcube_storage $a_out = $a_defaults = $folders = array(); $delimiter = $this->get_hierarchy_delimiter(); + $specials = array_merge(array('INBOX'), array_values($this->get_special_folders())); // find default folders and skip folders starting with '.' foreach ($a_folders as $folder) { @@ -4001,7 +4058,7 @@ class rcube_imap extends rcube_storage continue; } - if (!$skip_default && ($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) { + if (!$skip_default && ($p = array_search($folder, $specials)) !== false && !$a_defaults[$p]) { $a_defaults[$p] = $folder; } else { diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index f9a62f010..f45694dd0 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -73,6 +73,7 @@ class rcube_imap_generic 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 @@ -88,16 +89,28 @@ class rcube_imap_generic * * @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" : '')); @@ -116,10 +129,11 @@ class rcube_imap_generic * * @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; @@ -138,7 +152,7 @@ class rcube_imap_generic $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; @@ -153,7 +167,7 @@ class rcube_imap_generic $i++; } else { - $bytes = $this->putLine($parts[$i], false); + $bytes = $this->putLine($parts[$i], false, $anonymized); if ($bytes === false) return false; $res += $bytes; @@ -519,7 +533,7 @@ class rcube_imap_generic $reply = base64_encode($user . ' ' . $hash); // send result - $this->putLine($reply); + $this->putLine($reply, true, true); } else { // RFC2831: DIGEST-MD5 @@ -537,7 +551,7 @@ class rcube_imap_generic base64_decode($challenge), $this->host, 'imap', $user)); // send result - $this->putLine($reply); + $this->putLine($reply, true, true); $line = trim($this->readReply()); if ($line[0] == '+') { @@ -577,7 +591,7 @@ class rcube_imap_generic // 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"); @@ -588,7 +602,7 @@ class rcube_imap_generic } // send result, get reply and process it - $this->putLine($reply); + $this->putLine($reply, true, true); $line = $this->readReply(); $result = $this->parseResult($line); } @@ -619,7 +633,7 @@ class rcube_imap_generic 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)) { @@ -1177,13 +1191,20 @@ class rcube_imap_generic * 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); } @@ -1279,10 +1300,12 @@ class rcube_imap_generic * @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 $status_opts List of STATUS options + * (RFC5819: LIST-STATUS: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN) + * or RETURN options (RFC5258: LIST_EXTENDED: 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. @@ -1295,6 +1318,7 @@ class rcube_imap_generic } $args = array(); + $rets = array(); if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) { $select_opts = (array) $select_opts; @@ -1305,11 +1329,21 @@ class rcube_imap_generic $args[] = $this->escape($ref); $args[] = $this->escape($mailbox); + if (!empty($status_opts) && $this->getCapability('LIST-EXTENDED')) { + $rets = array_intersect($status_opts, array('SUBSCRIBED', 'CHILDREN')); + } + if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) { - $status_opts = (array) $status_opts; - $lstatus = true; + $status_opts = array_intersect($status_opts, array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN')); + + if (!empty($status_opts)) { + $lstatus = true; + $rets[] = 'STATUS (' . implode(' ', $status_opts) . ')'; + } + } - $args[] = 'RETURN (STATUS (' . implode(' ', $status_opts) . '))'; + if (!empty($rets)) { + $args[] = 'RETURN (' . implode(' ', $rets) . ')'; } list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args); @@ -1555,23 +1589,23 @@ class rcube_imap_generic * * @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); } @@ -1579,18 +1613,21 @@ class rcube_imap_generic 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; @@ -1620,7 +1657,7 @@ class rcube_imap_generic // 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'; @@ -3419,7 +3456,7 @@ class rcube_imap_generic } // 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, ''); } diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index de3790e5c..55a64acec 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -95,8 +95,8 @@ class rcube_ldap extends rcube_addressbook if (empty($this->prop['groups']['scope'])) $this->prop['groups']['scope'] = 'sub'; // extend group objectclass => member attribute mapping - if (!empty($this->prop['groups']['class_member_attr'])) - $this->group_types = array_merge($this->group_types, $this->prop['groups']['class_member_attr']); + if (!empty($this->prop['groups']['event-panel-summary'])) + $this->group_types = array_merge($this->group_types, $this->prop['groups']['event-panel-summary']); // add group name attrib to the list of attributes to be fetched $fetch_attributes[] = $this->prop['groups']['name_attr']; @@ -1409,6 +1409,16 @@ class rcube_ldap extends rcube_addressbook $fieldmap['name'] = $this->group_data['name_attr'] ? $this->group_data['name_attr'] : $this->prop['groups']['name_attr']; } + // assign object type from object class mapping + if (!empty($this->prop['class_type_map'])) { + foreach (array_map('strtolower', (array)$rec['objectclass']) as $objcls) { + if (!empty($this->prop['class_type_map'][$objcls])) { + $out['_type'] = $this->prop['class_type_map'][$objcls]; + break; + } + } + } + foreach ($fieldmap as $rf => $lf) { for ($i=0; $i < $rec[$lf]['count']; $i++) { diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php index 923a12a41..f1048ef39 100644 --- a/program/lib/Roundcube/rcube_ldap_generic.php +++ b/program/lib/Roundcube/rcube_ldap_generic.php @@ -160,7 +160,7 @@ class rcube_ldap_generic $this->config['hosts'] = array($this->config['hosts']); foreach ($this->config['hosts'] as $host) { - if ($this->connect($host)) { + if (!empty($host) && $this->connect($host)) { return true; } } @@ -240,7 +240,7 @@ class rcube_ldap_generic $method = 'DIGEST-MD5'; } - $this->_debug("C: SASL Bind [mech: $method, authc: $authc, authz: $authz, pass: $pass]"); + $this->_debug("C: SASL Bind [mech: $method, authc: $authc, authz: $authz, pass: **** [" . strlen($pass) . "]"); if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) { $this->_debug("S: OK"); @@ -271,7 +271,7 @@ class rcube_ldap_generic return false; } - $this->_debug("C: Bind $dn [pass: $pass]"); + $this->_debug("C: Bind $dn, pass: **** [" . strlen($pass) . "]"); if (@ldap_bind($this->conn, $dn, $pass)) { $this->_debug("S: OK"); diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index a931c27c1..091b2fae8 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -366,6 +366,9 @@ class rcube_mime $address = 'MAILER-DAEMON'; $name = substr($val, 0, -strlen($m[1])); } + else if (preg_match('/('.$email_rx.')/', $val, $m)) { + $name = $m[1]; + } else { $name = $val; } @@ -378,11 +381,16 @@ class rcube_mime } if ($decode) { $name = self::decode_header($name, $fallback); + // some clients encode addressee name with quotes around it + if ($name[0] == '"' && $name[strlen($name)-1] == '"') { + $name = substr($name, 1, -1); + } } } if (!$address && $name) { $address = $name; + $name = ''; } if ($address) { diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index 461c3cc07..feeeb192e 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -182,7 +182,7 @@ class rcube_plugin_api } // plugin already loaded - if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) { + if ($this->plugins[$plugin_name]) { return true; } @@ -190,7 +190,9 @@ class rcube_plugin_api . DIRECTORY_SEPARATOR . $plugin_name . '.php'; if (file_exists($fn)) { - include $fn; + if (!class_exists($plugin_name, false)) { + include $fn; + } // instantiate class if exists if (class_exists($plugin_name, false)) { diff --git a/program/lib/Roundcube/rcube_result_index.php b/program/lib/Roundcube/rcube_result_index.php index 5f592c54f..058f25c6f 100644 --- a/program/lib/Roundcube/rcube_result_index.php +++ b/program/lib/Roundcube/rcube_result_index.php @@ -231,29 +231,13 @@ class rcube_result_index /** - * Filters data set. Removes elements listed in $ids list. + * Filters data set. Removes elements not listed in $ids list. * * @param array $ids List of IDs to remove. */ public function filter($ids = array()) { $data = $this->get(); - $data = array_diff($data, $ids); - - $this->meta = array(); - $this->meta['count'] = count($data); - $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data); - } - - - /** - * Filters data set. Removes elements not listed in $ids list. - * - * @param array $ids List of IDs to keep. - */ - public function intersect($ids = array()) - { - $data = $this->get(); $data = array_intersect($data, $ids); $this->meta = array(); @@ -332,6 +316,7 @@ class rcube_result_index if (empty($this->raw_data)) { return array(); } + return explode(self::SEPARATOR_ELEMENT, $this->raw_data); } diff --git a/program/lib/Roundcube/rcube_result_set.php b/program/lib/Roundcube/rcube_result_set.php index a4b070e28..82502ce5f 100644 --- a/program/lib/Roundcube/rcube_result_set.php +++ b/program/lib/Roundcube/rcube_result_set.php @@ -25,7 +25,7 @@ * @package Framework * @subpackage Addressbook */ -class rcube_result_set implements Iterator +class rcube_result_set implements Iterator, ArrayAccess { public $count = 0; public $first = 0; @@ -61,6 +61,34 @@ class rcube_result_set implements Iterator $this->current = $i; } + /*** Implement PHP ArrayAccess interface ***/ + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $offset = count($this->records); + $this->records[] = $value; + } + else { + $this->records[$offset] = $value; + } + } + + public function offsetExists($offset) + { + return isset($this->records[$offset]); + } + + public function offsetUnset($offset) + { + unset($this->records[$offset]); + } + + public function offsetGet($offset) + { + return $this->records[$offset]; + } + /*** PHP 5 Iterator interface ***/ function rewind() diff --git a/program/lib/Roundcube/rcube_result_thread.php b/program/lib/Roundcube/rcube_result_thread.php index c7f21db53..ceaaf59a6 100644 --- a/program/lib/Roundcube/rcube_result_thread.php +++ b/program/lib/Roundcube/rcube_result_thread.php @@ -453,7 +453,7 @@ class rcube_result_thread // when sorting search result it's good to make the index smaller if ($index->count() != $this->count_messages()) { - $index->intersect($this->get()); + $index->filter($this->get()); } $result = array_fill_keys($index->get(), null); diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php index 60b1389ea..70f15dc7b 100644 --- a/program/lib/Roundcube/rcube_smtp.php +++ b/program/lib/Roundcube/rcube_smtp.php @@ -29,6 +29,7 @@ class rcube_smtp private $conn = null; private $response; private $error; + private $anonymize_log = 0; // define headers delimiter const SMTP_MIME_CRLF = "\r\n"; @@ -67,6 +68,7 @@ class rcube_smtp 'smtp_auth_type' => $rcube->config->get('smtp_auth_type'), 'smtp_helo_host' => $rcube->config->get('smtp_helo_host'), 'smtp_timeout' => $rcube->config->get('smtp_timeout'), + 'smtp_conn_options' => $rcube->config->get('smtp_conn_options'), 'smtp_auth_callbacks' => array(), )); @@ -106,10 +108,11 @@ class rcube_smtp // IDNA Support $smtp_host = rcube_utils::idn_to_ascii($smtp_host); - $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host); + $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host, false, 0, $CONFIG['smtp_conn_options']); if ($rcube->config->get('smtp_debug')) { $this->conn->setDebug(true, array($this, 'debug_handler')); + $this->anonymize_log = 0; } // register authentication methods @@ -329,6 +332,15 @@ class rcube_smtp */ public function debug_handler(&$smtp, $message) { + // catch AUTH commands and set anonymization flag for subsequent sends + if (preg_match('/^Send: AUTH ([A-Z]+)/', $message, $m)) { + $this->anonymize_log = $m[1] == 'LOGIN' ? 2 : 1; + } + // anonymize this log entry + else if ($this->anonymize_log > 0 && strpos($message, 'Send:') === 0 && --$this->anonymize_log == 0) { + $message = sprintf('Send: ****** [%d]', strlen($message) - 8); + } + if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) { $diff = $len - self::DEBUG_LINE_LENGTH; $message = substr($message, 0, self::DEBUG_LINE_LENGTH) diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index ca65af1cb..69d6d2fae 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -35,9 +35,15 @@ abstract class rcube_storage */ public $conn; + /** + * List of supported special folder types + * + * @var array + */ + public static $folder_types = array('drafts', 'sent', 'junk', 'trash'); + protected $folder = 'INBOX'; protected $default_charset = 'ISO-8859-1'; - protected $default_folders = array('INBOX'); protected $search_set; protected $options = array('auth_type' => 'check'); protected $page_size = 10; @@ -167,24 +173,6 @@ abstract class rcube_storage /** - * This list of folders will be listed above all other folders - * - * @param array $arr Indexed list of folder names - */ - public function set_default_folders($arr) - { - if (is_array($arr)) { - $this->default_folders = $arr; - - // add inbox if not included - if (!in_array('INBOX', $this->default_folders)) { - array_unshift($this->default_folders, 'INBOX'); - } - } - } - - - /** * Set internal folder reference. * All operations will be perfomed on this folder. * @@ -613,7 +601,7 @@ abstract class rcube_storage /** * Parse message UIDs input * - * @param mixed $uids UIDs array or comma-separated list or '*' or '1:*' + * @param mixed $uids UIDs array or comma-separated list or '*' or '1:*' * * @return array Two elements array with UIDs converted to list and ALL flag */ @@ -633,6 +621,9 @@ abstract class rcube_storage if (is_array($uids)) { $uids = join(',', $uids); } + else if (strpos($uids, ':')) { + $uids = join(',', rcube_imap_generic::uncompressMessageSet($uids)); + } if (preg_match('/[^0-9,]/', $uids)) { $uids = ''; @@ -855,15 +846,59 @@ abstract class rcube_storage */ public function create_default_folders() { + $rcube = rcube::get_instance(); + // create default folders if they do not exist - foreach ($this->default_folders as $folder) { - if (!$this->folder_exists($folder)) { - $this->create_folder($folder, true); + foreach (self::$folder_types as $type) { + if ($folder = $rcube->config->get($type . '_mbox')) { + if (!$this->folder_exists($folder)) { + $this->create_folder($folder, true, $type); + } + else if (!$this->folder_exists($folder, true)) { + $this->subscribe($folder); + } } - else if (!$this->folder_exists($folder, true)) { - $this->subscribe($folder); + } + } + + + /** + * Check if specified folder is a special folder + */ + public function is_special_folder($name) + { + return $name == 'INBOX' || in_array($name, $this->get_special_folders()); + } + + + /** + * Return configured special folders + */ + public function get_special_folders($forced = false) + { + // getting config might be expensive, store special folders in memory + if (!isset($this->icache['special-folders'])) { + $rcube = rcube::get_instance(); + $this->icache['special-folders'] = array(); + + foreach (self::$folder_types as $type) { + if ($folder = $rcube->config->get($type . '_mbox')) { + $this->icache['special-folders'][$type] = $folder; + } } } + + return $this->icache['special-folders']; + } + + + /** + * Set special folder associations stored in backend + */ + public function set_special_folders($specials) + { + // should be overriden by storage class if backend supports special folders (SPECIAL-USE) + unset($this->icache['special-folders']); } diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index c48cd80e8..46d53ac91 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -1045,4 +1045,16 @@ class rcube_utils return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true); } + /** + * OS-dependent absolute path detection + */ + public static function is_absolute_path($path) + { + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + return (bool) preg_match('!^[a-z]:[\\\\/]!i', $path); + } + else { + return $path[0] == DIRECTORY_SEPARATOR; + } + } } diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 5a5b3dc55..e9fec54b3 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -184,7 +184,7 @@ class rcube_washtml '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'. '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'. '|#[0-9a-f]{3,6}'. - '|[a-z0-9", -]+'. + '|[a-z0-9"\', -]+'. ')\s*/i', $str, $match) ) { if ($match[2]) { @@ -283,10 +283,12 @@ class rcube_washtml /** * The main loop that recurse on a node tree. - * It output only allowed tags with allowed attributes - * and allowed inline styles + * It output only allowed tags with allowed attributes and allowed inline styles + * + * @param DOMNode $node HTML element + * @param int $level Recurrence level (safe initial value found empirically) */ - private function dumpHtml($node, $level = 0) + private function dumpHtml($node, $level = 20) { if (!$node->hasChildNodes()) { return ''; |