From 681ba6fc3c296cd6cd11050531b8f4e785141786 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 16 Dec 2014 13:28:48 +0100 Subject: Improve system security by using optional special URL with security token Allows to define separate server/path for image/js/css files Fix bugs where CSRF attacks were still possible on some requests --- program/include/rcmail.php | 83 +++++++++++--------- program/include/rcmail_output.php | 1 + program/include/rcmail_output_html.php | 137 +++++++++++++++++++++++++++++---- program/include/rcmail_output_json.php | 5 ++ program/js/app.js | 24 +++++- program/js/editor.js | 5 +- program/lib/Roundcube/rcube.php | 112 ++++++++++++++++++++++++++- program/steps/addressbook/delete.inc | 5 +- program/steps/addressbook/func.inc | 11 +-- program/steps/addressbook/photo.inc | 10 ++- program/steps/mail/compose.inc | 2 +- program/steps/mail/show.inc | 8 +- program/steps/settings/func.inc | 18 +++-- program/steps/utils/error.inc | 12 ++- 14 files changed, 353 insertions(+), 80 deletions(-) (limited to 'program') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 27ec831e6..8b47a8d6c 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -759,50 +759,17 @@ class rcmail extends rcube } } - /** - * 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 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, $absolute = false, $full = false) + public function url($p, $absolute = false, $full = false, $secure = false) { if (!is_array($p)) { if (strpos($p, 'http') === 0) { @@ -828,9 +795,23 @@ class rcmail extends rcube } } + $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-f0-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 - $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME'])); if ($base_path == '') $base_path = '/'; $prefix = $base_path; @@ -879,6 +860,36 @@ class rcmail extends rcube } } + /** + * CSRF attack prevention code + * + * @param int Request mode + */ + public function request_security_check($mode = rcube_utils::INPUT_POST) + { + // don't check for valid request tokens in these actions + // @TODO: get rid of this + $request_check_whitelist = array('spell'=>1, 'spell_html'=>1); + + if ($request_check_whitelist[$this->action]) { + return; + } + + // 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); + } + } + /** * Registers action aliases for current task * diff --git a/program/include/rcmail_output.php b/program/include/rcmail_output.php index 0f7aaf966..76ff4e75e 100644 --- a/program/include/rcmail_output.php +++ b/program/include/rcmail_output.php @@ -28,6 +28,7 @@ abstract class rcmail_output extends rcube_output { const JS_OBJECT_NAME = 'rcmail'; + const BLANK_GIF = 'R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7'; public $type = 'html'; public $ajax_call = false; diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 026e9f869..c6c43b532 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -45,6 +45,8 @@ class rcmail_output_html extends rcmail_output protected $footer = ''; protected $body = ''; protected $base_path = ''; + protected $assets_path; + protected $assets_dir = RCUBE_INSTALL_PATH; protected $devel_mode = false; // deprecated names of templates used before 0.5 @@ -80,6 +82,8 @@ class rcmail_output_html extends rcmail_output $this->set_skin($skin); $this->set_env('skin', $skin); + $this->set_assets_path($this->config->get('assets_path'), $this->config->get('assets_dir')); + if (!empty($_REQUEST['_extwin'])) $this->set_env('extwin', 1); if ($this->framed || $framed) @@ -144,6 +148,55 @@ EOF; } } + /** + * Parse and set assets path + * + * @param string Assets path (relative or absolute URL) + */ + public function set_assets_path($path, $fs_dir = null) + { + if (empty($path)) { + return; + } + + $path = rtrim($path, '/') . '/'; + + // handle relative assets path + if (!preg_match('|^https?://|', $path) && $path[0] != '/') { + // save the path to search for asset files later + $this->assets_dir = $path; + + $base = preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI']); + $base = rtrim($base, '/'); + + // remove url token if exists + if ($len = intval($this->config->get('use_secure_urls'))) { + $_base = explode('/', $base); + $last = count($_base) - 1; + $length = $len > 1 ? $len : 16; // as in rcube::get_secure_url_token() + + // we can't use real token here because it + // does not exists in unauthenticated state, + // hope this will not produce false-positive matches + if ($last > -1 && preg_match('/^[a-f0-9]{' . $length . '}$/', $_base[$last])) { + $path = '../' . $path; + } + } + } + + // set filesystem path for assets + if ($fs_dir) { + if ($fs_dir[0] != '/') { + $fs_dir = realpath(RCUBE_INSTALL_PATH . $fs_dir); + } + // ensure the path ends with a slash + $this->assets_dir = rtrim($fs_dir, '/') . '/'; + } + + $this->assets_path = $path; + $this->set_env('assets_path', $path); + } + /** * Getter for the current page title * @@ -251,6 +304,7 @@ EOF; * @param string File name/path to resolve (starting with /) * @param string Reference to the base path of the matching skin * @param string Additional path to search in + * * @return mixed Relative path to the requested file or False if not found */ public function get_skin_file($file, &$skin_path = null, $add_path = null) @@ -261,10 +315,19 @@ EOF; } foreach ($skin_paths as $skin_path) { - $path = realpath($skin_path . $file); - if (is_file($path)) { + $path = realpath(RCUBE_INSTALL_PATH . $skin_path . $file); + + if ($path && is_file($path)) { return $skin_path . $file; } + + if ($this->assets_dir != RCUBE_INSTALL_PATH) { + $path = realpath($this->assets_dir . $skin_path . $file); + + if ($path && is_file($path)) { + return $skin_path . $file; + } + } } return false; @@ -369,14 +432,15 @@ EOF; /** * Redirect to a certain url * - * @param mixed $p Either a string with the action or url parameters as key-value pairs - * @param int $delay Delay in seconds + * @param mixed $p Either a string with the action or url parameters as key-value pairs + * @param int $delay Delay in seconds + * @param bool $secure Redirect to secure location (see rcmail::url()) */ - public function redirect($p = array(), $delay = 1) + public function redirect($p = array(), $delay = 1, $secure = false) { if ($this->env['extwin']) $p['extwin'] = 1; - $location = $this->app->url($p); + $location = $this->app->url($p, false, false, $secure); header('Location: ' . $location); exit; } @@ -490,11 +554,11 @@ EOF; // find skin template $path = false; foreach ($this->skin_paths as $skin_path) { - $path = "$skin_path/templates/$name.html"; + $path = RCUBE_INSTALL_PATH . "$skin_path/templates/$name.html"; // fallback to deprecated template names if (!is_readable($path) && $this->deprecated_templates[$realname]) { - $path = "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html"; + $path = RCUBE_INSTALL_PATH . "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html"; if (is_readable($path)) { rcube::raise_error(array( @@ -667,6 +731,21 @@ EOF; exit; } + /** + * Modify path by adding URL prefix if configured + */ + public function asset_url($path) + { + // iframe content can't be in a different domain + // @TODO: check if assests are on a different domain + + if (!$this->assets_path || in_array($path[0], array('?', '/', '.')) || strpos($path, '://')) { + return $path; + } + + return $this->assets_path . $path; + } + /***** Template parsing methods *****/ @@ -704,7 +783,7 @@ EOF; } /** - * Callback function for preg_replace_callback in write() + * Callback function for preg_replace_callback in fix_paths() * * @return string Parsed string */ @@ -726,6 +805,28 @@ EOF; return $matches[1] . '=' . $matches[2] . $file . $matches[4]; } + /** + * Correct paths of asset files according to assets_path + */ + protected function fix_assets_paths($output) + { + return preg_replace_callback( + '!(src|href|background)=(["\']?)([a-z0-9/_.?=-]+)(["\'\s>])!i', + array($this, 'assets_callback'), $output); + } + + /** + * Callback function for preg_replace_callback in fix_assets_paths() + * + * @return string Parsed string + */ + protected function assets_callback($matches) + { + $file = $this->asset_url($matches[3]); + + return $matches[1] . '=' . $matches[2] . $file . $matches[4]; + } + /** * Modify file by adding mtime indicator */ @@ -737,12 +838,12 @@ EOF; // use minified file if exists (not in development mode) if (!$this->devel_mode && !preg_match('/\.min\.' . $ext . '$/', $file)) { $minified_file = substr($file, 0, strlen($ext) * -1) . 'min.' . $ext; - if ($fs = @filemtime($minified_file)) { + if ($fs = @filemtime($this->assets_dir . $minified_file)) { return $minified_file . '?s=' . $fs; } } - if ($fs = @filemtime($file)) { + if ($fs = @filemtime($this->assets_dir . $file)) { $file .= '?s=' . $fs; } @@ -969,7 +1070,7 @@ EOF; if (!empty($attrib['skin_path'])) $attrib['skinpath'] = $attrib['skin_path']; if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) { $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin) - $path = realpath($path); + $path = realpath(RCUBE_INSTALL_PATH . $path); } if (is_readable($path)) { @@ -1521,6 +1622,10 @@ EOF; $output = $this->parse_with_globals($this->fix_paths($output)); + if ($this->assets_path) { + $output = $this->fix_assets_paths($output); + } + // trigger hook with final HTML content to be sent $hook = $this->app->plugins->exec_hook("send_page", array('content' => $output)); if (!$hook['abort']) { @@ -1549,12 +1654,12 @@ EOF; } $attrib['name'] = $attrib['id']; - $attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif'; + $attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif'; // register as 'contentframe' object if ($is_contentframe || $attrib['contentframe']) { $this->set_env('contentframe', $attrib['contentframe'] ? $attrib['contentframe'] : $attrib['name']); - $this->set_env('blankpage', $attrib['src']); + $this->set_env('blankpage', $this->asset_url($attrib['src'])); } return html::iframe($attrib); @@ -1766,9 +1871,11 @@ EOF; { $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY); $images = array_map(array($this, 'abs_url'), $images); + $images = array_map(array($this, 'asset_url'), $images); - if (empty($images) || $this->app->task == 'logout') + if (empty($images) || $_REQUEST['_task'] == 'logout') { return; + } $this->add_script('var images = ' . self::json_serialize($images) .'; for (var i=0; ishow_message("Application Error ($code): $message", 'error'); $this->remote_response(); exit; diff --git a/program/js/app.js b/program/js/app.js index e0a6d26e3..4e65a9c6d 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -58,7 +58,6 @@ function rcube_webmail() request_timeout: 180, // seconds draft_autosave: 0, // seconds comm_path: './', - blankpage: 'program/resources/blank.gif', recipients_separator: ',', recipients_delimiter: ', ', popup_width: 1150, @@ -163,6 +162,9 @@ function rcube_webmail() return; } + if (!this.env.blankpage) + this.env.blankpage = this.assets_path('program/resources/blank.gif'); + // find all registered gui containers for (n in this.gui_containers) this.gui_containers[n] = $('#'+this.gui_containers[n]); @@ -1406,8 +1408,10 @@ function rcube_webmail() if (task == 'mail') url += '&_mbox=INBOX'; - else if (task == 'logout' && !this.env.server_error) + else if (task == 'logout' && !this.env.server_error) { + url += '&_token=' + this.env.request_token; this.clear_compose_data(); + } this.redirect(url); }; @@ -1417,7 +1421,10 @@ function rcube_webmail() if (!url) url = this.env.comm_path; - return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task); + if (url.match(/[?&]_task=[a-zA-Z0-9_-]+/)) + return url.replace(/_task=[a-zA-Z0-9_-]+/, '_task=' + task); + else + return url.replace(/\?.*$/, '') + '?_task=' + task; }; this.reload = function(delay) @@ -8039,7 +8046,7 @@ function rcube_webmail() img.onload = function() { ref.env.browser_capabilities.tif = 1; }; img.onerror = function() { ref.env.browser_capabilities.tif = 0; }; - img.src = 'program/resources/blank.tif'; + img.src = this.assets_path('program/resources/blank.tif'); }; this.pdf_support_check = function() @@ -8096,6 +8103,15 @@ function rcube_webmail() return 0; }; + this.assets_path = function(path) + { + if (this.env.assets_path && !path.startsWith(this.env.assets_path)) { + path = this.env.assets_path + path; + } + + return path; + }; + // Cookie setter this.set_cookie = function(name, value, expires) { diff --git a/program/js/editor.js b/program/js/editor.js index 3dac5f371..2fc3429ab 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -36,12 +36,13 @@ function rcube_text_editor(config, id) { var ref = this, + abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''), conf = { selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'), cache_suffix: 's=4010700', theme: 'modern', language: config.lang, - content_css: 'program/js/tinymce/roundcube/content.css', + content_css: rcmail.assets_path('program/js/tinymce/roundcube/content.css'), menubar: false, statusbar: false, toolbar_items_size: 'small', @@ -83,7 +84,7 @@ function rcube_text_editor(config, id) toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify' + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', - spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1', + spellchecker_rpc_url: abs_url + '/?_task=utils&_action=spell_html&_remote=1', spellchecker_language: rcmail.env.spell_lang, accessibility_focus: false, file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 689823fcb..547e2b4ac 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -28,9 +28,15 @@ */ class rcube { - const INIT_WITH_DB = 1; + // Init options + const INIT_WITH_DB = 1; const INIT_WITH_PLUGINS = 2; + // Request status + const REQUEST_VALID = 0; + const REQUEST_ERROR_URL = 1; + const REQUEST_ERROR_TOKEN = 2; + /** * Singleton instace of rcube * @@ -101,6 +107,12 @@ class rcube */ public $user; + /** + * Request status + * + * @var int + */ + public $request_status = 0; /* private/protected vars */ protected $texts; @@ -977,6 +989,104 @@ class rcube } + /** + * Returns session token for secure URLs + * + * @param bool $generate Generate token if not exists in session yet + * + * @return string|bool Token string, False when disabled + */ + public function get_secure_url_token($generate = false) + { + if ($len = $this->config->get('use_secure_urls')) { + if (empty($_SESSION['secure_token']) && $generate) { + // generate x characters long token + $length = $len > 1 ? $len : 16; + $token = openssl_random_pseudo_bytes($length / 2); + $token = bin2hex($token); + + $plugin = $this->plugins->exec_hook('secure_token', + array('value' => $token, 'length' => $length)); + + $_SESSION['secure_token'] = $plugin['value']; + } + + return $_SESSION['secure_token']; + } + + return false; + } + + + /** + * 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. + * Empty requests aren't checked until use_secure_urls is set. + * + * @param int Request method + * + * @return boolean True if request token is valid false if not + */ + public function check_request($mode = rcube_utils::INPUT_POST) + { + // check secure token in URL if enabled + if ($token = $this->get_secure_url_token()) { + foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { + if ($tok == $token) { + return true; + } + } + + $this->request_status = self::REQUEST_ERROR_URL; + + return false; + } + + $sess_tok = $this->get_request_token(); + + // ajax requests + if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { + return true; + } + + // skip empty requests + if (($mode == rcube_utils::INPUT_POST && empty($_POST)) + || ($mode == rcube_utils::INPUT_GET && empty($_GET)) + ) { + return true; + } + + // default method of securing requests + $token = rcube_utils::get_input_value('_token', $mode); + $sess_id = $_COOKIE[ini_get('session.name')]; + + if (empty($sess_id) || $token != $sess_tok) { + $this->request_status = self::REQUEST_ERROR_TOKEN; + return false; + } + + return true; + } + + /** * Build a valid URL to this instance of Roundcube * diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc index f5b8e4eb5..9a23c59bb 100644 --- a/program/steps/addressbook/delete.inc +++ b/program/steps/addressbook/delete.inc @@ -20,10 +20,11 @@ */ // process ajax requests only -if (!$OUTPUT->ajax_call) +if (!$OUTPUT->ajax_call) { return; +} -$cids = rcmail_get_cids(); +$cids = rcmail_get_cids(null, rcube_utils::INPUT_POST); $delcnt = 0; // remove previous deletes diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index 008d20174..c40b517dc 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -785,11 +785,12 @@ function rcmail_contact_photo($attrib) if ($result = $CONTACTS->get_result()) $record = $result->first(); - $photo_img = $attrib['placeholder'] ? $RCMAIL->output->get_skin_file($attrib['placeholder']) : 'program/resources/blank.gif'; + $photo_img = $attrib['placeholder'] ? $RCMAIL->output->abs_url($attrib['placeholder'], true) : 'program/resources/blank.gif'; if ($record['_type'] == 'group' && $attrib['placeholdergroup']) - $photo_img = $RCMAIL->output->get_skin_file($attrib['placeholdergroup']); + $photo_img = $RCMAIL->output->abs_url($attrib['placeholdergroup'], true); + + $RCMAIL->output->set_env('photo_placeholder', $RCMAIL->output->asset_url($photo_img)); - $RCMAIL->output->set_env('photo_placeholder', $photo_img); unset($attrib['placeholder']); $plugin = $RCMAIL->plugins->exec_hook('contact_photo', array('record' => $record, 'data' => $record['photo'])); @@ -896,13 +897,13 @@ function rcmail_search_update($return = false) * * @return array List of contact IDs per-source */ -function rcmail_get_cids($filter = null) +function rcmail_get_cids($filter = null, $request_type = rcube_utils::INPUT_GPC) { // contact ID (or comma-separated list of IDs) is provided in two // forms. If _source is an empty string then the ID is a string // containing contact ID and source name in form: - - $cid = rcube_utils::get_input_value('_cid', rcube_utils::INPUT_GPC); + $cid = rcube_utils::get_input_value('_cid', $request_type); $source = (string) rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); if (is_array($cid)) { diff --git a/program/steps/addressbook/photo.inc b/program/steps/addressbook/photo.inc index 30d09ffcc..962ca3126 100644 --- a/program/steps/addressbook/photo.inc +++ b/program/steps/addressbook/photo.inc @@ -90,6 +90,12 @@ if (!$cid && $email) { $RCMAIL->output->future_expire_header(86400); } -header('Content-Type: ' . rcube_mime::image_content_type($data)); -echo $data ? $data : file_get_contents('program/resources/blank.gif'); +if ($data) { + header('Content-Type: ' . rcube_mime::image_content_type($data)); + echo $data; +} +else { + header('Content-Type: image/gif'); + echo base64_decode(rcmail_output::BLANK_GIF); +} exit; diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 5492f390d..fd25cf402 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -951,7 +951,7 @@ function rcmail_compose_body($attrib) "googie.setCurrentLanguage('%s');\n". "googie.setDecoration(false);\n". "googie.decorateTextarea('%s');\n", - $RCMAIL->output->get_skin_path(), + $RCMAIL->output->asset_url($RCMAIL->output->get_skin_path()), $RCMAIL->url(array('_task' => 'utils', '_action' => 'spell', '_remote' => 1)), !empty($dictionary) ? 'true' : 'false', rcube::JQ(rcube::Q($RCMAIL->gettext('checkspelling'))), diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 5adc97900..d9233a923 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -341,20 +341,20 @@ function rcmail_message_contactphoto($attrib) { global $RCMAIL, $MESSAGE; - $placeholder = $attrib['placeholder'] ? $RCMAIL->config->get('skin_path') . $attrib['placeholder'] : null; + $placeholder = $attrib['placeholder'] ? $RCMAIL->output->abs_url($attrib['placeholder'], true) : null; + $placeholder = $RCMAIL->output->asset_url($placeholder ? $placeholder : 'program/resources/blank.gif'); if ($MESSAGE->sender) { $photo_img = $RCMAIL->url(array( '_task' => 'addressbook', '_action' => 'photo', '_email' => $MESSAGE->sender['mailto'], - '_alt' => $placeholder, )); - $attrib['onerror'] = "this.src = '" . ($placeholder ? $placeholder : 'program/resources/blank.gif') . "'"; + $attrib['onerror'] = "this.src = '$placeholder'"; } else { - $photo_img = $placeholder ? $placeholder : 'program/resources/blank.gif'; + $photo_img = $placeholder; } return html::img(array('src' => $photo_img, 'alt' => $RCMAIL->gettext('contactphoto')) + $attrib); diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index f700e4f77..486e679c9 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -334,13 +334,10 @@ function rcmail_user_prefs($current = null) $input = new html_radiobutton(array('name'=>'_skin')); foreach ($skins as $skin) { - $thumbnail = "./skins/$skin/thumbnail.png"; - if (!is_file($thumbnail)) - $thumbnail = './program/resources/blank.gif'; - + $thumbnail = "skins/$skin/thumbnail.png"; $skinname = ucfirst($skin); $author_link = $license_link = ''; - $meta = @json_decode(@file_get_contents("./skins/$skin/meta.json"), true); + $meta = @json_decode(@file_get_contents(INSTALL_PATH . "skins/$skin/meta.json"), true); if (is_array($meta) && $meta['name']) { $skinname = $meta['name']; @@ -348,10 +345,19 @@ function rcmail_user_prefs($current = null) $license_link = $meta['license-url'] ? html::a(array('href' => $meta['license-url'], 'target' => '_blank', 'tabindex' => '-1'), rcube::Q($meta['license'])) : rcube::Q($meta['license']); } + $img = html::img(array( + 'src' => $thumbnail, + 'class' => 'skinthumbnail', + 'alt' => $skin, + 'width' => 64, + 'height' => 64, + 'onerror' => "this.src = rcmail.assets_path('program/resources/blank.gif')", + )); + $skinnames[] = mb_strtolower($skinname); $blocks['skin']['options'][$skin]['content'] = html::label(array('class' => 'skinselection'), html::span('skinitem', $input->show($config['skin'], array('value' => $skin, 'id' => $field_id.$skin))) . - html::span('skinitem', html::img(array('src' => $thumbnail, 'class' => 'skinthumbnail', 'alt' => $skin, 'width' => 64, 'height' => 64))) . + html::span('skinitem', $img) . html::span('skinitem', html::span('skinname', rcube::Q($skinname)) . html::br() . html::span('skinauthor', $author_link ? 'by ' . $author_link : '') . html::br() . html::span('skinlicense', $license_link ? $RCMAIL->gettext('license').': ' . $license_link : '')) diff --git a/program/steps/utils/error.inc b/program/steps/utils/error.inc index ec0d038f4..6bbc57fda 100644 --- a/program/steps/utils/error.inc +++ b/program/steps/utils/error.inc @@ -50,9 +50,17 @@ else if ($ERROR_CODE == 401) { // forbidden due to request check else if ($ERROR_CODE == 403) { + if ($_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->request_status == rcube::REQUEST_ERROR_URL) { + parse_str($_SERVER['QUERY_STRING'], $url); + $url = $rcmail->url($url, true, false, true); + $add = "
Click here to try again."; + } + else { + $add = "Please contact your server-administrator."; + } + $__error_title = "REQUEST CHECK FAILED"; - $__error_text = "Access to this service was denied due to failing security checks!
\n" - . "Please contact your server-administrator."; + $__error_text = "Access to this service was denied due to failing security checks!
\n$add"; } // failed request (wrong step in URL) -- cgit v1.2.3