From 5499336feff22f682448dd99cc00a9b36701fcd1 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Tue, 21 Jul 2009 12:02:33 -0400
Subject: [PATCH] Use global request tokens and automatically protect all POST requests
---
program/include/rcube_template.php | 99 +++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 87 insertions(+), 12 deletions(-)
diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php
index b597c55..0947944 100755
--- a/program/include/rcube_template.php
+++ b/program/include/rcube_template.php
@@ -59,6 +59,7 @@
//$this->framed = $framed;
$this->set_env('task', $task);
+ $this->set_env('request_token', $this->app->get_request_token());
// load the correct skin (in case user-defined)
$this->set_skin($this->config['skin']);
@@ -201,7 +202,9 @@
*/
public function command()
{
- $this->js_commands[] = func_get_args();
+ $cmd = func_get_args();
+ if (strpos($cmd[0], 'plugin.') === false)
+ $this->js_commands[] = $cmd;
}
@@ -285,6 +288,11 @@
public function send($templ = null, $exit = true)
{
if ($templ != 'iframe') {
+ // prevent from endless loops
+ if ($this->app->plugins->is_processing('render_page')) {
+ raise_error(array('code' => 505, 'type' => 'php', 'message' => 'Recursion alert: ignoring output->send()'), true, false);
+ return;
+ }
$this->parse($templ, false);
}
else {
@@ -292,6 +300,10 @@
$this->write();
}
+ // set output asap
+ ob_flush();
+ flush();
+
if ($exit) {
exit;
}
@@ -314,6 +326,9 @@
$js = $this->framed ? "if(window.parent) {\n" : '';
$js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
$this->add_script($js, 'head_top');
+
+ // make sure all <form> tags have a valid request token
+ $template = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $template);
// call super method
parent::write($template, $this->config['skin_path']);
@@ -339,10 +354,10 @@
$plugin = $temp[0];
$name = $temp[1];
$skin_dir = $plugin . '/skins/' . $this->config['skin'];
- $skin_path = $this->config['plugins_dir'] . '/' . $skin_dir;
+ $skin_path = $this->app->plugins->dir . $skin_dir;
if (!is_dir($skin_path)) { // fallback to default skin
$skin_dir = $plugin . '/skins/default';
- $skin_path = $this->config['plugins_dir'] . '/' . $skin_dir;
+ $skin_path = $this->app->plugins->dir . $skin_dir;
}
}
@@ -369,15 +384,19 @@
// parse for specialtags
$output = $this->parse_conditions($templ);
$output = $this->parse_xml($output);
+
+ // trigger generic hook where plugins can put additional content to the page
+ $hook = $this->app->plugins->exec_hook("render_page", array('template' => $name, 'content' => $output));
// add debug console
if ($this->config['debug_level'] & 8) {
- $this->add_footer('<div style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;opacity:0.8;filter:alpha(opacity=80);z-index:9000">
+ $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;">
<a href="#toggle" onclick="con=document.getElementById(\'dbgconsole\');con.style.display=(con.style.display==\'none\'?\'block\':\'none\');return false">console</a>
<form action="/" name="debugform" style="display:inline"><textarea name="console" id="dbgconsole" rows="20" cols="40" wrap="off" style="display:none;width:400px;border:none;font-size:x-small" spellcheck="false"></textarea></form></div>'
);
}
- $output = $this->parse_with_globals($output);
+
+ $output = $this->parse_with_globals($hook['content']);
$this->write(trim($output));
if ($exit) {
exit;
@@ -433,6 +452,7 @@
*/
private function parse_with_globals($input)
{
+ $GLOBALS['__version'] = Q(RCMAIL_VERSION);
$GLOBALS['__comm_path'] = Q($this->app->comm_path);
return preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input);
}
@@ -498,7 +518,24 @@
*/
private function check_condition($condition)
{
- return eval("return (".$this->parse_expression($condition).");");
+ return eval("return (".$this->parse_expression($condition).");");
+ }
+
+
+ /**
+ *
+ */
+ private function alter_form_tag($matches)
+ {
+ $out = $matches[0];
+ $attrib = parse_attrib_string($matches[1]);
+
+ if (strtolower($attrib['method']) == 'post') {
+ $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
+ $out .= "\n" . $hidden->show();
+ }
+
+ return $out;
}
@@ -516,14 +553,16 @@
'/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
'/env:([a-z0-9_]+)/i',
'/request:([a-z0-9_]+)/i',
- '/cookie:([a-z0-9_]+)/i'
+ '/cookie:([a-z0-9_]+)/i',
+ '/browser:([a-z0-9_]+)/i'
),
array(
"\$_SESSION['\\1']",
"\$this->app->config->get('\\1',get_boolean('\\3'))",
"\$this->env['\\1']",
"get_input_value('\\1', RCUBE_INPUT_GPC)",
- "\$_COOKIE['\\1']"
+ "\$_COOKIE['\\1']",
+ "\$this->browser->{'\\1'}"
),
$expression);
}
@@ -587,6 +626,7 @@
else {
$incl = file_get_contents($path);
}
+ $incl = $this->parse_conditions($incl);
return $this->parse_xml($incl);
}
break;
@@ -675,6 +715,9 @@
break;
case 'cookie':
$value = htmlspecialchars($_COOKIE[$name]);
+ break;
+ case 'browser':
+ $value = $this->browser->{$name};
break;
}
@@ -765,10 +808,10 @@
if ($attrib['alt']) {
$attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain']));
}
+
// set title to alt attribute for IE browsers
if ($this->browser->ie && $attrib['title'] && !$attrib['alt']) {
$attrib['alt'] = $attrib['title'];
- unset($attrib['title']);
}
// add empty alt attribute for XHTML compatibility
@@ -796,6 +839,9 @@
else if (in_array($attrib['command'], $a_static_commands)) {
$attrib['href'] = rcmail_url($attrib['command']);
}
+ else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
+ $attrib['href'] = $this->env['permaurl'];
+ }
}
// overwrite attributes
@@ -900,7 +946,7 @@
*/
public function form_tag($attrib, $content = null)
{
- if ($this->framed) {
+ if ($this->framed || !empty($_REQUEST['_framed'])) {
$hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
$hidden = $hiddenfield->show();
}
@@ -910,7 +956,36 @@
return html::tag('form',
$attrib + array('action' => "./", 'method' => "get"),
- $hidden . $content);
+ $hidden . $content,
+ array('id','class','style','name','method','action','enctype','onsubmit'));
+ }
+
+
+ /**
+ * Build a form tag with a unique request token
+ *
+ * @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
+ * @param string Form content
+ * @return string HTML code for the form
+ */
+ public function request_form($attrib, $content = '')
+ {
+ $hidden = new html_hiddenfield();
+ if ($attrib['task']) {
+ $hidden->add(array('name' => '_task', 'value' => $attrib['task']));
+ }
+ if ($attrib['action']) {
+ $hidden->add(array('name' => '_action', 'value' => $attrib['action']));
+ }
+
+ unset($attrib['task'], $attrib['request']);
+ $attrib['action'] = './';
+
+ // we already have a <form> tag
+ if ($attrib['form'])
+ return $hidden->show() . $content;
+ else
+ return $this->form_tag($attrib, $hidden->show() . $content);
}
@@ -957,7 +1032,7 @@
// save original url
$url = get_input_value('_url', RCUBE_INPUT_POST);
- if (empty($url) && !preg_match('/_action=logout/', $_SERVER['QUERY_STRING']))
+ if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
$url = $_SERVER['QUERY_STRING'];
$input_user = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser', 'size' => 30) + $attrib);
--
Gitblit v1.9.1