Kaynağa Gözat

Template variable values are not HTML-excaped by default.

Taddeus Kroes 13 yıl önce
ebeveyn
işleme
cc2ee3449a
3 değiştirilmiş dosya ile 45 ekleme ve 10 silme
  1. 0 3
      TODO.txt
  2. 25 7
      template.php
  3. 20 0
      tests/test_template.php

+ 0 - 3
TODO.txt

@@ -1,3 +0,0 @@
-Template:
-- Escape variable values by default, prevent escape using $$ in template instead of $.
-- Be less 'forgiving': throw errors if requested constant/variable does not exist.

+ 25 - 7
template.php

@@ -267,7 +267,7 @@ class Template extends Node {
 	 * 
 	 * This function is a helper for {@link evaluate_expression()}.
 	 * 
-	 * @param array $matches Regex matches for variable pattern.
+	 * @param string[] $matches Regex matches for variable pattern.
 	 * @return string The evaluation of the variable.
 	 * @param Node $data A data tree containing variable values to use.
 	 * @throws \BadMethodCallException If an error occured while calling a variable method.
@@ -276,12 +276,13 @@ class Template extends Node {
 	 */
 	private static function evaluate_variable(array $matches, Node $data) {
 		$before = $matches[1];
-		$variable = $matches[2];
+		$noescape_sign = $matches[2];
+		$variable = $matches[3];
 		$value = $data->get($variable);
 		
-		if( count($matches) == 4 ) {
+		if( count($matches) == 5 ) {
 			// $<name>.<name>
-			$attribute = $matches[3];
+			$attribute = $matches[4];
 			
 			if( $value === null ) {
 				throw new \UnexpectedValueException(
@@ -304,9 +305,9 @@ class Template extends Node {
 			} else {
 				$attr_error('variable is no array or object');
 			}
-		} elseif( count($matches) == 5 ) {
+		} elseif( count($matches) == 6 ) {
 			// $<name>.<name>()
-			$method = $matches[3];
+			$method = $matches[4];
 			
 			if( $value === null ) {
 				throw new \UnexpectedValueException(
@@ -328,9 +329,25 @@ class Template extends Node {
 			}
 		}
 		
+		// Escape value
+		if( is_string($value) && !$noescape_sign )
+			$value = self::escape_variable_value($value);
+		
 		return $before . $value;
 	}
 	
+	/**
+	 * Escape a vairable value for displaying in HTML.
+	 * 
+	 * Uses {@link http://php.net/htmlentities} with ENT_QUOTES.
+	 * 
+	 * @param string $value The variable value to escape.
+	 * @return string The escaped value.
+	 */
+	private static function escape_variable_value($value) {
+		return htmlentities($value, ENT_QUOTES);
+	}
+	
 	/**
 	 * Evaluate a conditional expression.
 	 * 
@@ -420,8 +437,9 @@ class Template extends Node {
 			if( preg_match("/^([^?]*?)\s*\?([^:]*)(?::(.*))?$/", $expression, $matches) ) {
 				// <nested_exp>?<nested_exp> | <nested_exp>?<nested_exp>:<nested_exp>
 				return self::evaluate_condition($matches, $data);
-			} elseif( preg_match("/^(.*?)\\$($name)(?:\.($name)(\(\))?)?$/", $expression, $matches) ) {
+			} elseif( preg_match("/^(.*?)\\$(\\$?)($name)(?:\.($name)(\(\))?)?$/", $expression, $matches) ) {
 				// $<name> | $<name>.<name> | $<name>.<name>()
+				// | $$<name> | $$<name>.<name> | $$<name>.<name>()
 				return self::evaluate_variable($matches, $data);
 			} elseif( preg_match("/^($function)\((.+?)\)?$/", $expression, $matches) ) {
 				// <function>(<nested_exp>)

+ 20 - 0
tests/test_template.php

@@ -21,6 +21,8 @@ class DataObject {
 }
 
 class TemplateTest extends PHPUnit_Framework_TestCase {
+	const INTERNATIONALIZATION_STRING = 'Iñtërnâtiônàlizætiøn';
+	
 	/**
 	 * @depends test_add_root_success
 	 */
@@ -43,6 +45,8 @@ class TemplateTest extends PHPUnit_Framework_TestCase {
 			'object' => new DataObject,
 			'foobar' => 'my_foobar_variable',
 			'foobaz' => 'MY_FOOBAZ_VARIABLE',
+			'html' => '<script></script>',
+			'internationalization' => self::INTERNATIONALIZATION_STRING,
 		));
 	}
 	
@@ -284,6 +288,22 @@ class TemplateTest extends PHPUnit_Framework_TestCase {
 		$this->assert_evaluates('foobar', '$object.baz()');
 	}
 	
+	/** 
+	 * @depends test_evaluate_variable_success
+	 */
+	function test_evaluate_variable_escape() {
+		$this->assert_evaluates('&lt;script&gt;&lt;/script&gt;', '$html');
+		$this->assert_evaluates('I&ntilde;t&euml;rn&acirc;ti&ocirc;n&agrave;liz&aelig;ti&oslash;n', '$internationalization');
+	}
+	
+	/** 
+	 * @depends test_evaluate_variable_success
+	 */
+	function test_evaluate_variable_noescape() {
+		$this->assert_evaluates('<script></script>', '$$html');
+		$this->assert_evaluates('Iñtërnâtiônàlizætiøn', '$$internationalization');
+	}
+	
 	function test_evaluate_constant() {
 		$this->assert_evaluates('foobar_const', 'FOOBAR');
 		$this->assert_evaluates('{NON_DEFINED_CONST}', 'NON_DEFINED_CONST');