# HG changeset patch
# User Dan
# Date 1191563820 14400
# Node ID e1a22031b5bdc58cc924628a78ded1e0a9476618
# Parent bed9d04fa144d66bbe1bf050f5f32d7f53419d4c
Major revamps to the template parser. Fixed a few security holes that could allow PHP to be injected in untimely places in TPL code. Improved Ux for XSS attempt in tplWikiFormat. Documented many functions. Backported much cleaner parser from 2.0 branch. Beautified a lot of code in the depths of the template class. Pretty much a small-scale Extreme Makeover.
diff -r bed9d04fa144 -r e1a22031b5bd includes/template.php
--- a/includes/template.php Thu Oct 04 08:22:25 2007 -0400
+++ b/includes/template.php Fri Oct 05 01:57:00 2007 -0400
@@ -871,78 +871,232 @@
else return '';
}
- function process_template($file) {
+ /**
+ * Compiles and executes a template based on the current variables and booleans. Loads
+ * the theme and initializes variables if needed. This mostly just calls child functions.
+ * @param string File to process
+ * @return string
+ */
+
+ function process_template($file)
+ {
global $db, $session, $paths, $template, $plugins; // Common objects
if(!defined('ENANO_TEMPLATE_LOADED'))
{
$this->load_theme();
$this->init_vars();
}
- eval($this->compile_template($file));
- return $tpl_code;
+
+ $compiled = $this->compile_template($file);
+ return eval($compiled);
}
- function extract_vars($file) {
+ /**
+ * Loads variables from the specified template file. Returns an associative array containing the variables.
+ * @param string Template file to process (elements.tpl)
+ * @return array
+ */
+
+ function extract_vars($file)
+ {
global $db, $session, $paths, $template, $plugins; // Common objects
- if(!$this->theme)
+
+ // Sometimes this function gets called before the theme is loaded
+ // This is a bad coding practice so this function will always be picky.
+ if ( !$this->theme )
{
die('$template->extract_vars(): theme not yet loaded, so we can\'t open template files yet...this is a bug and should be reported.
Backtrace, most recent call first:
'.enano_debug_print_backtrace(true).''); } - if(!is_file(ENANO_ROOT . '/themes/'.$this->theme.'/'.$file)) die('Cannot find '.$file.' file for style "'.$this->theme.'", exiting'); - $text = file_get_contents(ENANO_ROOT . '/themes/'.$this->theme.'/'.$file); + + // Full pathname of template file + $tpl_file_fullpath = ENANO_ROOT . '/themes/' . $this->theme . '/' . $file; + + // Make sure the template even exists + if ( !is_file($tpl_file_fullpath) ) + { + die_semicritical('Cannot find template file', + '
The template parser was asked to load the file "' . htmlspecialchars($filename) . '", but that file couldn\'t be found in the directory for + the current theme.
+Additional debugging information:
+ Theme currently in use: ' . $this->theme . '
+ Requested file: ' . $file . '
+
The template parser was asked to load the file "' . htmlspecialchars($filename) . '", but that file couldn\'t be found in the directory for + the current theme.
+Additional debugging information:
+ Theme currently in use: ' . $this->theme . '
+ Requested file: ' . $file . '
+
'.htmlspecialchars(print_r($m, true)).''); - for($i = 0; $i < sizeof($m[1]); $i++) + // Preprocessing and checks complete - compile the code + $text = $this->compile_tpl_code($text); + + // Perhaps caching is enabled and the admin has changed the template? + if ( is_writable( ENANO_ROOT . '/cache/' ) && getConfig('cache_thumbs') == '1' ) { - $text = str_replace("", "{PHPCODE:{$i}:{$seed}}", $text); - } - //die('
'.htmlspecialchars($text).''); - $text = 'ob_start(); echo \''.str_replace('\'', '\\\'', $text).'\'; $tpl_code = ob_get_contents(); ob_end_clean();'; - $text = preg_replace('##is', '\'; if(isset($this->tpl_bool[\'\\1\']) && $this->tpl_bool[\'\\1\']) { echo \'', $text); - $text = preg_replace('##is', '\'; if(isset($this->tpl_strings[\'\\1\'])) { echo \'', $text); - $text = preg_replace('##is', '\'; if(getConfig(\'plugin_\\1\')==\'1\') { echo \'', $text); - $text = preg_replace('##is', '\'; echo $template->tplWikiFormat($paths->sysMsg(\'\\1\')); echo \'', $text); - $text = preg_replace('##is', '\'; if(!$this->tpl_bool[\'\\1\']) { echo \'', $text); - $text = preg_replace('##is', '\'; } else { echo \'', $text); - $text = preg_replace('##is', '\'; } echo \'', $text); - $text = preg_replace('#\{([A-z0-9]*)\}#is', '\'.$this->tpl_strings[\'\\1\'].\'', $text); - for($i = 0; $i < sizeof($m[1]); $i++) - { - $text = str_replace("{PHPCODE:{$i}:{$seed}}", "'; {$m[1][$i]} echo '", $text); - } - if(is_writable(ENANO_ROOT.'/cache/') && getConfig('cache_thumbs')=='1') - { - //die($tpl_filename); - $h = fopen($tpl_filename, 'w'); - if(!$h) return $text; - $t = addslashes($text); + $h = fopen($cache_file, 'w'); + if ( !$h ) + { + // Couldn't open the file - silently ignore and return + return $text; + } + + // Escape the compiled code so it can be eval'ed + $text_escaped = addslashes($text); $notice = <<
'.htmlspecialchars($text).''); } - function compile_template_text($text) { - $seed = md5 ( microtime() . mt_rand() ); - preg_match_all("/<\?php(.*?)\?>/is", $text, $m); - //die('
'.htmlspecialchars(print_r($m, true)).''); - for($i = 0; $i < sizeof($m[1]); $i++) - { - $text = str_replace("", "{PHPCODE:{$i}:{$seed}}", $text); - } - //die('
'.htmlspecialchars($text).''); - $text = 'ob_start(); echo \''.str_replace('\'', '\\\'', $text).'\'; $tpl_code = ob_get_contents(); ob_end_clean(); return $tpl_code;'; - $text = preg_replace('##is', '\'; if(isset($this->tpl_bool[\'\\1\']) && $this->tpl_bool[\'\\1\']) { echo \'', $text); - $text = preg_replace('##is', '\'; if(isset($this->tpl_strings[\'\\1\'])) { echo \'', $text); - $text = preg_replace('##is', '\'; if(getConfig(\'plugin_\\1\')==\'1\') { echo \'', $text); - $text = preg_replace('##is', '\'; echo $template->tplWikiFormat($paths->sysMsg(\'\\1\')); echo \'', $text); - $text = preg_replace('##is', '\'; if(!$this->tpl_bool[\'\\1\']) { echo \'', $text); - $text = preg_replace('##is', '\'; } else { echo \'', $text); - $text = preg_replace('##is', '\'; } echo \'', $text); - $text = preg_replace('#\{([A-z0-9]*)\}#is', '\'.$this->tpl_strings[\'\\1\'].\'', $text); - for($i = 0; $i < sizeof($m[1]); $i++) - { - $text = str_replace("{PHPCODE:{$i}:{$seed}}", "'; {$m[1][$i]} echo '", $text); - } - return $text; //('
'.htmlspecialchars($text).''); + + /** + * Compiles (parses) some template code with the current master set of variables and booleans. + * @param string Text to process + * @return string + */ + + function compile_template_text($text) + { + // this might do something else in the future, possibly cache large templates + return $this->compile_tpl_code($text); } + /** + * For convenience - compiles AND parses some template code. + * @param string Text to process + * @return string + */ + function parse($text) { $text = $this->compile_template_text($text); @@ -1004,7 +1155,18 @@ // So you can implement custom logic into your sidebar if you wish. // "Real" PHP support coming soon :-D - function tplWikiFormat($message, $filter_links = false, $filename = 'elements.tpl') { + /** + * Takes a blob of HTML with the specially formatted template-oriented wikitext and formats it. Does not use eval(). + * This function butchers every coding standard in Enano and should eventually be rewritten. The fact is that the + * code _works_ and does a good job of checking for errors and cleanly complaining about them. + * @param string Text to process + * @param bool Ignored for backwards compatibility + * @param string File to get variables for sidebar data from + * @return string + */ + + function tplWikiFormat($message, $filter_links = false, $filename = 'elements.tpl') + { global $db, $session, $paths, $template, $plugins; // Common objects $filter_links = false; $tplvars = $this->extract_vars($filename); @@ -1029,83 +1191,93 @@ // Conditionals - preg_match_all('#\{if ([A-Za-z0-9_ &\|\!-]*)\}(.*?)\{\/if\}#is', $message, $links); + preg_match_all('#\{if ([A-Za-z0-9_ \(\)&\|\!-]*)\}(.*?)\{\/if\}#is', $message, $links); - for($i=0;$i
Error: Syntax error (possibly XSS attack) caught in template code:
'; + $errmsg .= '';
+ $errmsg .= '{if '.htmlspecialchars($condition).'}';
+ $errmsg .= "\n ";
+ for ( $k = 0; $k < $j; $k++ )
+ {
+ $errmsg .= " ";
+ }
+ // Show position of error
+ $errmsg .= '^';
+ $errmsg .= '
';
+ $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $errmsg, $message);
+ continue 2;
+ }
+ if($current_var != '')
+ {
+ $cd = '( isset($this->tpl_bool[\''.$current_var.'\']) && $this->tpl_bool[\''.$current_var.'\'] )';
+ $cvt = substr($condition, 0, $current_var_start_pos) . $cd . substr($condition, $current_var_end_pos, strlen($condition));
+ $j = $j + strlen($cd) - strlen($current_var);
+ $current_var = '';
+ $condition = $cvt;
+ $d = strlen($condition);
+ }
}
- // OK we are not inside of a variable. That means that we JUST hit the end because the counter ($j) will be advanced to the beginning of the next variable once processing here is complete.
- if($char != ' ' && $char != '(' && $char != ')' && $char != 'A' && $char != 'N' && $char != 'D' && $char != 'O' && $char != 'R' && $char != '&' && $char != '|' && $char != '!' && $char != '<' && $char != '>' && $char != '0' && $char != '1' && $char != '2' && $char != '3' && $char != '4' && $char != '5' && $char != '6' && $char != '7' && $char != '8' && $char != '9')
- {
- // XSS attack! Bail out
- echo 'Error: Syntax error (possibly XSS attack) caught in template code:
'; - echo '';
- echo '{if '.$links[1][$i].'}';
- echo "\n ";
- for($k=0;$k<$j;$k++) echo " ";
- echo '^';
- echo '
';
- continue 2;
- }
- if($current_var != '')
+ $condition = substr($condition, 0, strlen($condition)-1);
+ $condition = '$chk = ( '.$condition.' ) ? true : false;';
+ eval($condition);
+
+ if($chk)
{
- $cd = '( isset($this->tpl_bool[\''.$current_var.'\']) && $this->tpl_bool[\''.$current_var.'\'] )';
- $cvt = substr($links[1][$i], 0, $current_var_start_pos) . $cd . substr($links[1][$i], $current_var_end_pos, strlen($links[1][$i]));
- $j = $j + strlen($cd) - strlen($current_var);
- $current_var = '';
- $links[1][$i] = $cvt;
- $d = strlen($links[1][$i]);
+ if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], 0, strpos($links[2][$i], '{else}'));
+ else $c = $links[2][$i];
+ $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
}
- }
- $links[1][$i] = substr($links[1][$i], 0, strlen($links[1][$i])-1);
- $links[1][$i] = '$chk = ( '.$links[1][$i].' ) ? true : false;';
- eval($links[1][$i]);
-
- if($chk) { // isset($this->tpl_bool[$links[1][$i]]) && $this->tpl_bool[$links[1][$i]]
- if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], 0, strpos($links[2][$i], '{else}'));
- else $c = $links[2][$i];
- $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
- } else {
- if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], strpos($links[2][$i], '{else}')+6, strlen($links[2][$i]));
- else $c = '';
- $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
- }
+ else
+ {
+ if(strstr($links[2][$i], '{else}')) $c = substr($links[2][$i], strpos($links[2][$i], '{else}')+6, strlen($links[2][$i]));
+ else $c = '';
+ $message = str_replace('{CONDITIONAL:'.$i.':'.$random_id.'}', $c, $message);
+ }
}
preg_match_all('#\{!if ([A-Za-z_-]*)\}(.*?)\{\/if\}#is', $message, $links);
diff -r bed9d04fa144 -r e1a22031b5bd plugins/SpecialAdmin.php
--- a/plugins/SpecialAdmin.php Thu Oct 04 08:22:25 2007 -0400
+++ b/plugins/SpecialAdmin.php Fri Oct 05 01:57:00 2007 -0400
@@ -2834,9 +2834,8 @@
if(isset($_GET['action']) && isset($_GET['id']))
{
- if(preg_match('#^([0-9]*)$#', $_GET['id']))
+ if(!preg_match('#^([0-9]*)$#', $_GET['id']))
{
- } else {
echo '