diff options
Diffstat (limited to 'program')
33 files changed, 1130 insertions, 887 deletions
diff --git a/program/include/html.php b/program/include/html.php index d15d50875..c6507f813 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -154,7 +154,7 @@ class html $attr = array('src' => $attr); } return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib, - array('src','alt','width','height','border','usemap','onclick'))); + array('src','alt','width','height','border','usemap','onclick'))); } /** diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a6b0bcd57..02f38e647 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -157,14 +157,16 @@ class rcmail extends rcube $this->config->set_user_prefs((array)$this->user->get_prefs()); } - $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language'])); + $lang = $this->language_prop($this->config->get('language', $_SESSION['language'])); + $_SESSION['language'] = $this->user->language = $lang; // set localization - setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8'); + setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8'); // workaround for http://bugs.php.net/bug.php?id=18556 - if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ'))) - setlocale(LC_CTYPE, 'en_US' . '.utf8'); + if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) { + setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); + } } @@ -306,7 +308,7 @@ class rcmail extends rcube /** * Init output object for GUI and add common scripts. - * This will instantiate a rcmail_template object and set + * This will instantiate a rcube_output_html object and set * environment vars according to the current session and configuration * * @param boolean True if this request is loaded in a (i)frame @@ -453,7 +455,14 @@ class rcmail extends rcube // Convert username to lowercase. If storage backend // is case-insensitive we need to store always the same username (#1487113) if ($config['login_lc']) { - $username = mb_strtolower($username); + if ($config['login_lc'] == 2 || $config['login_lc'] === true) { + $username = mb_strtolower($username); + } + else if (strpos($username, '@')) { + // lowercase domain name + list($local, $domain) = explode('@', $username); + $username = $local . '@' . mb_strtolower($domain); + } } // try to resolve email address from virtuser table @@ -463,17 +472,13 @@ class rcmail extends rcube // Here we need IDNA ASCII // Only rcube_contacts class is using domain names in Unicode - $host = rcube_utils::idn_to_ascii($host); - if (strpos($username, '@')) { - // lowercase domain name - list($local, $domain) = explode('@', $username); - $username = $local . '@' . mb_strtolower($domain); - $username = rcube_utils::idn_to_ascii($username); - } + $host = rcube_utils::idn_to_ascii($host); + $username = rcube_utils::idn_to_ascii($username); // user already registered -> overwrite username - if ($user = rcube_user::query($username, $host)) + if ($user = rcube_user::query($username, $host)) { $username = $user->data['username']; + } $storage = $this->get_storage(); @@ -1204,7 +1209,7 @@ class rcmail extends rcube } else { if (!empty($date)) { - $timestamp = rcube_strtotime($date); + $timestamp = rcube_utils::strtotime($date); } if (empty($timestamp)) { diff --git a/program/include/rcube.php b/program/include/rcube.php index 494b5c3dd..0e40b3c6b 100644 --- a/program/include/rcube.php +++ b/program/include/rcube.php @@ -25,364 +25,372 @@ * Base class of the Roundcube Framework * implemented as singleton * - * @package Core + * @package Framework + * @subpackage Core */ class rcube { - const INIT_WITH_DB = 1; - const INIT_WITH_PLUGINS = 2; - - /** - * Singleton instace of rcube - * - * @var rcmail - */ - static protected $instance; - - /** - * Stores instance of rcube_config. - * - * @var rcube_config - */ - public $config; - - /** - * Instace of database class. - * - * @var rcube_pdo - */ - public $db; - - /** - * Instace of Memcache class. - * - * @var Memcache - */ - public $memcache; - - /** - * Instace of rcube_session class. - * - * @var rcube_session - */ - public $session; - - /** - * Instance of rcube_smtp class. - * - * @var rcube_smtp - */ - public $smtp; - - /** - * Instance of rcube_storage class. - * - * @var rcube_storage - */ - public $storage; - - /** - * Instance of rcube_output class. - * - * @var rcube_output - */ - public $output; - - /** - * Instance of rcube_plugin_api. - * - * @var rcube_plugin_api - */ - public $plugins; - - - /* private/protected vars */ - protected $texts; - protected $caches = array(); - protected $shutdown_functions = array(); - protected $expunge_cache = false; - - - /** - * This implements the 'singleton' design pattern - * - * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants - * @return rcube The one and only instance - */ - static function get_instance($mode = 0) - { - if (!self::$instance) { - self::$instance = new rcube(); - self::$instance->init($mode); - } + const INIT_WITH_DB = 1; + const INIT_WITH_PLUGINS = 2; - return self::$instance; - } - - - /** - * Private constructor - */ - protected function __construct() - { - // load configuration - $this->config = new rcube_config(); - $this->plugins = new rcube_dummy_plugin_api; - - register_shutdown_function(array($this, 'shutdown')); - } - - - /** - * Initial startup function - */ - protected function init($mode = 0) - { - // initialize syslog - if ($this->config->get('log_driver') == 'syslog') { - $syslog_id = $this->config->get('syslog_id', 'roundcube'); - $syslog_facility = $this->config->get('syslog_facility', LOG_USER); - openlog($syslog_id, LOG_ODELAY, $syslog_facility); - } + /** + * Singleton instace of rcube + * + * @var rcmail + */ + static protected $instance; - // connect to database - if ($mode & self::INIT_WITH_DB) { - $this->get_dbh(); - } + /** + * Stores instance of rcube_config. + * + * @var rcube_config + */ + public $config; + + /** + * Instace of database class. + * + * @var rcube_db + */ + public $db; + + /** + * Instace of Memcache class. + * + * @var Memcache + */ + public $memcache; + + /** + * Instace of rcube_session class. + * + * @var rcube_session + */ + public $session; + + /** + * Instance of rcube_smtp class. + * + * @var rcube_smtp + */ + public $smtp; + + /** + * Instance of rcube_storage class. + * + * @var rcube_storage + */ + public $storage; + + /** + * Instance of rcube_output class. + * + * @var rcube_output + */ + public $output; + + /** + * Instance of rcube_plugin_api. + * + * @var rcube_plugin_api + */ + public $plugins; - // create plugin API and load plugins - if ($mode & self::INIT_WITH_PLUGINS) { - $this->plugins = rcube_plugin_api::get_instance(); + + /* private/protected vars */ + protected $texts; + protected $caches = array(); + protected $shutdown_functions = array(); + protected $expunge_cache = false; + + + /** + * This implements the 'singleton' design pattern + * + * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants + * + * @return rcube The one and only instance + */ + static function get_instance($mode = 0) + { + if (!self::$instance) { + self::$instance = new rcube(); + self::$instance->init($mode); + } + + return self::$instance; } - } - - - /** - * Get the current database connection - * - * @return rcube_pdo Database connection object - */ - public function get_dbh() - { - if (!$this->db) { - $config_all = $this->config->all(); - $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); - $this->db->set_debug((bool)$config_all['sql_debug']); + + + /** + * Private constructor + */ + protected function __construct() + { + // load configuration + $this->config = new rcube_config; + $this->plugins = new rcube_dummy_plugin_api; + + register_shutdown_function(array($this, 'shutdown')); } - return $this->db; - } - - - /** - * Get global handle for memcache access - * - * @return object Memcache - */ - public function get_memcache() - { - if (!isset($this->memcache)) { - // no memcache support in PHP - if (!class_exists('Memcache')) { - $this->memcache = false; - return false; - } - $this->memcache = new Memcache; - $this->mc_available = 0; + /** + * Initial startup function + */ + protected function init($mode = 0) + { + // initialize syslog + if ($this->config->get('log_driver') == 'syslog') { + $syslog_id = $this->config->get('syslog_id', 'roundcube'); + $syslog_facility = $this->config->get('syslog_facility', LOG_USER); + openlog($syslog_id, LOG_ODELAY, $syslog_facility); + } - // add all configured hosts to pool - $pconnect = $this->config->get('memcache_pconnect', true); - foreach ($this->config->get('memcache_hosts', array()) as $host) { - if (substr($host, 0, 7) != 'unix://') { - list($host, $port) = explode(':', $host); - if (!$port) $port = 11211; + // connect to database + if ($mode & self::INIT_WITH_DB) { + $this->get_dbh(); } - else { - $port = 0; + + // create plugin API and load plugins + if ($mode & self::INIT_WITH_PLUGINS) { + $this->plugins = rcube_plugin_api::get_instance(); } + } - $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 + /** + * Get the current database connection + * + * @return rcube_db Database object + */ + public function get_dbh() + { + if (!$this->db) { + $config_all = $this->config->all(); + $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); + $this->db->set_debug((bool)$config_all['sql_debug']); + } - if (!$this->mc_available) - $this->memcache = false; + return $this->db; } - return $this->memcache; - } + /** + * Get global handle for memcache access + * + * @return object Memcache + */ + public function get_memcache() + { + if (!isset($this->memcache)) { + // no memcache support in PHP + if (!class_exists('Memcache')) { + $this->memcache = false; + return false; + } - /** - * Callback for memcache failure - */ - public function memcache_failure($host, $port) - { - static $seen = array(); + $this->memcache = new Memcache; + $this->mc_available = 0; - // only report once - if (!$seen["$host:$port"]++) { - $this->mc_available--; - self::raise_error(array('code' => 604, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => "Memcache failure on host $host:$port"), - true, false); - } - } - - - /** - * Initialize and get cache object - * - * @param string $name Cache identifier - * @param string $type Cache type ('db', 'apc' or 'memcache') - * @param string $ttl Expiration time for cache items - * @param bool $packed Enables/disables data serialization - * - * @return rcube_cache Cache object - */ - public function get_cache($name, $type='db', $ttl=0, $packed=true) - { - if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) { - $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed); - } + // add all configured hosts to pool + $pconnect = $this->config->get('memcache_pconnect', true); + foreach ($this->config->get('memcache_hosts', array()) as $host) { + if (substr($host, 0, 7) != 'unix://') { + list($host, $port) = explode(':', $host); + if (!$port) $port = 11211; + } + else { + $port = 0; + } - return $this->caches[$name]; - } - - - /** - * Create SMTP object and connect to server - * - * @param boolean True if connection should be established - */ - public function smtp_init($connect = false) - { - $this->smtp = new rcube_smtp(); - - if ($connect) - $this->smtp->connect(); - } - - - /** - * Initialize and get storage object - * - * @return rcube_storage Storage object - */ - public function get_storage() - { - // already initialized - if (!is_object($this->storage)) { - $this->storage_init(); - } + $this->mc_available += intval($this->memcache->addServer( + $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure'))); + } - return $this->storage; - } + // 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 (!$this->mc_available) { + $this->memcache = false; + } + } - /** - * Initialize storage object - */ - public function storage_init() - { - // already initialized - if (is_object($this->storage)) { - return; + return $this->memcache; } - $driver = $this->config->get('storage_driver', 'imap'); - $driver_class = "rcube_{$driver}"; - if (!class_exists($driver_class)) { - self::raise_error(array( - 'code' => 700, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Storage driver class ($driver) not found!"), - true, true); + /** + * Callback for memcache failure + */ + public function memcache_failure($host, $port) + { + static $seen = array(); + + // only report once + if (!$seen["$host:$port"]++) { + $this->mc_available--; + self::raise_error(array( + 'code' => 604, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Memcache failure on host $host:$port"), + true, false); + } } - // Initialize storage object - $this->storage = new $driver_class; - // for backward compat. (deprecated, will be removed) - $this->imap = $this->storage; + /** + * Initialize and get cache object + * + * @param string $name Cache identifier + * @param string $type Cache type ('db', 'apc' or 'memcache') + * @param string $ttl Expiration time for cache items + * @param bool $packed Enables/disables data serialization + * + * @return rcube_cache Cache object + */ + public function get_cache($name, $type='db', $ttl=0, $packed=true) + { + if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) { + $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed); + } - // enable caching of mail data - $storage_cache = $this->config->get("{$driver}_cache"); - $messages_cache = $this->config->get('messages_cache'); - // for backward compatybility - if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { - $storage_cache = 'db'; - $messages_cache = true; + return $this->caches[$name]; } - if ($storage_cache) - $this->storage->set_caching($storage_cache); - if ($messages_cache) - $this->storage->set_messages_caching(true); - // set pagesize from config - $pagesize = $this->config->get('mail_pagesize'); - if (!$pagesize) { - $pagesize = $this->config->get('pagesize', 50); + /** + * Create SMTP object and connect to server + * + * @param boolean True if connection should be established + */ + public function smtp_init($connect = false) + { + $this->smtp = new rcube_smtp(); + + if ($connect) { + $this->smtp->connect(); + } } - $this->storage->set_pagesize($pagesize); - - // set class options - $options = array( - 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), - 'auth_cid' => $this->config->get("{$driver}_auth_cid"), - 'auth_pw' => $this->config->get("{$driver}_auth_pw"), - 'debug' => (bool) $this->config->get("{$driver}_debug"), - 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), - 'timeout' => (int) $this->config->get("{$driver}_timeout"), - 'skip_deleted' => (bool) $this->config->get('skip_deleted'), - 'driver' => $driver, - ); - - if (!empty($_SESSION['storage_host'])) { - $options['host'] = $_SESSION['storage_host']; - $options['user'] = $_SESSION['username']; - $options['port'] = $_SESSION['storage_port']; - $options['ssl'] = $_SESSION['storage_ssl']; - $options['password'] = $this->decrypt($_SESSION['password']); - $_SESSION[$driver.'_host'] = $_SESSION['storage_host']; + + + /** + * Initialize and get storage object + * + * @return rcube_storage Storage object + */ + public function get_storage() + { + // already initialized + if (!is_object($this->storage)) { + $this->storage_init(); + } + + return $this->storage; } - $options = $this->plugins->exec_hook("storage_init", $options); - // for backward compat. (deprecated, to be removed) - $options = $this->plugins->exec_hook("imap_init", $options); + /** + * Initialize storage object + */ + public function storage_init() + { + // already initialized + if (is_object($this->storage)) { + return; + } - $this->storage->set_options($options); - $this->set_storage_prop(); - } + $driver = $this->config->get('storage_driver', 'imap'); + $driver_class = "rcube_{$driver}"; + if (!class_exists($driver_class)) { + self::raise_error(array( + 'code' => 700, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Storage driver class ($driver) not found!"), + true, true); + } - /** - * Set storage parameters. - * This must be done AFTER connecting to the server! - */ - protected function set_storage_prop() - { - $storage = $this->get_storage(); + // Initialize storage object + $this->storage = new $driver_class; - $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET)); + // for backward compat. (deprecated, will be removed) + $this->imap = $this->storage; - if ($default_folders = $this->config->get('default_folders')) { - $storage->set_default_folders($default_folders); - } - if (isset($_SESSION['mbox'])) { - $storage->set_folder($_SESSION['mbox']); + // enable caching of mail data + $storage_cache = $this->config->get("{$driver}_cache"); + $messages_cache = $this->config->get('messages_cache'); + // for backward compatybility + if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { + $storage_cache = 'db'; + $messages_cache = true; + } + + if ($storage_cache) { + $this->storage->set_caching($storage_cache); + } + if ($messages_cache) { + $this->storage->set_messages_caching(true); + } + + // set pagesize from config + $pagesize = $this->config->get('mail_pagesize'); + if (!$pagesize) { + $pagesize = $this->config->get('pagesize', 50); + } + $this->storage->set_pagesize($pagesize); + + // set class options + $options = array( + 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), + 'auth_cid' => $this->config->get("{$driver}_auth_cid"), + 'auth_pw' => $this->config->get("{$driver}_auth_pw"), + 'debug' => (bool) $this->config->get("{$driver}_debug"), + 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), + 'timeout' => (int) $this->config->get("{$driver}_timeout"), + 'skip_deleted' => (bool) $this->config->get('skip_deleted'), + 'driver' => $driver, + ); + + if (!empty($_SESSION['storage_host'])) { + $options['host'] = $_SESSION['storage_host']; + $options['user'] = $_SESSION['username']; + $options['port'] = $_SESSION['storage_port']; + $options['ssl'] = $_SESSION['storage_ssl']; + $options['password'] = $this->decrypt($_SESSION['password']); + $_SESSION[$driver.'_host'] = $_SESSION['storage_host']; + } + + $options = $this->plugins->exec_hook("storage_init", $options); + + // for backward compat. (deprecated, to be removed) + $options = $this->plugins->exec_hook("imap_init", $options); + + $this->storage->set_options($options); + $this->set_storage_prop(); } - if (isset($_SESSION['page'])) { - $storage->set_page($_SESSION['page']); + + + /** + * Set storage parameters. + * This must be done AFTER connecting to the server! + */ + protected function set_storage_prop() + { + $storage = $this->get_storage(); + + $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET)); + + if ($default_folders = $this->config->get('default_folders')) { + $storage->set_default_folders($default_folders); + } + if (isset($_SESSION['mbox'])) { + $storage->set_folder($_SESSION['mbox']); + } + if (isset($_SESSION['page'])) { + $storage->set_page($_SESSION['page']); + } } - } /** @@ -397,12 +405,17 @@ class rcube $sess_name = $this->config->get('session_name'); $sess_domain = $this->config->get('session_domain'); + $sess_path = $this->config->get('session_path'); $lifetime = $this->config->get('session_lifetime', 0) * 60; // set session domain if ($sess_domain) { ini_set('session.cookie_domain', $sess_domain); } + // set session path + if ($sess_path) { + ini_set('session.cookie_path', $sess_path); + } // set session garbage collecting time according to session_lifetime if ($lifetime) { ini_set('session.gc_maxlifetime', $lifetime * 2); @@ -492,433 +505,469 @@ class rcube } - /** - * Get localized text in the desired language - * - * @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) - { - // load localization files if not done yet - if (empty($this->texts)) - $this->load_language(); - - // extract attributes - if (is_string($attrib)) - $attrib = array('name' => $attrib); - - $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 = $this->texts[$domain.'.'.$name])) - ; - // text does not exist - else if (!($text = $this->texts[$name])) { - return "[$name]"; - } + /** + * Get localized text in the desired language + * + * @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) + { + // load localization files if not done yet + if (empty($this->texts)) { + $this->load_language(); + } - // replace vars in text - if (is_array($attrib['vars'])) { - foreach ($attrib['vars'] as $var_key => $var_value) - $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text); - } + // extract attributes + if (is_string($attrib)) { + $attrib = array('name' => $attrib); + } - // format output - if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst']) - return ucfirst($text); - else if ($attrib['uppercase']) - return mb_strtoupper($text); - else if ($attrib['lowercase']) - return mb_strtolower($text); - - return strtr($text, array('\n' => "\n")); - } - - - /** - * Check if the given text label exists - * - * @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, &$ref_domain = null) - { - // load localization files if not done yet - if (empty($this->texts)) - $this->load_language(); - - if (isset($this->texts[$name])) { - $ref_domain = ''; - return true; - } + $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; + } - // 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; + // check for text with domain + if ($domain && ($text = $this->texts[$domain.'.'.$name])) { + } + // text does not exist + else if (!($text = $this->texts[$name])) { + return "[$name]"; + } + + // replace vars in text + if (is_array($attrib['vars'])) { + foreach ($attrib['vars'] as $var_key => $var_value) { + $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text); + } } + + // format output + if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) { + return ucfirst($text); + } + else if ($attrib['uppercase']) { + return mb_strtoupper($text); + } + else if ($attrib['lowercase']) { + return mb_strtolower($text); + } + + return strtr($text, array('\n' => "\n")); } - // specified domain - else if ($domain) { - $ref_domain = $domain; - return isset($this->texts[$domain.'.'.$name]); + + + /** + * Check if the given text label exists + * + * @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, &$ref_domain = null) + { + // load localization files if not done yet + if (empty($this->texts)) { + $this->load_language(); + } + + 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; } - return false; - } - /** - * Load a localization package - * - * @param string Language ID - */ - public function load_language($lang = null, $add = array()) - { - $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); + /** + * Load a localization package + * + * @param string Language ID + * @param array Additional text labels/messages + */ + public function load_language($lang = null, $add = array()) + { + $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); + + // load localized texts + if (empty($this->texts) || $lang != $_SESSION['language']) { + $this->texts = array(); - // load localized texts - if (empty($this->texts) || $lang != $_SESSION['language']) { - $this->texts = array(); + // handle empty lines after closing PHP tag in localization files + ob_start(); - // handle empty lines after closing PHP tag in localization files - ob_start(); + // get english labels (these should be complete) + @include(INSTALL_PATH . 'program/localization/en_US/labels.inc'); + @include(INSTALL_PATH . 'program/localization/en_US/messages.inc'); - // get english labels (these should be complete) - @include(INSTALL_PATH . 'program/localization/en_US/labels.inc'); - @include(INSTALL_PATH . 'program/localization/en_US/messages.inc'); + if (is_array($labels)) + $this->texts = $labels; + if (is_array($messages)) + $this->texts = array_merge($this->texts, $messages); - if (is_array($labels)) - $this->texts = $labels; - if (is_array($messages)) - $this->texts = array_merge($this->texts, $messages); + // include user language files + if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { + include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc'); + include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc'); - // include user language files - if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { - include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc'); - include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc'); + if (is_array($labels)) + $this->texts = array_merge($this->texts, $labels); + if (is_array($messages)) + $this->texts = array_merge($this->texts, $messages); + } - if (is_array($labels)) - $this->texts = array_merge($this->texts, $labels); - if (is_array($messages)) - $this->texts = array_merge($this->texts, $messages); - } + ob_end_clean(); - ob_end_clean(); + $_SESSION['language'] = $lang; + } - $_SESSION['language'] = $lang; + // append additional texts (from plugin) + if (is_array($add) && !empty($add)) { + $this->texts += $add; + } } - // append additional texts (from plugin) - if (is_array($add) && !empty($add)) - $this->texts += $add; - } - - - /** - * Check the given string and return a valid language code - * - * @param string Language code - * @return string Valid language code - */ - protected function language_prop($lang) - { - static $rcube_languages, $rcube_language_aliases; - - // user HTTP_ACCEPT_LANGUAGE if no language is specified - if (empty($lang) || $lang == 'auto') { - $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); - $lang = str_replace('-', '_', $accept_langs[0]); - } - - if (empty($rcube_languages)) { - @include(INSTALL_PATH . 'program/localization/index.inc'); - } - // check if we have an alias for that language - if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { - $lang = $rcube_language_aliases[$lang]; + /** + * Check the given string and return a valid language code + * + * @param string Language code + * + * @return string Valid language code + */ + protected function language_prop($lang) + { + static $rcube_languages, $rcube_language_aliases; + + // user HTTP_ACCEPT_LANGUAGE if no language is specified + if (empty($lang) || $lang == 'auto') { + $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); + $lang = str_replace('-', '_', $accept_langs[0]); + } + + if (empty($rcube_languages)) { + @include(INSTALL_PATH . 'program/localization/index.inc'); + } + + // check if we have an alias for that language + if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { + $lang = $rcube_language_aliases[$lang]; + } + // try the first two chars + else if (!isset($rcube_languages[$lang])) { + $short = substr($lang, 0, 2); + + // check if we have an alias for the short language code + if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { + $lang = $rcube_language_aliases[$short]; + } + // expand 'nn' to 'nn_NN' + else if (!isset($rcube_languages[$short])) { + $lang = $short.'_'.strtoupper($short); + } + } + + if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { + $lang = 'en_US'; + } + + return $lang; } - // try the first two chars - else if (!isset($rcube_languages[$lang])) { - $short = substr($lang, 0, 2); - - // check if we have an alias for the short language code - if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { - $lang = $rcube_language_aliases[$short]; - } - // expand 'nn' to 'nn_NN' - else if (!isset($rcube_languages[$short])) { - $lang = $short.'_'.strtoupper($short); - } + + + /** + * Read directory program/localization and return a list of available languages + * + * @return array List of available localizations + */ + public function list_languages() + { + static $sa_languages = array(); + + if (!sizeof($sa_languages)) { + @include(INSTALL_PATH . 'program/localization/index.inc'); + + if ($dh = @opendir(INSTALL_PATH . 'program/localization')) { + while (($name = readdir($dh)) !== false) { + if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) { + continue; + } + + if ($label = $rcube_languages[$name]) { + $sa_languages[$name] = $label; + } + } + closedir($dh); + } + } + + return $sa_languages; } - if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { - $lang = 'en_US'; + + /** + * Encrypt using 3DES + * + * @param string $clear clear text input + * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 whether or not to base64_encode() the result before returning + * + * @return string encrypted text + */ + public function encrypt($clear, $key = 'des_key', $base64 = true) + { + if (!$clear) { + return ''; + } + + /*- + * Add a single canary byte to the end of the clear text, which + * will help find out how much of padding will need to be removed + * upon decryption; see http://php.net/mcrypt_generic#68082 + */ + $clear = pack("a*H2", $clear, "80"); + + if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) + ) { + $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); + mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $cipher = $iv . mcrypt_generic($td, $clear); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + } + else { + @include_once 'des.inc'; + + if (function_exists('des')) { + $des_iv_size = 8; + $iv = $this->create_iv($des_iv_size); + $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); + } + else { + self::raise_error(array( + 'code' => 500, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" + ), true, true); + } + } + + return $base64 ? base64_encode($cipher) : $cipher; } - return $lang; - } + /** + * Decrypt 3DES-encrypted string + * + * @param string $cipher encrypted text + * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 whether or not input is base64-encoded + * + * @return string decrypted text + */ + public function decrypt($cipher, $key = 'des_key', $base64 = true) + { + if (!$cipher) { + return ''; + } - /** - * Read directory program/localization and return a list of available languages - * - * @return array List of available localizations - */ - public function list_languages() - { - static $sa_languages = array(); + $cipher = $base64 ? base64_decode($cipher) : $cipher; - if (!sizeof($sa_languages)) { - @include(INSTALL_PATH . 'program/localization/index.inc'); + if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) + ) { + $iv_size = mcrypt_enc_get_iv_size($td); + $iv = substr($cipher, 0, $iv_size); - if ($dh = @opendir(INSTALL_PATH . 'program/localization')) { - while (($name = readdir($dh)) !== false) { - if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) - continue; + // session corruption? (#1485970) + if (strlen($iv) < $iv_size) { + return ''; + } - if ($label = $rcube_languages[$name]) - $sa_languages[$name] = $label; + $cipher = substr($cipher, $iv_size); + mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $clear = mdecrypt_generic($td, $cipher); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + } + else { + @include_once 'des.inc'; + + if (function_exists('des')) { + $des_iv_size = 8; + $iv = substr($cipher, 0, $des_iv_size); + $cipher = substr($cipher, $des_iv_size); + $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); + } + else { + self::raise_error(array( + 'code' => 500, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" + ), true, true); + } } - closedir($dh); - } + + /*- + * Trim PHP's padding and the canary byte; see note in + * rcube::encrypt() and http://php.net/mcrypt_generic#68082 + */ + $clear = substr(rtrim($clear, "\0"), 0, -1); + + return $clear; } - return $sa_languages; - } - - - /** - * Encrypt using 3DES - * - * @param string $clear clear text input - * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' - * @param boolean $base64 whether or not to base64_encode() the result before returning - * - * @return string encrypted text - */ - public function encrypt($clear, $key = 'des_key', $base64 = true) - { - if (!$clear) - return ''; - - /*- - * Add a single canary byte to the end of the clear text, which - * will help find out how much of padding will need to be removed - * upon decryption; see http://php.net/mcrypt_generic#68082 + + /** + * Generates encryption initialization vector (IV) + * + * @param int Vector size + * + * @return string Vector string */ - $clear = pack("a*H2", $clear, "80"); - - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) { - $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); - $cipher = $iv . mcrypt_generic($td, $clear); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - } - else { - @include_once 'des.inc'; - - if (function_exists('des')) { - $des_iv_size = 8; - $iv = $this->create_iv($des_iv_size); - $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); - } - else { - self::raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" - ), true, true); - } + private function create_iv($size) + { + // mcrypt_create_iv() can be slow when system lacks entrophy + // we'll generate IV vector manually + $iv = ''; + for ($i = 0; $i < $size; $i++) { + $iv .= chr(mt_rand(0, 255)); + } + + return $iv; } - return $base64 ? base64_encode($cipher) : $cipher; - } - - /** - * Decrypt 3DES-encrypted string - * - * @param string $cipher encrypted text - * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' - * @param boolean $base64 whether or not input is base64-encoded - * - * @return string decrypted text - */ - public function decrypt($cipher, $key = 'des_key', $base64 = true) - { - if (!$cipher) - return ''; - - $cipher = $base64 ? base64_decode($cipher) : $cipher; - - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) { - $iv_size = mcrypt_enc_get_iv_size($td); - $iv = substr($cipher, 0, $iv_size); - - // session corruption? (#1485970) - if (strlen($iv) < $iv_size) - return ''; - $cipher = substr($cipher, $iv_size); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); - $clear = mdecrypt_generic($td, $cipher); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - } - else { - @include_once 'des.inc'; - - if (function_exists('des')) { - $des_iv_size = 8; - $iv = substr($cipher, 0, $des_iv_size); - $cipher = substr($cipher, $des_iv_size); - $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); - } - else { - self::raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" - ), true, true); - } + /** + * Build a valid URL to this instance of Roundcube + * + * @param mixed Either a string with the action or url parameters as key-value pairs + * @return string Valid application URL + */ + public function url($p) + { + // STUB: should be overloaded by the application + return ''; } - /*- - * Trim PHP's padding and the canary byte; see note in - * rcube::encrypt() and http://php.net/mcrypt_generic#68082 + + /** + * Function to be executed in script shutdown + * Registered with register_shutdown_function() */ - $clear = substr(rtrim($clear, "\0"), 0, -1); - - return $clear; - } - - /** - * Generates encryption initialization vector (IV) - * - * @param int Vector size - * @return string Vector string - */ - private function create_iv($size) - { - // mcrypt_create_iv() can be slow when system lacks entrophy - // we'll generate IV vector manually - $iv = ''; - for ($i = 0; $i < $size; $i++) - $iv .= chr(mt_rand(0, 255)); - return $iv; - } - - - /** - * Build a valid URL to this instance of Roundcube - * - * @param mixed Either a string with the action or url parameters as key-value pairs - * @return string Valid application URL - */ - public function url($p) - { - // STUB: should be overloaded by the application - return ''; - } - - - /** - * Function to be executed in script shutdown - * Registered with register_shutdown_function() - */ - public function shutdown() - { - foreach ($this->shutdown_functions as $function) - call_user_func($function); - - if (is_object($this->smtp)) - $this->smtp->disconnect(); - - foreach ($this->caches as $cache) { - if (is_object($cache)) - $cache->close(); - } + public function shutdown() + { + foreach ($this->shutdown_functions as $function) { + call_user_func($function); + } + + if (is_object($this->smtp)) { + $this->smtp->disconnect(); + } + + foreach ($this->caches as $cache) { + if (is_object($cache)) { + $cache->close(); + } + } - if (is_object($this->storage)) { - if ($this->expunge_cache) - $this->storage->expunge_cache(); - $this->storage->close(); + if (is_object($this->storage)) { + if ($this->expunge_cache) { + $this->storage->expunge_cache(); + } + $this->storage->close(); + } } - } - - - /** - * 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; - } - - - /** - * Construct shell command, execute it and return output as string. - * Keywords {keyword} are replaced with arguments - * - * @param $cmd Format string with {keywords} to be replaced - * @param $values (zero, one or more arrays can be passed) - * @return output of command. shell errors not detectable - */ - public static function exec(/* $cmd, $values1 = array(), ... */) - { - $args = func_get_args(); - $cmd = array_shift($args); - $values = $replacements = array(); - - // merge values into one array - foreach ($args as $arg) - $values += (array)$arg; - - preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); - foreach ($matches as $tags) { - list(, $tag, $option, $key) = $tags; - $parts = array(); - - if ($option) { - foreach ((array)$values["-$key"] as $key => $value) { - if ($value === true || $value === false || $value === null) - $parts[] = $value ? $key : ""; - else foreach ((array)$value as $val) - $parts[] = "$key " . escapeshellarg($val); - } - } - else { - foreach ((array)$values[$key] as $value) - $parts[] = escapeshellarg($value); - } - - $replacements[$tag] = join(" ", $parts); + + + /** + * 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; } - // use strtr behaviour of going through source string once - $cmd = strtr($cmd, $replacements); - return (string)shell_exec($cmd); - } + /** + * Construct shell command, execute it and return output as string. + * Keywords {keyword} are replaced with arguments + * + * @param $cmd Format string with {keywords} to be replaced + * @param $values (zero, one or more arrays can be passed) + * + * @return output of command. shell errors not detectable + */ + public static function exec(/* $cmd, $values1 = array(), ... */) + { + $args = func_get_args(); + $cmd = array_shift($args); + $values = $replacements = array(); + + // merge values into one array + foreach ($args as $arg) { + $values += (array)$arg; + } + + preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); + foreach ($matches as $tags) { + list(, $tag, $option, $key) = $tags; + $parts = array(); + + if ($option) { + foreach ((array)$values["-$key"] as $key => $value) { + if ($value === true || $value === false || $value === null) { + $parts[] = $value ? $key : ""; + } + else { + foreach ((array)$value as $val) { + $parts[] = "$key " . escapeshellarg($val); + } + } + } + } + else { + foreach ((array)$values[$key] as $value) { + $parts[] = escapeshellarg($value); + } + } + + $replacements[$tag] = join(" ", $parts); + } + + // use strtr behaviour of going through source string once + $cmd = strtr($cmd, $replacements); + + return (string)shell_exec($cmd); + } /** diff --git a/program/include/rcube_bc.inc b/program/include/rcube_bc.inc index 1932f86e2..1894873e6 100644 --- a/program/include/rcube_bc.inc +++ b/program/include/rcube_bc.inc @@ -38,11 +38,6 @@ function get_table_name($table) return rcmail::get_instance()->db->table_name($table); } -function get_sequence_name($sequence) -{ - return rcmail::get_instance()->db->sequence_name($sequence); -} - function rcube_label($p, $domain=null) { return rcmail::get_instance()->gettext($p, $domain); diff --git a/program/include/rcube_browser.php b/program/include/rcube_browser.php index 06033e036..7cfae709d 100644 --- a/program/include/rcube_browser.php +++ b/program/include/rcube_browser.php @@ -20,8 +20,6 @@ */ /** - * rcube_browser - * * Provide details about the client's browser based on the User-Agent header * * @package Core diff --git a/program/include/rcube_charset.php b/program/include/rcube_charset.php index 380d14978..1740a6096 100644 --- a/program/include/rcube_charset.php +++ b/program/include/rcube_charset.php @@ -181,6 +181,12 @@ class rcube_charset $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : self::parse_charset($to); $from = self::parse_charset($from); + // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654) + // In that case we can just skip the conversion (use UTF-8) + if ($from == 'UTF-16' && !preg_match('/[^\x00-\x7F]/', $str)) { + $from = 'UTF-8'; + } + if ($from == $to || empty($str) || empty($from)) { return $str; } diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index e2997906c..41acc80dd 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -42,6 +42,7 @@ class rcube_config 'default_folders' => 'default_imap_folders', 'mail_pagesize' => 'pagesize', 'addressbook_pagesize' => 'pagesize', + 'reply_mode' => 'top_posting', ); diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 5dd9c1250..66b5c4bd6 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -2222,6 +2222,10 @@ class rcube_imap extends rcube_storage $folder = $this->folder; } + if (!$this->check_connection()) { + return false; + } + // make sure folder exists if ($this->folder_exists($folder)) { if ($is_file) { diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php index a061a1f6e..f36ace0eb 100644 --- a/program/include/rcube_imap_cache.php +++ b/program/include/rcube_imap_cache.php @@ -5,7 +5,7 @@ | program/include/rcube_imap_cache.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2011, The Roundcube Dev Team | + | Copyright (C) 2005-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -350,11 +350,11 @@ class rcube_imap_cache function get_message($mailbox, $uid, $update = true, $cache = true) { // Check internal cache - if ($this->icache['message'] - && $this->icache['message']['mailbox'] == $mailbox - && $this->icache['message']['object']->uid == $uid + if ($this->icache['__message'] + && $this->icache['__message']['mailbox'] == $mailbox + && $this->icache['__message']['object']->uid == $uid ) { - return $this->icache['message']['object']; + return $this->icache['__message']['object']; } $sql_result = $this->db->query( @@ -386,7 +386,7 @@ class rcube_imap_cache // Save current message from internal cache $this->save_icache(); - $this->icache['message'] = array( + $this->icache['__message'] = array( 'object' => $message, 'mailbox' => $mailbox, 'exists' => $found, @@ -459,20 +459,28 @@ class rcube_imap_cache */ function change_flag($mailbox, $uids, $flag, $enabled = false) { + if (empty($uids)) { + return; + } + $flag = strtoupper($flag); $idx = (int) array_search($flag, $this->flags); + $uids = (array) $uids; if (!$idx) { return; } // Internal cache update - if ($uids && count($uids) == 1 && ($uid = current($uids)) - && ($message = $this->icache['message']) - && $message['mailbox'] == $mailbox && $message['object']->uid == $uid + if (($message = $this->icache['__message']) + && $message['mailbox'] === $mailbox + && in_array($message['object']->uid, $uids) ) { $message['object']->flags[$flag] = $enabled; - return; + + if (count($uids) == 1) { + return; + } } $this->db->query( @@ -481,7 +489,7 @@ class rcube_imap_cache .", flags = flags ".($enabled ? "+ $idx" : "- $idx") ." WHERE user_id = ?" ." AND mailbox = ?" - .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : "") + .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "") ." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"), $this->userid, $mailbox); } @@ -503,10 +511,11 @@ class rcube_imap_cache } else { // Remove the message from internal cache - if (!empty($uids) && !is_array($uids) && ($message = $this->icache['message']) - && $message['mailbox'] == $mailbox && $message['object']->uid == $uids + if (!empty($uids) && ($message = $this->icache['__message']) + && $message['mailbox'] === $mailbox + && in_array($message['object']->uid, (array)$uids) ) { - $this->icache['message'] = null; + $this->icache['__message'] = null; } $this->db->query( @@ -608,13 +617,13 @@ class rcube_imap_cache // get expiration timestamp $ts = get_offset_time($ttl, -1); - $this->db->query("DELETE FROM ".get_table_name('cache_messages') + $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages') ." WHERE changed < " . $this->db->fromunixtime($ts)); - $this->db->query("DELETE FROM ".get_table_name('cache_index') + $this->db->query("DELETE FROM ".$this->db->table_name('cache_index') ." WHERE changed < " . $this->db->fromunixtime($ts)); - $this->db->query("DELETE FROM ".get_table_name('cache_thread') + $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread') ." WHERE changed < " . $this->db->fromunixtime($ts)); } @@ -763,6 +772,11 @@ class rcube_imap_cache $object = $index['object']; $is_thread = is_a($object, 'rcube_result_thread'); + // sanity check + if (empty($object)) { + return false; + } + // Get mailbox data (UIDVALIDITY, counters, etc.) for status check $mbox_data = $this->imap->folder_data($mailbox); @@ -1078,7 +1092,7 @@ class rcube_imap_cache private function save_icache() { // Save current message from internal cache - if ($message = $this->icache['message']) { + if ($message = $this->icache['__message']) { // clean up some object's data $object = $this->message_object_prepare($message['object']); @@ -1089,7 +1103,7 @@ class rcube_imap_cache $this->add_message($message['mailbox'], $object, !$message['exists']); } - $this->icache['message']['md5sum'] = $md5sum; + $this->icache['__message']['md5sum'] = $md5sum; } } diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index 915a11aad..c3cfabc3a 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -2538,7 +2538,7 @@ class rcube_imap_generic { unset($this->data['APPENDUID']); - if (!$mailbox) { + if ($mailbox === null || $mailbox === '') { return false; } @@ -2603,7 +2603,7 @@ class rcube_imap_generic { unset($this->data['APPENDUID']); - if (!$mailbox) { + if ($mailbox === null || $mailbox === '') { return false; } @@ -2612,6 +2612,7 @@ class rcube_imap_generic if (file_exists(realpath($path))) { $in_fp = fopen($path, 'r'); } + if (!$in_fp) { $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading"); return false; diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index dbab0fd06..ad2ccddeb 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -139,6 +139,11 @@ class rcube_ldap extends rcube_addressbook unset($this->coltypes[$childcol]); // remove address child col from global coltypes list } } + + // at least one address type must be specified + if (empty($this->coltypes['address']['subtypes'])) { + $this->coltypes['address']['subtypes'] = array('home'); + } } else if ($this->coltypes['address']) { $this->coltypes['address'] += array('type' => 'textarea', 'childs' => null, 'size' => 40); diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index 9d36acf38..6af1d0133 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -50,13 +50,14 @@ class rcube_message */ private $mime; private $opt = array(); - private $inline_parts = array(); private $parse_alternative = false; - public $uid = null; + public $uid; + public $folder; public $headers; public $parts = array(); public $mime_parts = array(); + public $inline_parts = array(); public $attachments = array(); public $subject = ''; public $sender = null; @@ -68,17 +69,22 @@ class rcube_message * * Provide a uid, and parse message structure. * - * @param string $uid The message UID. + * @param string $uid The message UID. + * @param string $folder Folder name * * @see self::$app, self::$storage, self::$opt, self::$parts */ - function __construct($uid) + function __construct($uid, $folder = null) { $this->uid = $uid; $this->app = rcube::get_instance(); $this->storage = $this->app->get_storage(); + $this->folder = strlen($folder) ? $folder : $this->storage->get_folder(); $this->storage->set_options(array('all_headers' => true)); + // Set current folder + $this->storage->set_folder($this->folder); + $this->headers = $this->storage->get_message($uid); if (!$this->headers) @@ -179,10 +185,12 @@ class rcube_message } return $fp ? true : $part->body; } + // get from IMAP + $this->storage->set_folder($this->folder); + return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv); - } else - return null; + } } @@ -637,8 +645,10 @@ class rcube_message function tnef_decode(&$part) { // @TODO: attachment may be huge, hadle it via file - if (!isset($part->body)) + if (!isset($part->body)) { + $this->storage->set_folder($this->folder); $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part); + } $parts = array(); $tnef = new tnef_decoder; @@ -673,8 +683,10 @@ class rcube_message function uu_decode(&$part) { // @TODO: messages may be huge, hadle body via file - if (!isset($part->body)) + if (!isset($part->body)) { + $this->storage->set_folder($this->folder); $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part); + } $parts = array(); // FIXME: line length is max.65? diff --git a/program/include/rcube_mime.php b/program/include/rcube_mime.php index e1f736a78..d8e04a97c 100644 --- a/program/include/rcube_mime.php +++ b/program/include/rcube_mime.php @@ -541,10 +541,10 @@ class rcube_mime $prefix = $regs[0]; $level = strlen($prefix); $line = rtrim(substr($line, $level)); - $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix "); + $line = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix "); } else if ($line) { - $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n"); + $line = self::wordwrap(rtrim($line), $length - 2, " \r\n"); // space-stuffing $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line); } diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php index 0a8f0e364..2743e7705 100644 --- a/program/include/rcube_output_html.php +++ b/program/include/rcube_output_html.php @@ -67,6 +67,11 @@ class rcube_output_html extends rcube_output $this->set_env('task', $task); $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin')); + // add cookie info + $this->set_env('cookie_domain', ini_get('session.cookie_domain')); + $this->set_env('cookie_path', ini_get('session.cookie_path')); + $this->set_env('cookie_secure', ini_get('session.cookie_secure')); + // load the correct skin (in case user-defined) $skin = $this->config->get('skin'); $this->set_skin($skin); @@ -395,7 +400,7 @@ class rcube_output_html extends rcube_output 'line' => __LINE__, 'file' => __FILE__, 'message' => 'Error loading template for '.$realname - ), true, true); + ), true, $write); return false; } @@ -693,6 +698,11 @@ class rcube_output_html extends rcube_output } break; + // frame + case 'frame': + return $this->frame($attrib); + break; + // show a label case 'label': if ($attrib['name'] || $attrib['command']) { @@ -1270,6 +1280,30 @@ class rcube_output_html extends rcube_output } + /** + * Returns iframe object, registers some related env variables + * + * @param array $attrib HTML attributes + * + * @return string IFRAME element + */ + public function frame($attrib) + { + if (!$attrib['id']) { + $attrib['id'] = 'rcmframe'; + } + + if (!$attrib['name']) { + $attrib['name'] = $attrib['id']; + } + + $this->set_env('contentframe', $attrib['id']); + $this->set_env('blankpage', $attrib['src'] ? $this->abs_url($attrib['src']) : 'program/resources/blank.gif'); + + return html::iframe($attrib); + } + + /* ************* common functions delivering gui objects ************** */ diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php index b1ec32a8a..c1035733b 100644 --- a/program/include/rcube_plugin.php +++ b/program/include/rcube_plugin.php @@ -336,7 +336,7 @@ abstract class rcube_plugin public function local_skin_path() { $rcmail = rcube::get_instance(); - foreach (array($rcmail->config->get('skin'),'default') as $skin) { + foreach (array($rcmail->config->get('skin'), 'larry') as $skin) { $skin_path = 'skins/' . $skin; if (is_dir(realpath(slashify($this->home) . $skin_path))) break; diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index 85f278432..c15305c08 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -108,11 +108,11 @@ function slashify($str) /** - * Remove slash at the end of the string + * Remove slashes at the end of the string */ function unslashify($str) { - return preg_replace('/\/$/', '', $str); + return preg_replace('/\/+$/', '', $str); } @@ -231,7 +231,7 @@ function array_keys_recursive($array) { $keys = array(); - if (!empty($array)) { + if (!empty($array) && is_array($array)) { foreach ($array as $key => $child) { $keys[] = $key; foreach (array_keys_recursive($child) as $val) { @@ -255,7 +255,7 @@ function asciiwords($str, $css_id = false, $replace_with = '') /** - * Remove single and double quotes from given string + * Remove single and double quotes from a given string * * @param string Input value * @@ -306,6 +306,29 @@ function format_email_recipient($email, $name = '') /** + * Format e-mail address + * + * @param string $email E-mail address + * + * @return string Formatted e-mail address + */ +function format_email($email) +{ + $email = trim($email); + $parts = explode('@', $email); + $count = count($parts); + + if ($count > 1) { + $parts[$count-1] = mb_strtolower($parts[$count-1]); + + $email = implode('@', $parts); + } + + return $email; +} + + +/** * mbstring replacement functions */ if (!extension_loaded('mbstring')) @@ -399,7 +422,6 @@ function rcube_autoload($classname) { $filename = preg_replace( array( - '/MDB2_(.+)/', '/Mail_(.+)/', '/Net_(.+)/', '/Auth_(.+)/', @@ -408,7 +430,6 @@ function rcube_autoload($classname) ), array( 'Mail/\\1', - 'Mail/\\1', 'Net/\\1', 'Auth/\\1', 'html', diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php index 768a26d73..f83e24041 100644 --- a/program/include/rcube_storage.php +++ b/program/include/rcube_storage.php @@ -195,7 +195,7 @@ abstract class rcube_storage */ public function set_folder($folder) { - if ($this->folder == $folder) { + if ($this->folder === $folder) { return; } diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php index d1a8315ec..23bf556e4 100644 --- a/program/include/rcube_utils.php +++ b/program/include/rcube_utils.php @@ -92,9 +92,9 @@ class rcube_utils return false; } - // Check domain part - if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) { - return true; // IP address + // Validate domain part + if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) { + return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address } else { // If not an IP address @@ -110,6 +110,11 @@ class rcube_utils } } + // last domain part + if (preg_match('/[^a-zA-Z]/', array_pop($domain_array))) { + return false; + } + $rcube = rcube::get_instance(); if (!$dns_check || !$rcube->config->get('email_dns_check')) { @@ -141,6 +146,52 @@ class rcube_utils return false; } + + /** + * Validates IPv4 or IPv6 address + * + * @param string $ip IP address in v4 or v6 format + * + * @return bool True if the address is valid + */ + public static function check_ip($ip) + { + // IPv6, but there's no build-in IPv6 support + if (strpos($ip, ':') !== false && !defined('AF_INET6')) { + $parts = explode(':', $domain_part); + $count = count($parts); + + if ($count > 8 || $count < 2) { + return false; + } + + foreach ($parts as $idx => $part) { + $length = strlen($part); + if (!$length) { + // there can be only one :: + if ($found_empty) { + return false; + } + $found_empty = true; + } + // last part can be an IPv4 address + else if ($idx == $count - 1) { + if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) { + return @inet_pton($part) !== false; + } + } + else if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) { + return false; + } + } + + return true; + } + + return @inet_pton($ip) !== false; + } + + /** * Check whether the HTTP referer matches the current request * @@ -149,8 +200,8 @@ class rcube_utils public static function check_referer() { $uri = parse_url($_SERVER['REQUEST_URI']); - $referer = parse_url(rcube_request_header('Referer')); - return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path']; + $referer = parse_url(self::request_header('Referer')); + return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path']; } diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php index 37cd3ab26..2bfd474c6 100644 --- a/program/include/rcube_vcard.php +++ b/program/include/rcube_vcard.php @@ -313,7 +313,7 @@ class rcube_vcard case 'birthday': case 'anniversary': - if (($val = rcube_strtotime($value)) && ($fn = self::$fieldmap[$field])) + if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); break; diff --git a/program/js/app.js b/program/js/app.js index e8bb6c1a7..48de21764 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -976,7 +976,7 @@ function rcube_webmail() // do reply-list, when list is detected and popup menu wasn't used url._all = (!props && this.commands['reply-list'] ? 'list' : 'all'); else if (command == 'reply-list') - url._all = list; + url._all = 'list'; this.goto_url('compose', url, true); } @@ -1447,29 +1447,21 @@ function rcube_webmail() this.doc_mouse_up = function(e) { - var model, list, li, id; + var model, list, id; // ignore event if jquery UI dialog is open if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length) return; - if (list = this.message_list) { - if (!rcube_mouse_is_over(e, list.list.parentNode)) - list.blur(); - else - list.focus(); + if (list = this.message_list) model = this.env.mailboxes; - } - else if (list = this.contact_list) { - if (!rcube_mouse_is_over(e, list.list.parentNode)) - list.blur(); - else - list.focus(); + else if (list = this.contact_list) model = this.env.contactfolders; - } - else if (this.ksearch_value) { + else if (this.ksearch_value) this.ksearch_blur(); - } + + if (list && !rcube_mouse_is_over(e, list.list.parentNode)) + list.blur(); // handle mouse release when dragging if (this.drag_active && model && this.env.last_folder_target) { @@ -1546,14 +1538,17 @@ function rcube_webmail() if (list.multi_selecting || !this.env.contentframe) return; - if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) { - if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) { - if (this.preview_timer) - clearTimeout(this.preview_timer); - if (this.preview_read_timer) - clearTimeout(this.preview_read_timer); - this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200); - } + if (list.get_single_selection()) + return; + + var win = this.get_frame_window(this.env.contentframe); + + if (win && win.location.href.indexOf(this.env.blankpage)>=0) { + if (this.preview_timer) + clearTimeout(this.preview_timer); + if (this.preview_read_timer) + clearTimeout(this.preview_read_timer); + this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200); } }; @@ -1918,12 +1913,12 @@ function rcube_webmail() if (!id) return; - var target = window, + var win, target = window, action = preview ? 'preview': 'show', url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox); - if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { - target = window.frames[this.env.contentframe]; + if (preview && (win = this.get_frame_window(this.env.contentframe))) { + target = win; url += '&_framed=1'; } @@ -1960,20 +1955,37 @@ function rcube_webmail() this.show_contentframe = function(show) { - var frm, win; - if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) { - if (!show && (win = window.frames[this.env.contentframe])) { + var frame, win, name = this.env.contentframe; + + if (name && (frame = this.get_frame_element(name))) { + if (!show && (win = this.get_frame_window(name))) { if (win.location && win.location.href.indexOf(this.env.blankpage)<0) win.location.href = this.env.blankpage; } else if (!bw.safari && !bw.konq) - frm[show ? 'show' : 'hide'](); - } + $(frame)[show ? 'show' : 'hide'](); + } if (!show && this.busy) this.set_busy(false, null, this.env.frame_lock); }; + this.get_frame_element = function(id) + { + var frame; + + if (id && (frame = document.getElementById(id))) + return frame; + }; + + this.get_frame_window = function(id) + { + var frame = this.get_frame_element(id); + + if (frame && frame.name && window.frames) + return window.frames[frame.name]; + }; + this.lock_frame = function() { if (!this.env.frame_lock) @@ -2017,7 +2029,7 @@ function rcube_webmail() // list messages of a specific mailbox this.list_mailbox = function(mbox, page, sort, url) { - var target = window; + var win, target = window; if (typeof url != 'object') url = {}; @@ -2056,8 +2068,8 @@ function rcube_webmail() return; } - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { - target = window.frames[this.env.contentframe]; + if (win = this.get_frame_window(this.env.contentframe)) { + target = win; url._framed = 1; } @@ -2652,34 +2664,37 @@ function rcube_webmail() // set a specific flag to one or more messages this.mark_message = function(flag, uid) { - var a_uids = [], r_uids = [], len, n, id, - selection = this.message_list ? this.message_list.get_selection() : []; + var a_uids = [], r_uids = [], len, n, id, selection, + list = this.message_list; if (uid) a_uids[0] = uid; else if (this.env.uid) a_uids[0] = this.env.uid; - else if (this.message_list) { + else if (list) { + selection = list.get_selection(); for (n=0, len=selection.length; n<len; n++) { a_uids.push(selection[n]); } } - if (!this.message_list) + if (!list) r_uids = a_uids; - else + else { + list.focus(); for (n=0, len=a_uids.length; n<len; n++) { id = a_uids[n]; - if ((flag=='read' && this.message_list.rows[id].unread) - || (flag=='unread' && !this.message_list.rows[id].unread) - || (flag=='delete' && !this.message_list.rows[id].deleted) - || (flag=='undelete' && this.message_list.rows[id].deleted) - || (flag=='flagged' && !this.message_list.rows[id].flagged) - || (flag=='unflagged' && this.message_list.rows[id].flagged)) + if ((flag=='read' && list.rows[id].unread) + || (flag=='unread' && !list.rows[id].unread) + || (flag=='delete' && !list.rows[id].deleted) + || (flag=='undelete' && list.rows[id].deleted) + || (flag=='flagged' && !list.rows[id].flagged) + || (flag=='unflagged' && list.rows[id].flagged)) { r_uids.push(id); } } + } // nothing to do if (!r_uids.length && !this.select_all_mode) @@ -4020,7 +4035,7 @@ function rcube_webmail() this.list_contacts = function(src, group, page) { - var folder, url = {}, + var win, folder, url = {}, target = window; if (!src) @@ -4052,8 +4067,8 @@ function rcube_webmail() return; } - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { - target = window.frames[this.env.contentframe]; + if (win = this.get_frame_window(this.env.contentframe)) { + target = win; url._framed = 1; } @@ -4109,11 +4124,11 @@ function rcube_webmail() // load contact record this.load_contact = function(cid, action, framed) { - var url = {}, target = window; + var win, url = {}, target = window; - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { + if (win = this.get_frame_window(this.env.contentframe)) { url._framed = 1; - target = window.frames[this.env.contentframe]; + target = win; this.show_contentframe(true); // load dummy content @@ -4731,11 +4746,11 @@ function rcube_webmail() // load advanced search page this.advanced_search = function() { - var url = {_form: 1, _action: 'search'}, target = window; + var win, url = {_form: 1, _action: 'search'}, target = window; - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { + if (win = this.get_frame_window(this.env.contentframe)) { url._framed = 1; - target = window.frames[this.env.contentframe]; + target = win; this.contact_list.clear_selection(); } @@ -4857,13 +4872,13 @@ function rcube_webmail() // preferences section select and load options frame this.section_select = function(list) { - var id = list.get_single_selection(), target = window, + var win, id = list.get_single_selection(), target = window, url = {_action: 'edit-prefs', _section: id}; if (id) { - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { + if (win = this.get_frame_window(this.env.contentframe)) { url._framed = 1; - target = window.frames[this.env.contentframe]; + target = win; } this.location_href(url, target, true); } @@ -4886,13 +4901,12 @@ function rcube_webmail() if (action == 'edit-identity' && (!id || id == this.env.iid)) return false; - var target = window, + var win, target = window, url = {_action: action, _iid: id}; - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { + if (win = this.get_frame_window(this.env.contentframe)) { url._framed = 1; - target = window.frames[this.env.contentframe]; - document.getElementById(this.env.contentframe).style.visibility = 'inherit'; + target = win; } if (action && (id || action == 'add-identity')) { @@ -5268,14 +5282,14 @@ function rcube_webmail() // when user select a folder in manager this.show_folder = function(folder, path, force) { - var target = window, + var win, target = window, url = '&_action=edit-folder&_mbox='+urlencode(folder); if (path) url += '&_path='+urlencode(path); - if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { - target = window.frames[this.env.contentframe]; + if (win = this.get_frame_window(this.env.contentframe)) { + target = win; url += '&_framed=1'; } @@ -6585,6 +6599,12 @@ function rcube_webmail() return 0; }; + // Cookie setter + this.set_cookie = function(name, value, expires) + { + setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure); + } + } // end object rcube_webmail @@ -6615,6 +6635,8 @@ rcube_webmail.long_subject_title_ie = function(elem, indent) } }; +rcube_webmail.prototype.get_cookie = getCookie; + // copy event engine prototype rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; diff --git a/program/js/common.js b/program/js/common.js index fdef3453e..f9e945c05 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -494,12 +494,15 @@ function rcube_check_email(input, inline) atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+', quoted_pair = '\\x5c[\\x00-\\x7f]', quoted_string = '\\x22('+qtext+'|'+quoted_pair+')*\\x22', + ipv4 = '\\[(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\\]', + ipv6 = '\\[IPv6:[0-9a-f:.]+\\]', + ip_addr = '(' + ipv4 + ')|(' + ipv6 + ')', // Use simplified domain matching, because we need to allow Unicode characters here // So, e-mail address should be validated also on server side after idn_to_ascii() use //domain_literal = '\\x5b('+dtext+'|'+quoted_pair+')*\\x5d', //sub_domain = '('+atom+'|'+domain_literal+')', // allow punycode/unicode top-level domain - domain = '([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})', + domain = '(('+ip_addr+')|(([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})))', // ICANN e-mail test (http://idn.icann.org/E-mail_test) icann_domains = [ '\\u0645\\u062b\\u0627\\u0644\\x2e\\u0625\\u062e\\u062a\\u0628\\u0627\\u0631', @@ -527,7 +530,6 @@ function rcube_check_email(input, inline) return false; }; - // recursively copy an object function rcube_clone_object(obj) { @@ -635,6 +637,7 @@ function getCookie(name) return unescape(dc.substring(begin + prefix.length, end)); }; +// deprecated aliases, to be removed, use rcmail.set_cookie/rcmail.get_cookie roundcube_browser.prototype.set_cookie = setCookie; roundcube_browser.prototype.get_cookie = getCookie; diff --git a/program/js/googiespell.js b/program/js/googiespell.js index 9f1b41bb2..478858bac 100644 --- a/program/js/googiespell.js +++ b/program/js/googiespell.js @@ -25,7 +25,7 @@ var GOOGIE_CUR_LANG, function GoogieSpell(img_dir, server_url, has_dict) { var ref = this, - cookie_value = getCookie('language'); + cookie_value = rcmail.get_cookie('language'); GOOGIE_CUR_LANG = cookie_value != null ? cookie_value : GOOGIE_DEFAULT_LANG; @@ -150,7 +150,7 @@ this.setCurrentLanguage = function(lan_code) //Set cookie var now = new Date(); now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000); - setCookie('language', lan_code, now); + rcmail.set_cookie('language', lan_code, now); }; this.setForceWidthHeight = function(width, height) diff --git a/program/js/list.js b/program/js/list.js index e84124b7c..1457382a4 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -231,8 +231,8 @@ focus: function(e) } } - // Un-focus already focused elements - $(document.activeElement).blur(); + // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) + $(':focus:not(body)').blur(); $('iframe').each(function() { this.blur(); }); if (e || (e = window.event)) diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 94bae1974..9882c19b5 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -1,7 +1,6 @@ <?php /* - +-----------------------------------------------------------------------+ | language/en_US/labels.inc | | | @@ -15,9 +14,6 @@ +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | +-----------------------------------------------------------------------+ - - @version $Id$ - */ $labels = array(); @@ -163,6 +159,7 @@ $labels['unread'] = 'Unread'; $labels['flagged'] = 'Flagged'; $labels['unanswered'] = 'Unanswered'; $labels['deleted'] = 'Deleted'; +$labels['undeleted'] = 'Not deleted'; $labels['invert'] = 'Invert'; $labels['filter'] = 'Filter'; $labels['list'] = 'List'; @@ -384,7 +381,8 @@ $labels['pagesize'] = 'Rows per page'; $labels['signature'] = 'Signature'; $labels['dstactive'] = 'Daylight saving time'; $labels['htmleditor'] = 'Compose HTML messages'; -$labels['htmlonreply'] = 'on reply to HTML message only'; +$labels['htmlonreply'] = 'on reply to HTML message'; +$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message'; $labels['htmlsignature'] = 'HTML signature'; $labels['previewpane'] = 'Show preview pane'; $labels['skin'] = 'Interface skin'; @@ -432,8 +430,9 @@ $labels['maintenance'] = 'Maintenance'; $labels['newmessage'] = 'New Message'; $labels['signatureoptions'] = 'Signature Options'; $labels['whenreplying'] = 'When replying'; -$labels['replytopposting'] = 'start new message above original'; -$labels['replybottomposting'] = 'start new message below original'; +$labels['replyempty'] = 'do not quote the original message'; +$labels['replytopposting'] = 'start new message above the quote'; +$labels['replybottomposting'] = 'start new message below the quote'; $labels['replyremovesignature'] = 'When replying remove original signature from message'; $labels['autoaddsignature'] = 'Automatically add signature'; $labels['newmessageonly'] = 'new message only'; diff --git a/program/localization/pl_PL/labels.inc b/program/localization/pl_PL/labels.inc index 92da1f6ef..d5ffcaa61 100644 --- a/program/localization/pl_PL/labels.inc +++ b/program/localization/pl_PL/labels.inc @@ -134,6 +134,7 @@ $labels['unread'] = 'Nieprzeczytane'; $labels['flagged'] = 'Oznaczone'; $labels['unanswered'] = 'Bez odpowiedzi'; $labels['deleted'] = 'Usunięte'; +$labels['undeleted'] = 'Nieusunięte'; $labels['invert'] = 'Odwróć'; $labels['filter'] = 'Filtr'; $labels['list'] = 'Lista'; diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc index 84a63aebc..850795c85 100644 --- a/program/steps/addressbook/export.inc +++ b/program/steps/addressbook/export.inc @@ -86,7 +86,7 @@ while ($result && ($row = $result->next())) { foreach ($row as $key => $values) { list($field, $section) = explode(':', $key); foreach ((array)$values as $value) { - if (is_array($value) || strlen($value)) + if (is_array($value) || @strlen($value)) $vcard->set($field, $value, strtoupper($section)); } } diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 1a1d244e1..29e12675e 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -139,7 +139,7 @@ if (!empty($CONFIG['drafts_mbox'])) { // set current mailbox in client environment $OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder()); $OUTPUT->set_env('sig_above', $RCMAIL->config->get('sig_above', false)); -$OUTPUT->set_env('top_posting', $RCMAIL->config->get('top_posting', false)); +$OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0); $OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ','))); // default font for HTML editor @@ -252,7 +252,8 @@ $MESSAGE->identities = $RCMAIL->user->list_identities(); if (count($MESSAGE->identities)) { foreach ($MESSAGE->identities as $idx => $ident) { - $email = mb_strtolower(rcube_idn_to_utf8($ident['email'])); + $ident['email'] = format_email($ident['email']); + $email = format_email(rcube_idn_to_utf8($ident['email'])); $MESSAGE->identities[$idx]['email_ascii'] = $ident['email']; $MESSAGE->identities[$idx]['ident'] = format_email_recipient($ident['email'], $ident['name']); @@ -277,7 +278,7 @@ else if (count($MESSAGE->identities)) { $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset); foreach ($a_to as $addr) { if (!empty($addr['mailto'])) { - $a_recipients[] = strtolower($addr['mailto']); + $a_recipients[] = format_email($addr['mailto']); $a_names[] = $addr['name']; } } @@ -286,7 +287,7 @@ else if (count($MESSAGE->identities)) { $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset); foreach ($a_cc as $addr) { if (!empty($addr['mailto'])) { - $a_recipients[] = strtolower($addr['mailto']); + $a_recipients[] = format_email($addr['mailto']); $a_names[] = $addr['name']; } } @@ -294,16 +295,12 @@ else if (count($MESSAGE->identities)) { } $from_idx = null; - $default_identity = null; + $found_idx = null; + $default_identity = 0; // default identity is always first on the list $return_path = $MESSAGE->headers->others['return-path']; // Select identity foreach ($MESSAGE->identities as $idx => $ident) { - // save default identity ID - if ($ident['standard']) { - $default_identity = $idx; - } - // use From header if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) { if ($MESSAGE->headers->from == $ident['ident']) { @@ -318,13 +315,22 @@ else if (count($MESSAGE->identities)) { } // use replied message recipients else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) { - // match identity name, prefer default identity - if ($from_idx === null || ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name'])) { + if ($found_idx === null) { + $found_idx = $idx; + } + // match identity name + if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) { $from_idx = $idx; + break; } } } + // If matching by name+address doesn't found any amtches, get first found address (identity) + if ($from_idx === null) { + $from_idx = $found_idx; + } + // Fallback using Return-Path if ($from_idx === null && $return_path) { foreach ($MESSAGE->identities as $idx => $ident) { @@ -335,12 +341,7 @@ else if (count($MESSAGE->identities)) { } } - // Still no ID, use default/first identity - if ($from_idx === null) { - $from_idx = $default_identity !== null ? $default_identity : key(reset($MESSAGE->identities)); - } - - $ident = $MESSAGE->identities[$from_idx]; + $ident = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity]; $from_id = $ident['identity_id']; $MESSAGE->compose['from_email'] = $ident['email']; @@ -433,7 +434,7 @@ foreach ($parts as $header) { if (empty($addr_part['mailto'])) continue; - $mailto = mb_strtolower(rcube_idn_to_utf8($addr_part['mailto'])); + $mailto = format_email(rcube_idn_to_utf8($addr_part['mailto'])); if (!in_array($mailto, $a_recipients) && ($header == 'to' || empty($MESSAGE->compose['from_email']) || $mailto != $MESSAGE->compose['from_email']) @@ -609,9 +610,12 @@ function rcmail_compose_editor_mode() $useHtml = $MESSAGE->has_html_part(false); } else if ($compose_mode == RCUBE_COMPOSE_REPLY) { - $useHtml = ($html_editor == 1 || ($html_editor == 2 && $MESSAGE->has_html_part(false))); + $useHtml = ($html_editor == 1 || ($html_editor >= 2 && $MESSAGE->has_html_part(false))); } - else { // RCUBE_COMPOSE_FORWARD or NEW + else if ($compose_mode == RCUBE_COMPOSE_FORWARD) { + $useHtml = ($html_editor == 1 || ($html_editor == 3 && $MESSAGE->has_html_part(false))); + } + else { $useHtml = ($html_editor == 1); } @@ -640,7 +644,7 @@ function rcmail_prepare_message_body() rcmail_write_forward_attachment($MESSAGE); } // reply/edit/draft/forward - else if ($compose_mode) { + else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || $RCMAIL->config->get('reply_mode') != -1)) { $isHtml = rcmail_compose_editor_mode(); if (!empty($MESSAGE->parts)) { @@ -905,8 +909,9 @@ function rcmail_create_reply_body($body, $bodyIsHtml) $prefix .= "\n"; $suffix = ''; - if ($RCMAIL->config->get('top_posting')) + if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting $prefix = "\n\n\n" . $prefix; + } } else { // save inline images to files @@ -920,7 +925,7 @@ function rcmail_create_reply_body($body, $bodyIsHtml) $prefix = '<p>' . Q($prefix) . "</p>\n"; $prefix .= '<blockquote>'; - if ($RCMAIL->config->get('top_posting')) { + if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting $prefix = '<br>' . $prefix; $suffix = '</blockquote>'; } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 3d65eacb1..8bf80a6ee 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -90,11 +90,13 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { // set current mailbox and some other vars in client environment $OUTPUT->set_env('mailbox', $mbox_name); $OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize()); - $OUTPUT->set_env('quota', $RCMAIL->storage->get_capability('QUOTA')); $OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter()); $OUTPUT->set_env('threading', $threading); $OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD')); $OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0)); + if ($RCMAIL->storage->get_capability('QUOTA')) { + $OUTPUT->set_env('quota', true); + } if ($CONFIG['delete_junk']) $OUTPUT->set_env('delete_junk', true); @@ -1053,12 +1055,17 @@ function rcmail_message_full_headers($attrib, $headers=NULL) global $OUTPUT; $html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), '')); - $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), ''); + + if (!get_boolean($attrib['no-switch'])) { + $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), ''); + } + + unset($attrib['no-switch']); $OUTPUT->add_gui_object('all_headers_row', 'all-headers'); $OUTPUT->add_gui_object('all_headers_box', 'headers-source'); - return html::div($attrib, $html); + return count($attrib) > 1 ? html::div($attrib, $html) : $html; } @@ -1712,8 +1719,10 @@ function rcmail_search_filter($attrib) $select_filter->add(rcube_label('unread'), 'UNSEEN'); $select_filter->add(rcube_label('flagged'), 'FLAGGED'); $select_filter->add(rcube_label('unanswered'), 'UNANSWERED'); - if (!$CONFIG['skip_deleted']) + if (!$CONFIG['skip_deleted']) { $select_filter->add(rcube_label('deleted'), 'DELETED'); + $select_filter->add(rcube_label('undeleted'), 'UNDELETED'); + } $select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1'); $select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2'); $select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5'); diff --git a/program/steps/mail/headers.inc b/program/steps/mail/headers.inc index 4d6627393..cad113f68 100644 --- a/program/steps/mail/headers.inc +++ b/program/steps/mail/headers.inc @@ -24,7 +24,8 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_POST)) $source = $RCMAIL->storage->get_raw_headers($uid); if ($source !== false) { - $source = htmlspecialchars(trim($source)); + $source = trim(rcube_charset::clean($source)); + $source = htmlspecialchars($source); $source = preg_replace( array( '/\n[\t\s]+/', diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 2691a6e26..6ca704998 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -44,8 +44,8 @@ if ($RCMAIL->action == 'subscribe') if ($result) { // Handle subscription of protected folder (#1487656) - if ($CONFIG['protect_default_folders'] == true - && in_array($mbox, $CONFIG['default_folders']) + if ($RCMAIL->config->get('protect_default_folders') + && in_array($mbox, (array)$RCMAIL->config->get('default_folders')) ) { $OUTPUT->command('disable_subscription', $mbox); } @@ -321,8 +321,8 @@ function rcube_subscription_form($attrib) } } } - // check if the folder is shared, then disable subscription option on it - if (!$disabled && $folder['virtual'] && !empty($namespace)) { + // check if the folder is shared, then disable subscription option on it (if not subscribed already) + if (!$disabled && !$subscribed && $folder['virtual'] && !empty($namespace)) { $tmp_ns = array_merge((array)$namespace['other'], (array)$namespace['shared']); foreach ($tmp_ns as $item) { if (strpos($folder['id'], $item[0]) === 0) { @@ -411,8 +411,10 @@ function rcmail_rename_folder($oldname, $newname) $OUTPUT->set_pagetitle(rcube_label('folders')); $OUTPUT->include_script('list.js'); -$OUTPUT->set_env('quota', $STORAGE->get_capability('QUOTA')); $OUTPUT->set_env('prefix_ns', $STORAGE->get_namespace('prefix')); +if ($STORAGE->get_capability('QUOTA')) { + $OUTPUT->set_env('quota', true); +} // add some labels to client $OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'folderdeleting', diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 3f5ef5390..59b4e3735 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -470,6 +470,7 @@ function rcmail_user_prefs($current=null) $select_htmleditor->add(rcube_label('never'), 0); $select_htmleditor->add(rcube_label('always'), 1); $select_htmleditor->add(rcube_label('htmlonreply'), 2); + $select_htmleditor->add(rcube_label('htmlonreplyandforward'), 3); $blocks['main']['options']['htmleditor'] = array( 'title' => html::label($field_id, Q(rcube_label('htmleditor'))), @@ -544,16 +545,17 @@ function rcmail_user_prefs($current=null) ); } - if (!isset($no_override['top_posting'])) { - $field_id = 'rcmfd_top_posting'; - $select_replymode = new html_select(array('name' => '_top_posting', 'id' => $field_id, - 'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex==0)")); + if (!isset($no_override['reply_mode'])) { + $field_id = 'rcmfd_reply_mode'; + $select_replymode = new html_select(array('name' => '_reply_mode', 'id' => $field_id, + 'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex<2)")); + $select_replymode->add(rcube_label('replyempty'), -1); $select_replymode->add(rcube_label('replybottomposting'), 0); $select_replymode->add(rcube_label('replytopposting'), 1); - $blocks['main']['options']['top_posting'] = array( + $blocks['main']['options']['reply_mode'] = array( 'title' => html::label($field_id, Q(rcube_label('whenreplying'))), - 'content' => $select_replymode->show($config['top_posting']?1:0), + 'content' => $select_replymode->show(intval($config['reply_mode'])), ); } @@ -597,7 +599,7 @@ function rcmail_user_prefs($current=null) if (!isset($no_override['sig_above'])) { $field_id = 'rcmfd_sig_above'; - $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => !$config['top_posting'])); + $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => $config['reply_mode'] < 1)); $select_sigabove->add(rcube_label('belowquote'), 0); $select_sigabove->add(rcube_label('abovequote'), 1); diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc index 09f76ac27..73cc5e4bf 100644 --- a/program/steps/settings/save_folder.inc +++ b/program/steps/settings/save_folder.inc @@ -80,7 +80,10 @@ if (!$error && strlen($path) && (!strlen($old_imap) || $old_imap != $name_imap)) } } -if (!$error) { +if ($error) { + $OUTPUT->command('display_message', $error, 'error'); +} +else { $folder['name'] = $name_imap; $folder['oldname'] = $old_imap; $folder['class'] = ''; diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc index 88fa5298a..dc149929e 100644 --- a/program/steps/settings/save_prefs.inc +++ b/program/steps/settings/save_prefs.inc @@ -82,9 +82,9 @@ switch ($CURR_SECTION) 'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']) ? TRUE : FALSE, 'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']) ? TRUE : FALSE, 'show_sig' => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1, - 'top_posting' => !empty($_POST['_top_posting']), + 'reply_mode' => isset($_POST['_reply_mode']) ? intval($_POST['_reply_mode']) : 0, 'strip_existing_sig' => isset($_POST['_strip_existing_sig']), - 'sig_above' => !empty($_POST['_sig_above']) && !empty($_POST['_top_posting']), + 'sig_above' => !empty($_POST['_sig_above']) && $_POST['_reply_mode'] < 1, 'default_font' => get_input_value('_default_font', RCUBE_INPUT_POST), 'forward_attachment' => !empty($_POST['_forward_attachment']), ); |