From 2b017e7f79be26563ab767bb9e97fee5d88a01f4 Mon Sep 17 00:00:00 2001 From: thomascube <thomas@roundcube.net> Date: Fri, 09 Dec 2011 16:13:54 -0500 Subject: [PATCH] Allow clean background:url(...) styles in safe mode. This will make Roundcube pass the Email Standards Acid Test --- program/include/rcmail.php | 252 ++++++++++++++++++++++++++++++------------------- 1 files changed, 153 insertions(+), 99 deletions(-) diff --git a/program/include/rcmail.php b/program/include/rcmail.php index fe2d994..59ffaea 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -6,6 +6,7 @@ | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2011, The Roundcube Dev Team | + | Copyright (C) 2011, Kolab Systems AG | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -124,6 +125,7 @@ private $address_books = array(); private $caches = array(); private $action_map = array(); + private $shutdown_functions = array(); /** @@ -157,8 +159,6 @@ /** * Initial startup function * to register session, create database and imap connections - * - * @todo Remove global vars $DB, $USER */ private function startup() { @@ -170,7 +170,10 @@ } // connect to database - $GLOBALS['DB'] = $this->get_dbh(); + $this->get_dbh(); + + // set global object for backward compatibility + $GLOBALS['DB'] = $this->db; // start session $this->session_init(); @@ -238,6 +241,8 @@ { if (is_object($user)) { $this->user = $user; + + // set global object for backward compatibility $GLOBALS['USER'] = $this->user; // overwrite config with user preferences @@ -318,8 +323,8 @@ return $this->db; } - - + + /** * Get global handle for memcache access * @@ -335,20 +340,41 @@ } $this->memcache = new Memcache; - $mc_available = 0; + $this->mc_available = 0; + + // add alll configured hosts to pool + $pconnect = $this->config->get('memcache_pconnect', true); foreach ($this->config->get('memcache_hosts', array()) as $host) { list($host, $port) = explode(':', $host); if (!$port) $port = 11211; - // add server and attempt to connect if not already done yet - if ($this->memcache->addServer($host, $port) && !$mc_available) - $mc_available += intval($this->memcache->connect($host, $port)); + $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure'))); } + + // test connection and failover (will result in $this->mc_available == 0 on complete failure) + $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist - if (!$mc_available) + if (!$this->mc_available) $this->memcache = false; } return $this->memcache; + } + + /** + * Callback for memcache failure + */ + public function memcache_failure($host, $port) + { + static $seen = array(); + + // only report once + if (!$seen["$host:$port"]++) { + $this->mc_available--; + raise_error(array('code' => 604, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Memcache failure on host $host:$port"), + true, false); + } } @@ -377,6 +403,7 @@ * * @param string Address book identifier * @param boolean True if the address book needs to be writeable + * * @return rcube_contacts Address book object */ public function get_address_book($id, $writeable = false) @@ -385,8 +412,15 @@ $ldap_config = (array)$this->config->get('ldap_public'); $abook_type = strtolower($this->config->get('address_book_type')); + // 'sql' is the alias for '0' used by autocomplete + if ($id == 'sql') + $id = '0'; + // use existing instance - if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) { + if (isset($this->address_books[$id]) && is_object($this->address_books[$id]) + && is_a($this->address_books[$id], 'rcube_addressbook') + && (!$writeable || !$this->address_books[$id]->readonly) + ) { $contacts = $this->address_books[$id]; } else if ($id && $ldap_config[$id]) { @@ -402,23 +436,27 @@ if ($plugin['instance'] instanceof rcube_addressbook) { $contacts = $plugin['instance']; } - else if ($abook_type == 'ldap') { - // Use the first writable LDAP address book. - foreach ($ldap_config as $id => $prop) { - if (!$writeable || $prop['writable']) { - $contacts = new rcube_ldap($prop, $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host'])); - break; - } + // get first source from the list + else if (!$id) { + $source = reset($this->get_address_sources($writeable)); + if (!empty($source)) { + $contacts = $this->get_address_book($source['id']); + if ($contacts) + $id = $source['id']; } - } - else { // $id == 'sql' - $contacts = new rcube_contacts($this->db, $this->user->ID); } } + if (!$contacts) { + raise_error(array( + 'code' => 700, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Addressbook source ($id) not found!"), + true, true); + } + // add to the 'books' array for shutdown function - if (!isset($this->address_books[$id])) - $this->address_books[$id] = $contacts; + $this->address_books[$id] = $contacts; return $contacts; } @@ -428,6 +466,7 @@ * Return address books list * * @param boolean True if the address book needs to be writeable + * * @return array Address books array */ public function get_address_sources($writeable = false) @@ -442,11 +481,12 @@ if (!isset($this->address_books['0'])) $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID); $list['0'] = array( - 'id' => '0', - 'name' => rcube_label('personaladrbook'), - 'groups' => $this->address_books['0']->groups, + 'id' => '0', + 'name' => rcube_label('personaladrbook'), + 'groups' => $this->address_books['0']->groups, 'readonly' => $this->address_books['0']->readonly, - 'autocomplete' => in_array('sql', $autocomplete) + 'autocomplete' => in_array('sql', $autocomplete), + 'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'), ); } @@ -454,11 +494,12 @@ $ldap_config = (array) $ldap_config; foreach ($ldap_config as $id => $prop) $list[$id] = array( - 'id' => $id, - 'name' => $prop['name'], - 'groups' => is_array($prop['groups']), + 'id' => $id, + 'name' => $prop['name'], + 'groups' => is_array($prop['groups']), 'readonly' => !$prop['writable'], - 'autocomplete' => in_array('sql', $autocomplete) + 'hidden' => $prop['hidden'], + 'autocomplete' => in_array($id, $autocomplete) ); } @@ -546,7 +587,6 @@ * Create global IMAP object and connect to server * * @param boolean True if connection should be established - * @todo Remove global $IMAP */ public function imap_init($connect = false) { @@ -555,7 +595,6 @@ return; $this->imap = new rcube_imap(); - $this->imap->debug_level = $this->config->get('debug_level'); $this->imap->skip_deleted = $this->config->get('skip_deleted'); // enable caching of imap data @@ -577,7 +616,7 @@ // Setting root and delimiter before establishing the connection // can save time detecting them using NAMESPACE and LIST $options = array( - 'auth_method' => $this->config->get('imap_auth_type', 'check'), + 'auth_type' => $this->config->get('imap_auth_type', 'check'), 'auth_cid' => $this->config->get('imap_auth_cid'), 'auth_pw' => $this->config->get('imap_auth_pw'), 'debug' => (bool) $this->config->get('imap_debug', 0), @@ -640,18 +679,21 @@ if (session_id()) return; + $sess_name = $this->config->get('session_name'); + $sess_domain = $this->config->get('session_domain'); + $lifetime = $this->config->get('session_lifetime', 0) * 60; + // set session domain - if ($domain = $this->config->get('session_domain')) { - ini_set('session.cookie_domain', $domain); + if ($sess_domain) { + ini_set('session.cookie_domain', $sess_domain); } // set session garbage collecting time according to session_lifetime - $lifetime = $this->config->get('session_lifetime', 0) * 60; if ($lifetime) { ini_set('session.gc_maxlifetime', $lifetime * 2); } ini_set('session.cookie_secure', rcube_https_check()); - ini_set('session.name', 'roundcube_sessid'); + ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.serialize_handler', 'php'); @@ -691,7 +733,7 @@ $keep_alive = max(60, $keep_alive); $this->session->set_keep_alive($keep_alive); } - + $this->session->set_secret($this->config->get('des_key') . $_SERVER['HTTP_USER_AGENT']); $this->session->set_ip_check($this->config->get('ip_check')); } @@ -801,16 +843,8 @@ if (!$imap_login) return false; - $this->set_imap_prop(); - // user already registered -> update user's record if (is_object($user)) { - // fix some old settings according to namespace prefix - $this->fix_namespace_settings($user); - - // create default folders on first login - if (!$user->data['last_login'] && $config['create_default_folders']) - $this->imap->create_default_folders(); // update last login timestamp $user->touch(); } @@ -818,13 +852,10 @@ else if ($config['auto_create_user']) { if ($created = rcube_user::create($username, $host)) { $user = $created; - // create default folders on first login - if ($config['create_default_folders']) - $this->imap->create_default_folders(); } else { raise_error(array( - 'code' => 600, 'type' => 'php', + 'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to create a user record. Maybe aborted by a plugin?" ), true, false); @@ -832,16 +863,26 @@ } else { raise_error(array( - 'code' => 600, 'type' => 'php', + 'code' => 621, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Acces denied for new user $username. 'auto_create_user' is disabled" + 'message' => "Access denied for new user $username. 'auto_create_user' is disabled" ), true, false); } // login succeeded if (is_object($user) && $user->ID) { + // Configure environment $this->set_user($user); + $this->set_imap_prop(); $this->session_configure(); + + // fix some old settings according to namespace prefix + $this->fix_namespace_settings($user); + + // create default folders on first login + if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) { + $this->imap->create_default_folders(); + } // set session vars $_SESSION['user_id'] = $user->ID; @@ -851,9 +892,11 @@ $_SESSION['imap_ssl'] = $imap_ssl; $_SESSION['password'] = $this->encrypt($pass); $_SESSION['login_time'] = mktime(); - + if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_') $_SESSION['timezone'] = floatval($_REQUEST['_timezone']); + if (isset($_REQUEST['_dstactive']) && $_REQUEST['_dstactive'] != '_default_') + $_SESSION['dst_active'] = intval($_REQUEST['_dstactive']); // force reloading complete list of subscribed mailboxes $this->imap->clear_cache('mailboxes', true); @@ -932,7 +975,9 @@ /** * Get localized text in the desired language * - * @param mixed Named parameters array or label name + * @param mixed $attrib Named parameters array or label name + * @param string $domain Label domain (plugin) name + * * @return string Localized text */ public function gettext($attrib, $domain=null) @@ -945,43 +990,18 @@ if (is_string($attrib)) $attrib = array('name' => $attrib); - $nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1; $name = $attrib['name'] ? $attrib['name'] : ''; - + // attrib contain text values: use them from now if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) $this->texts[$name] = $setval; // check for text with domain - if ($domain && ($text_item = $this->texts[$domain.'.'.$name])) + if ($domain && ($text = $this->texts[$domain.'.'.$name])) ; // text does not exist - else if (!($text_item = $this->texts[$name])) { + else if (!($text = $this->texts[$name])) { return "[$name]"; - } - - // make text item array - $a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item); - - // decide which text to use - if ($nr == 1) { - $text = $a_text_item['single']; - } - else if ($nr > 0) { - $text = $a_text_item['multiple']; - } - else if ($nr == 0) { - if ($a_text_item['none']) - $text = $a_text_item['none']; - else if ($a_text_item['single']) - $text = $a_text_item['single']; - else if ($a_text_item['multiple']) - $text = $a_text_item['multiple']; - } - - // default text is single - if ($text == '') { - $text = $a_text_item['single']; } // replace vars in text @@ -998,24 +1018,45 @@ else if ($attrib['lowercase']) return mb_strtolower($text); - return $text; + return strtr($text, array('\n' => "\n")); } /** - * Check if the given text lable exists + * Check if the given text label exists * - * @param string Label name + * @param string $name Label name + * @param string $domain Label domain (plugin) name or '*' for all domains + * @param string $ref_domain Sets domain name if label is found + * * @return boolean True if text exists (either in the current language or in en_US) */ - public function text_exists($name, $domain=null) + public function text_exists($name, $domain = null, &$ref_domain = null) { // load localization files if not done yet if (empty($this->texts)) $this->load_language(); - // check for text with domain first - return ($domain && isset($this->texts[$domain.'.'.$name])) || isset($this->texts[$name]); + if (isset($this->texts[$name])) { + $ref_domain = ''; + return true; + } + + // any of loaded domains (plugins) + if ($domain == '*') { + foreach ($this->plugins->loaded_plugins() as $domain) + if (isset($this->texts[$domain.'.'.$name])) { + $ref_domain = $domain; + return true; + } + } + // specified domain + else if ($domain) { + $ref_domain = $domain; + return isset($this->texts[$domain.'.'.$name]); + } + + return false; } /** @@ -1142,13 +1183,14 @@ */ public function shutdown() { + foreach ($this->shutdown_functions as $function) + call_user_func($function); + if (is_object($this->smtp)) $this->smtp->disconnect(); foreach ($this->address_books as $book) { - if (!is_object($book)) // maybe an address book instance wasn't fetched using get_address_book() yet - $book = $this->get_address_book($book['id']); - if (is_a($book, 'rcube_addressbook')) + if (is_object($book) && is_a($book, 'rcube_addressbook')) $book->close(); } @@ -1161,8 +1203,7 @@ $this->imap->close(); // before closing the database connection, write session data - if ($_SERVER['REMOTE_ADDR']) { - $this->session->cleanup(); + if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { session_write_close(); } @@ -1183,6 +1224,19 @@ /** + * Registers shutdown function to be executed on shutdown. + * The functions will be executed before destroying any + * objects like smtp, imap, session, etc. + * + * @param callback Function callback + */ + public function add_shutdown_function($function) + { + $this->shutdown_functions[] = $function; + } + + + /** * Generate a unique token to be used in a form request * * @return string The request token @@ -1191,7 +1245,7 @@ { $sess_id = $_COOKIE[ini_get('session.name')]; if (!$sess_id) $sess_id = session_id(); - $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->task . $this->config->get('des_key') . $sess_id))); + $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->user->ID . $this->config->get('des_key') . $sess_id))); return $plugin['value']; } @@ -1485,7 +1539,7 @@ // use strtr behaviour of going through source string once $cmd = strtr($cmd, $replacements); - + return (string)shell_exec($cmd); } @@ -1521,7 +1575,7 @@ } } } - + /** * Returns current action filename * @@ -1551,8 +1605,8 @@ if (!$prefix_len) return; - $prefs = $user->get_prefs(); - if (empty($prefs) || $prefs['namespace_fixed']) + $prefs = $this->config->all(); + if (!empty($prefs['namespace_fixed'])) return; // Build namespace prefix regexp -- Gitblit v1.9.1