diff options
Diffstat (limited to 'program/include')
-rw-r--r-- | program/include/rcmail.php | 123 | ||||
-rw-r--r-- | program/include/rcmail_install.php | 2 | ||||
-rw-r--r-- | program/include/rcmail_output.php | 1 | ||||
-rw-r--r-- | program/include/rcmail_output_html.php | 137 | ||||
-rw-r--r-- | program/include/rcmail_output_json.php | 5 |
5 files changed, 204 insertions, 64 deletions
diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 8ea42b600..a16319f72 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -439,7 +439,7 @@ class rcmail extends rcube // add some basic labels to client $this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout', - 'refreshing', 'windowopenerror'); + 'refreshing', 'windowopenerror', 'uploadingmany'); return $this->output; } @@ -760,49 +760,16 @@ 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; @@ -880,6 +861,28 @@ class rcmail extends rcube } /** + * 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); + } + } + + /** * Registers action aliases for current task * * @param array $map Alias-to-filename hash array @@ -1958,13 +1961,32 @@ class rcmail extends rcube } 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 ) )); } @@ -2150,25 +2172,30 @@ class rcmail extends rcube /** * 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; diff --git a/program/include/rcmail_install.php b/program/include/rcmail_install.php index a13cbd20b..26c613a11 100644 --- a/program/include/rcmail_install.php +++ b/program/include/rcmail_install.php @@ -298,7 +298,7 @@ class rcmail_install $out = $seen = array(); // iterate over the current configuration - foreach ($this->config as $prop => $value) { + foreach (array_keys($this->config) as $prop) { if ($replacement = $this->replaced_config[$prop]) { $out['replaced'][] = array('prop' => $prop, 'replacement' => $replacement); $seen[$replacement] = true; 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) @@ -145,6 +149,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 * * @return string The 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 */ @@ -727,6 +806,28 @@ EOF; } /** + * 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 */ protected function file_mod($file) @@ -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; i<images.length; i++) { diff --git a/program/include/rcmail_output_json.php b/program/include/rcmail_output_json.php index fa35824db..91262acb3 100644 --- a/program/include/rcmail_output_json.php +++ b/program/include/rcmail_output_json.php @@ -181,6 +181,11 @@ class rcmail_output_json extends rcmail_output */ public function raise_error($code, $message) { + if ($code == 403) { + header('HTTP/1.1 403 Forbidden'); + die("Invalid Request"); + } + $this->show_message("Application Error ($code): $message", 'error'); $this->remote_response(); exit; |