From ed1d212ae2daea5e4bd043417610177093e99f19 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 16 Jan 2016 03:03:51 -0500
Subject: [PATCH] Improved SVG cleanup code
---
program/include/rcmail.php | 398 +++++++++++++++++++++++++++++++++++++-------------------
1 files changed, 260 insertions(+), 138 deletions(-)
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index ed5fedb..bdf9405 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1,6 +1,6 @@
<?php
-/*
+/**
+-----------------------------------------------------------------------+
| program/include/rcmail.php |
| |
@@ -60,16 +60,18 @@
const ERROR_INVALID_REQUEST = 1;
const ERROR_INVALID_HOST = 2;
const ERROR_COOKIES_DISABLED = 3;
+ const ERROR_RATE_LIMIT = 4;
/**
* This implements the 'singleton' design pattern
*
- * @param string Environment name to run (e.g. live, dev, test)
+ * @param integer $mode Ignored rcube::get_instance() argument
+ * @param string $env Environment name to run (e.g. live, dev, test)
*
* @return rcmail The one and only instance
*/
- static function get_instance($env = '')
+ static function get_instance($mode = 0, $env = '')
{
if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
self::$instance = new rcmail($env);
@@ -93,6 +95,11 @@
$this->filename = $basename;
}
+ // load all configured plugins
+ $plugins = (array) $this->config->get('plugins', array());
+ $required_plugins = array('filesystem_attachments', 'jqueryui');
+ $this->plugins->load_plugins($plugins, $required_plugins);
+
// start session
$this->session_init();
@@ -106,26 +113,26 @@
// reset some session parameters when changing task
if ($this->task != 'utils') {
// we reset list page when switching to another task
- // but only to the main task interface - empty action (#1489076)
+ // but only to the main task interface - empty action (#1489076, #1490116)
// this will prevent from unintentional page reset on cross-task requests
if ($this->session && $_SESSION['task'] != $this->task && empty($this->action)) {
$this->session->remove('page');
- }
- // set current task to session
- $_SESSION['task'] = $this->task;
+ // set current task to session
+ $_SESSION['task'] = $this->task;
+ }
}
- // init output class
- if (!empty($_REQUEST['_remote']))
+ // init output class (not in CLI mode)
+ if (!empty($_REQUEST['_remote'])) {
$GLOBALS['OUTPUT'] = $this->json_init();
- else
+ }
+ else if ($_SERVER['REMOTE_ADDR']) {
$GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
+ }
- // load plugins
+ // run init method on all the plugins
$this->plugins->init($this, $this->task);
- $this->plugins->load_plugins((array)$this->config->get('plugins', array()),
- array('filesystem_attachments', 'jqueryui'));
}
/**
@@ -147,8 +154,13 @@
$this->task = $task;
$this->comm_path = $this->url(array('task' => $this->task));
+ if (!empty($_REQUEST['_framed'])) {
+ $this->comm_path .= '&_framed=1';
+ }
+
if ($this->output) {
$this->output->set_env('task', $this->task);
+ $this->output->set_env('comm_path', $this->comm_path);
}
}
@@ -167,9 +179,11 @@
// set localization
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 (version_compare(PHP_VERSION, '5.5.0', '<') && in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
- setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
+ // Workaround for http://bugs.php.net/bug.php?id=18556
+ // Also strtoupper/strtolower and other methods are locale-aware
+ // for these locales it is problematic (#1490519)
+ if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
+ setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8', 'C');
}
}
@@ -427,9 +441,12 @@
$this->output->set_env('user_id', $this->user->get_hash());
}
+ // set compose mode for all tasks (message compose step can be triggered from everywhere)
+ $this->output->set_env('compose_extwin', $this->config->get('compose_extwin',false));
+
// add some basic labels to client
$this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout',
- 'refreshing', 'windowopenerror');
+ 'refreshing', 'windowopenerror', 'uploadingmany');
return $this->output;
}
@@ -477,7 +494,7 @@
*
* @return boolean True on success, False on failure
*/
- function login($username, $pass, $host = null, $cookiecheck = false)
+ function login($username, $password, $host = null, $cookiecheck = false)
{
$this->login_error = null;
@@ -490,10 +507,22 @@
return false;
}
+ $username_filter = $this->config->get('login_username_filter');
+ $username_maxlen = $this->config->get('login_username_maxlen', 1024);
+ $password_maxlen = $this->config->get('login_password_maxlen', 1024);
$default_host = $this->config->get('default_host');
$default_port = $this->config->get('default_port');
$username_domain = $this->config->get('username_domain');
$login_lc = $this->config->get('login_lc', 2);
+
+ // check input for security (#1490500)
+ if (($username_maxlen && strlen($username) > $username_maxlen)
+ || ($username_filter && !preg_match($username_filter, $username))
+ || ($password_maxlen && strlen($password) > $password_maxlen)
+ ) {
+ $this->login_error = self::ERROR_INVALID_REQUEST;
+ return false;
+ }
// host is validated in rcmail::autoselect_host(), so here
// we'll only handle unset host (if possible)
@@ -574,12 +603,24 @@
// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];
+
+ // Brute-force prevention
+ if ($user->is_locked()) {
+ $this->login_error = self::ERROR_RATE_LIMIT;
+ return false;
+ }
}
$storage = $this->get_storage();
// try to log in
- if (!$storage->connect($host, $username, $pass, $port, $ssl)) {
+ if (!$storage->connect($host, $username, $password, $port, $ssl)) {
+ if ($user) {
+ $user->failed_login();
+ }
+
+ // Wait a second to slow down brute-force attacks (#1490549)
+ sleep(1);
return false;
}
@@ -625,7 +666,7 @@
$_SESSION['storage_host'] = $host;
$_SESSION['storage_port'] = $port;
$_SESSION['storage_ssl'] = $ssl;
- $_SESSION['password'] = $this->encrypt($pass);
+ $_SESSION['password'] = $this->encrypt($password);
$_SESSION['login_time'] = time();
if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_') {
@@ -750,47 +791,16 @@
}
/**
- * Generate a unique token to be used in a form request
- *
- * @return string The request token
- */
- public function get_request_token()
- {
- $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->get_user_id() . $this->config->get('des_key') . $sess_id)));
-
- return $plugin['value'];
- }
-
- /**
- * Check if the current request contains a valid token
- *
- * @param int Request method
- *
- * @return boolean True if request token is valid false if not
- */
- public function check_request($mode = rcube_utils::INPUT_POST)
- {
- $token = rcube_utils::get_input_value('_token', $mode);
- $sess_id = $_COOKIE[ini_get('session.name')];
-
- return !empty($sess_id) && $token == $this->get_request_token();
- }
-
- /**
* Build a valid URL to this instance of Roundcube
*
- * @param mixed Either a string with the action or url parameters as key-value pairs
+ * @param mixed Either a string with the action or url parameters as key-value pairs
+ * @param boolean Build an URL absolute to document root
+ * @param boolean Create fully qualified URL including http(s):// and hostname
+ * @param bool Return absolute URL in secure location
*
* @return string Valid application URL
*/
- public function url($p)
+ public function url($p, $absolute = false, $full = false, $secure = false)
{
if (!is_array($p)) {
if (strpos($p, 'http') === 0) {
@@ -800,14 +810,15 @@
$p = array('_action' => @func_get_arg(0));
}
- $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
- $p['_task'] = $task;
- unset($p['task']);
+ $pre = array();
+ $task = $p['_task'] ?: ($p['task'] ?: $this->task);
+ $pre['_task'] = $task;
+ unset($p['task'], $p['_task']);
- $url = './' . $this->filename;
+ $url = $this->filename;
$delm = '?';
- foreach (array_reverse($p) as $key => $val) {
+ foreach (array_merge($pre, $p) as $key => $val) {
if ($val !== '' && $val !== null) {
$par = $key[0] == '_' ? $key : '_'.$key;
$url .= $delm.urlencode($par).'='.urlencode($val);
@@ -815,7 +826,38 @@
}
}
- return $url;
+ $base_path = strval($_SERVER['REDIRECT_SCRIPT_URL'] ?: $_SERVER['SCRIPT_NAME']);
+ $base_path = preg_replace('![^/]+$!', '', $base_path);
+
+ if ($secure && ($token = $this->get_secure_url_token(true))) {
+ // add token to the url
+ $url = $token . '/' . $url;
+
+ // remove old token from the path
+ $base_path = rtrim($base_path, '/');
+ $base_path = preg_replace('/\/[a-zA-Z0-9]{' . strlen($token) . '}$/', '', $base_path);
+
+ // this need to be full url to make redirects work
+ $absolute = true;
+ }
+
+ if ($absolute || $full) {
+ // add base path to this Roundcube installation
+ if ($base_path == '') $base_path = '/';
+ $prefix = $base_path;
+
+ // prepend protocol://hostname:port
+ if ($full) {
+ $prefix = rcube_utils::resolve_url($prefix);
+ }
+
+ $prefix = rtrim($prefix, '/') . '/';
+ }
+ else {
+ $prefix = './';
+ }
+
+ return $prefix . $url;
}
/**
@@ -846,6 +888,28 @@
self::print_timer(RCMAIL_START, $log);
else
self::console($log);
+ }
+ }
+
+ /**
+ * CSRF attack prevention code
+ *
+ * @param int Request mode
+ */
+ public function request_security_check($mode = rcube_utils::INPUT_POST)
+ {
+ // check request token
+ if (!$this->check_request($mode)) {
+ self::raise_error(array(
+ 'code' => 403, 'type' => 'php',
+ 'message' => "Request security check failed"), false, true);
+ }
+
+ // check referer if configured
+ if ($this->config->get('referer_check') && !rcube_utils::check_referer()) {
+ self::raise_error(array(
+ 'code' => 403, 'type' => 'php',
+ 'message' => "Referer check failed"), true, true);
}
}
@@ -1028,6 +1092,12 @@
// failed login
if ($failed_login) {
+ // don't fill the log with complete input, which could
+ // have been prepared by a hacker
+ if (strlen($user) > 256) {
+ $user = substr($user, 0, 256) . '...';
+ }
+
$message = sprintf('Failed login for %s from %s in session %s (error: %d)',
$user, rcube_utils::remote_ip(), session_id(), $error_code);
}
@@ -1551,7 +1621,7 @@
// skip folders in which it isn't possible to create subfolders
if (!empty($opts['skip_noinferiors'])) {
$attrs = $this->storage->folder_attributes($folder['id']);
- if ($attrs && in_array('\\Noinferiors', $attrs)) {
+ if ($attrs && in_array_nocase('\\Noinferiors', $attrs)) {
continue;
}
}
@@ -1758,8 +1828,9 @@
* @param string $fallback Fallback message label
* @param array $fallback_args Fallback message label arguments
* @param string $suffix Message label suffix
+ * @param array $params Additional parameters (type, prefix)
*/
- public function display_server_error($fallback = null, $fallback_args = null, $suffix = '')
+ public function display_server_error($fallback = null, $fallback_args = null, $suffix = '', $params = array())
{
$err_code = $this->storage->get_error_code();
$res_code = $this->storage->get_response_code();
@@ -1772,7 +1843,7 @@
$error = 'errorreadonly';
}
else if ($res_code == rcube_storage::OVERQUOTA) {
- $error = 'errorroverquota';
+ $error = 'erroroverquota';
}
else if ($err_code && ($err_str = $this->storage->get_error_str())) {
// try to detect access rights problem and display appropriate message
@@ -1780,13 +1851,13 @@
$error = 'errornoperm';
}
// try to detect full mailbox problem and display appropriate message
- // there can be e.g. "Quota exceeded" or "quotum would exceed"
- else if (stripos($err_str, 'quot') !== false && stripos($err_str, 'exceed') !== false) {
+ // there can be e.g. "Quota exceeded" / "quotum would exceed" / "Over quota"
+ else if (stripos($err_str, 'quot') !== false && preg_match('/exceed|over/i', $err_str)) {
$error = 'erroroverquota';
}
else {
$error = 'servererrormsg';
- $args = array('msg' => $err_str);
+ $args = array('msg' => rcube::Q($err_str));
}
}
else if ($err_code < 0) {
@@ -1795,13 +1866,21 @@
else if ($fallback) {
$error = $fallback;
$args = $fallback_args;
+ $params['prefix'] = false;
}
if ($error) {
if ($suffix && $this->text_exists($error . $suffix)) {
$error .= $suffix;
}
- $this->output->show_message($error, 'error', $args);
+
+ $msg = $this->gettext(array('name' => $error, 'vars' => $args));
+
+ if ($params['prefix'] && $fallback) {
+ $msg = $this->gettext(array('name' => $fallback, 'vars' => $fallback_args)) . ' ' . $msg;
+ }
+
+ $this->output->show_message($msg, $params['type'] ?: 'error');
}
}
@@ -1812,7 +1891,24 @@
*/
public function html_editor($mode = '')
{
- $hook = $this->plugins->exec_hook('html_editor', array('mode' => $mode));
+ $spellcheck = intval($this->config->get('enable_spellcheck'));
+ $spelldict = intval($this->config->get('spellcheck_dictionary'));
+ $disabled_plugins = array();
+ $disabled_buttons = array();
+ $extra_plugins = array();
+ $extra_buttons = array();
+
+ if (!$spellcheck) {
+ $disabled_plugins[] = 'spellchecker';
+ }
+
+ $hook = $this->plugins->exec_hook('html_editor', array(
+ 'mode' => $mode,
+ 'disabled_plugins' => $disabled_plugins,
+ 'disabled_buttons' => $disabled_buttons,
+ 'extra_plugins' => $extra_plugins,
+ 'extra_buttons' => $extra_buttons,
+ ));
if ($hook['abort']) {
return;
@@ -1839,8 +1935,12 @@
'mode' => $mode,
'lang' => $lang,
'skin_path' => $this->output->get_skin_path(),
- 'spellcheck' => intval($this->config->get('enable_spellcheck')),
- 'spelldict' => intval($this->config->get('spellcheck_dictionary'))
+ 'spellcheck' => $spellcheck, // deprecated
+ 'spelldict' => $spelldict,
+ 'disabled_plugins' => $hook['disabled_plugins'],
+ 'disabled_buttons' => $hook['disabled_buttons'],
+ 'extra_plugins' => $hook['extra_plugins'],
+ 'extra_buttons' => $hook['extra_buttons'],
);
$this->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia');
@@ -1848,43 +1948,6 @@
$this->output->include_css('program/js/tinymce/roundcube/browser.css');
$this->output->include_script('tinymce/tinymce.min.js');
$this->output->include_script('editor.js');
- }
-
- /**
- * Replaces TinyMCE's emoticon images with plain-text representation
- *
- * @param string $html HTML content
- *
- * @return string HTML content
- */
- public static function replace_emoticons($html)
- {
- $emoticons = array(
- '8-)' => 'smiley-cool',
- ':-#' => 'smiley-foot-in-mouth',
- ':-*' => 'smiley-kiss',
- ':-X' => 'smiley-sealed',
- ':-P' => 'smiley-tongue-out',
- ':-@' => 'smiley-yell',
- ":'(" => 'smiley-cry',
- ':-(' => 'smiley-frown',
- ':-D' => 'smiley-laughing',
- ':-)' => 'smiley-smile',
- ':-S' => 'smiley-undecided',
- ':-$' => 'smiley-embarassed',
- 'O:-)' => 'smiley-innocent',
- ':-|' => 'smiley-money-mouth',
- ':-O' => 'smiley-surprised',
- ';-)' => 'smiley-wink',
- );
-
- foreach ($emoticons as $idx => $file) {
- // <img title="Cry" src="http://.../program/js/tinymce/plugins/emoticons/img/smiley-cry.gif" border="0" alt="Cry" />
- $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tinymce\/plugins\/emoticons\/img\/'.$file.'.gif"[^>]+\/>/i';
- $replace[] = $idx;
- }
-
- return preg_replace($search, $replace, $html);
}
/**
@@ -1928,13 +1991,32 @@
}
if (!empty($params['total'])) {
- $params['percent'] = round($status['current']/$status['total']*100);
+ $total = $this->show_bytes($params['total'], $unit);
+ switch ($unit) {
+ case 'GB':
+ $gb = $params['current']/1073741824;
+ $current = sprintf($gb >= 10 ? "%d" : "%.1f", $gb);
+ break;
+ case 'MB':
+ $mb = $params['current']/1048576;
+ $current = sprintf($mb >= 10 ? "%d" : "%.1f", $mb);
+ break;
+ case 'KB':
+ $current = round($params['current']/1024);
+ break;
+ case 'B':
+ default:
+ $current = $params['current'];
+ break;
+ }
+
+ $params['percent'] = round($params['current']/$params['total']*100);
$params['text'] = $this->gettext(array(
'name' => 'uploadprogress',
'vars' => array(
'percent' => $params['percent'] . '%',
- 'current' => $this->show_bytes($params['current']),
- 'total' => $this->show_bytes($params['total'])
+ 'current' => $current,
+ 'total' => $total
)
));
}
@@ -2010,16 +2092,15 @@
if (!empty($_GET['_thumbnail'])) {
$temp_dir = $this->config->get('temp_dir');
$thumbnail_size = 80;
- list(,$ext) = explode('/', $file['mimetype']);
$mimetype = $file['mimetype'];
$file_ident = $file['id'] . ':' . $file['mimetype'] . ':' . $file['size'];
$cache_basename = $temp_dir . '/' . md5($file_ident . ':' . $this->user->ID . ':' . $thumbnail_size);
- $cache_file = $cache_basename . '.' . $ext;
+ $cache_file = $cache_basename . '.thumb';
// render thumbnail image if not done yet
if (!is_file($cache_file)) {
if (!$file['path']) {
- $orig_name = $filename = $cache_basename . '.orig.' . $ext;
+ $orig_name = $filename = $cache_basename . '.tmp';
file_put_contents($orig_name, $file['data']);
}
else {
@@ -2120,25 +2201,30 @@
/**
* Create a human readable string for a number of bytes
*
- * @param int Number of bytes
+ * @param int Number of bytes
+ * @param string Size unit
*
* @return string Byte string
*/
- public function show_bytes($bytes)
+ public function show_bytes($bytes, &$unit = null)
{
if ($bytes >= 1073741824) {
- $gb = $bytes/1073741824;
- $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . $this->gettext('GB');
+ $unit = 'GB';
+ $gb = $bytes/1073741824;
+ $str = sprintf($gb >= 10 ? "%d " : "%.1f ", $gb) . $this->gettext($unit);
}
else if ($bytes >= 1048576) {
- $mb = $bytes/1048576;
- $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . $this->gettext('MB');
+ $unit = 'MB';
+ $mb = $bytes/1048576;
+ $str = sprintf($mb >= 10 ? "%d " : "%.1f ", $mb) . $this->gettext($unit);
}
else if ($bytes >= 1024) {
- $str = sprintf("%d ", round($bytes/1024)) . $this->gettext('KB');
+ $unit = 'KB';
+ $str = sprintf("%d ", round($bytes/1024)) . $this->gettext($unit);
}
else {
- $str = sprintf('%d ', $bytes) . $this->gettext('B');
+ $unit = 'B';
+ $str = sprintf('%d ', $bytes) . $this->gettext($unit);
}
return $str;
@@ -2182,8 +2268,8 @@
// message UID (or comma-separated list of IDs) is provided in
// the form of <ID>-<MBOX>[,<ID>-<MBOX>]*
- $_uid = $uids ?: rcube_utils::get_input_value('_uid', RCUBE_INPUT_GPC);
- $_mbox = $mbox ?: (string)rcube_utils::get_input_value('_mbox', RCUBE_INPUT_GPC);
+ $_uid = $uids ?: rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GPC);
+ $_mbox = $mbox ?: (string) rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC);
// already a hash array
if (is_array($_uid) && !isset($_uid[0])) {
@@ -2227,24 +2313,60 @@
return $result;
}
-
- /************************************************************************
- ********* Deprecated methods (to be removed) *********
- ***********************************************************************/
-
- public static function setcookie($name, $value, $exp = 0)
+ /**
+ * Get resource file content (with assets_dir support)
+ *
+ * @param string $name File name
+ */
+ public function get_resource_content($name)
{
- rcube_utils::setcookie($name, $value, $exp);
+ if (!strpos($name, '/')) {
+ $name = "program/resources/$name";
+ }
+
+ $assets_dir = $this->config->get('assets_dir');
+
+ if ($assets_dir) {
+ $path = slashify($assets_dir) . $name;
+ if (@file_exists($path)) {
+ $name = $path;
+ }
+ }
+
+ return file_get_contents($name, false);
}
- public function imap_connect()
+ /**
+ * Converts HTML content into plain text
+ *
+ * @param string $html HTML content
+ * @param array $options Conversion parameters (width, links, charset)
+ *
+ * @return string Plain text
+ */
+ public function html2text($html, $options = array())
{
- return $this->storage_connect();
- }
+ $default_options = array(
+ 'links' => true,
+ 'width' => 75,
+ 'body' => $html,
+ 'charset' => RCUBE_CHARSET,
+ );
- public function imap_init()
- {
- return $this->storage_init();
+ $options = array_merge($default_options, (array) $options);
+
+ // Plugins may want to modify HTML in another/additional way
+ $options = $this->plugins->exec_hook('html2text', $options);
+
+ // Convert to text
+ if (!$options['abort']) {
+ $converter = new rcube_html2text($options['body'],
+ false, $options['links'], $options['width'], $options['charset']);
+
+ $options['body'] = rtrim($converter->get_text());
+ }
+
+ return $options['body'];
}
/**
--
Gitblit v1.9.1