Add {autoescape} block tag and {raw} inline pseudo tag. Improve auto escaping

This commit is contained in:
bzick 2013-07-07 01:29:33 +04:00
parent b99b874d38
commit 42b71ed644
4 changed files with 100 additions and 18 deletions

View File

@ -33,6 +33,7 @@ class Fenom {
const FORCE_COMPILE = 0xF0; const FORCE_COMPILE = 0xF0;
const DISABLE_CACHE = 0x1F0; const DISABLE_CACHE = 0x1F0;
const AUTO_ESCAPE = 0x200; const AUTO_ESCAPE = 0x200;
const FORCE_VALIDATE = 0x400;
/* Default parsers */ /* Default parsers */
const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose'; const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose';
@ -43,7 +44,7 @@ class Fenom {
/** /**
* @var int[] of possible options, as associative array * @var int[] of possible options, as associative array
* @see setOptions, addOptions, delOptions * @see setOptions
*/ */
private static $_option_list = array( private static $_option_list = array(
"disable_methods" => self::DENY_METHODS, "disable_methods" => self::DENY_METHODS,
@ -52,6 +53,8 @@ class Fenom {
"force_compile" => self::FORCE_COMPILE, "force_compile" => self::FORCE_COMPILE,
"auto_reload" => self::AUTO_RELOAD, "auto_reload" => self::AUTO_RELOAD,
"force_include" => self::FORCE_INCLUDE, "force_include" => self::FORCE_INCLUDE,
"auto_escape" => self::AUTO_ESCAPE,
"force_validate" => self::FORCE_VALIDATE
); );
/** /**
@ -213,6 +216,15 @@ class Fenom {
'cycle' => array( 'cycle' => array(
'type' => self::INLINE_COMPILER, 'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagCycle' 'parser' => 'Fenom\Compiler::tagCycle'
),
'raw' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagRaw'
),
'autoescape' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::autoescapeOpen',
'close' => 'Fenom\Compiler::autoescapeClose'
) )
); );
@ -234,7 +246,6 @@ class Fenom {
throw new InvalidArgumentException("Source must be a valid path or provider object"); throw new InvalidArgumentException("Source must be a valid path or provider object");
} }
$fenom = new static($provider); $fenom = new static($provider);
/* @var Fenom $fytro */
$fenom->setCompileDir($compile_dir); $fenom->setCompileDir($compile_dir);
if($options) { if($options) {
$fenom->setOptions($options); $fenom->setOptions($options);

View File

@ -588,7 +588,7 @@ class Compiler {
* @return string * @return string
*/ */
public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) { public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) {
return "echo $function(".self::toArray($tpl->parseParams($tokens)).', $tpl);'; return "$function(".self::toArray($tpl->parseParams($tokens)).', $tpl)';
} }
/** /**
@ -618,7 +618,7 @@ class Compiler {
$args[] = $param->getDefaultValue(); $args[] = $param->getDefaultValue();
} }
} }
return "echo $function(".implode(", ", $args).');'; return "$function(".implode(", ", $args).')';
} }
/** /**
@ -643,7 +643,7 @@ class Compiler {
* @return string * @return string
*/ */
public static function stdFuncClose($tokens, Scope $scope) { public static function stdFuncClose($tokens, Scope $scope) {
return "echo ".$scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl);'; return $scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl)';
} }
/** /**
@ -866,4 +866,48 @@ class Compiler {
$scope->tpl->_body = substr($scope->tpl->_body, 0, strlen($scope->tpl->_body) - strlen($content)); $scope->tpl->_body = substr($scope->tpl->_body, 0, strlen($scope->tpl->_body) - strlen($content));
} }
/**
* Output value as is, without escaping
*
* @param Tokenizer $tokens
* @param Template $tpl
* @throws InvalidUsageException
* @return string
*/
public static function tagRaw(Tokenizer $tokens, Template $tpl) {
$tpl->escape = false;
if($tokens->is(':')) {
$func = $tokens->getNext(Tokenizer::MACRO_STRING);
$tag = $tpl->getStorage()->getFunction($func);
if($tag["type"] == \Fenom::INLINE_FUNCTION) {
return $tpl->parseAct($tokens);
} elseif ($tag["type"] == \Fenom::BLOCK_FUNCTION) {
$code = $tpl->parseAct($tokens);
$tpl->getLastScope()->escape = false;
return $code;
}
throw new InvalidUsageException("Raw mode allow for expressions or functions");
} else {
return $tpl->out($tpl->parseExp($tokens, true));
}
}
/**
* @param Tokenizer $tokens
* @param Scope $scope
*/
public static function autoescapeOpen(Tokenizer $tokens, Scope $scope) {
$boolean = ($tokens->get(T_STRING) == "true" ? true : false);
$scope["escape"] = $scope->tpl->escape;
$scope->tpl->escape = $boolean;
$tokens->next();
}
/**
* @param Tokenizer $tokens
* @param Scope $scope
*/
public static function autoescapeClose(Tokenizer $tokens, Scope $scope) {
$scope->tpl->escape = $scope["escape"];
}
} }

View File

@ -25,9 +25,11 @@ class Scope extends \ArrayObject {
public $tpl; public $tpl;
public $is_compiler = true; public $is_compiler = true;
public $is_closed = false; public $is_closed = false;
public $escape = false;
private $_action; private $_action;
private $_body; private $_body;
private $_offset; private $_offset;
private $_global_escape = false;
/** /**
* Creating cope * Creating cope
@ -56,6 +58,8 @@ class Scope extends \ArrayObject {
public function setFuncName($function) { public function setFuncName($function) {
$this["function"] = $function; $this["function"] = $function;
$this->is_compiler = false; $this->is_compiler = false;
$this->_global_escape = $this->tpl->escape;
$this->tpl->escape = false;
} }
/** /**
@ -104,6 +108,9 @@ class Scope extends \ArrayObject {
* @return string * @return string
*/ */
public function close($tokenizer) { public function close($tokenizer) {
if(!$this->is_compiler) {
$this->tpl->escape = $this->_global_escape;
}
return call_user_func($this->_action["close"], $tokenizer, $this); return call_user_func($this->_action["close"], $tokenizer, $this);
} }

View File

@ -58,6 +58,11 @@ class Template extends Render {
public $parents = array(); public $parents = array();
/**
* Escape output value
* @var bool
*/
public $escape = false;
public $_extends; public $_extends;
public $_extended = false; public $_extended = false;
public $_compatible; public $_compatible;
@ -159,6 +164,7 @@ class Template extends Render {
*/ */
public function compile() { public function compile() {
$end = $pos = 0; $end = $pos = 0;
$this->escape = $this->_options & Fenom::AUTO_ESCAPE;
while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-symbol of tags while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-symbol of tags
switch($this->_src[$start + 1]) { // check next character switch($this->_src[$start + 1]) { // check next character
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
@ -397,11 +403,11 @@ class Template extends Render {
* @param $data * @param $data
* @return string * @return string
*/ */
private function _print($data) { public function out($data) {
if($this->_options & Fenom::AUTO_ESCAPE) { if($this->escape) {
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8')"; return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');";
} else { } else {
return "echo $data"; return "echo $data;";
} }
} }
/** /**
@ -420,14 +426,14 @@ class Template extends Render {
$tokens->next(); $tokens->next();
return ''; return '';
} else { } else {
return $this->_parseAct($tokens); return $this->parseAct($tokens);
} }
} elseif ($tokens->is('/')) { } elseif ($tokens->is('/')) {
return $this->_end($tokens); return $this->_end($tokens);
} elseif ($tokens->is('#')) { } elseif ($tokens->is('#')) {
return $this->_print($this->parseConst($tokens), $tokens).';'; return $this->out($this->parseConst($tokens), $tokens).';';
} else { } else {
return $code = $this->_print($this->parseExp($tokens), $tokens).";"; return $code = $this->out($this->parseExp($tokens), $tokens).";";
} }
} catch (InvalidUsageException $e) { } catch (InvalidUsageException $e) {
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e); throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e);
@ -442,7 +448,7 @@ class Template extends Render {
* Close tag handler * Close tag handler
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @return mixed * @return string
* @throws TokenizeException * @throws TokenizeException
*/ */
private function _end(Tokenizer $tokens) { private function _end(Tokenizer $tokens) {
@ -457,7 +463,20 @@ class Template extends Render {
if($scope->name !== $name) { if($scope->name !== $name) {
throw new TokenizeException("Unexpected closing of the tag '$name' (expecting closing of the tag {$scope->name}, opened on line {$scope->line})"); throw new TokenizeException("Unexpected closing of the tag '$name' (expecting closing of the tag {$scope->name}, opened on line {$scope->line})");
} }
return $scope->close($tokens); if($scope->is_compiler) {
return $scope->close($tokens);
} else {
$scope->tpl->escape = $scope->escape;
return $this->out($scope->close($tokens));
}
}
/**
* Get current scope
* @return Scope
*/
public function getLastScope() {
return end($this->_stack);
} }
/** /**
@ -469,16 +488,16 @@ class Template extends Render {
* @throws TokenizeException * @throws TokenizeException
* @return string * @return string
*/ */
private function _parseAct(Tokenizer $tokens) { public function parseAct(Tokenizer $tokens) {
if($tokens->is(Tokenizer::MACRO_STRING)) { if($tokens->is(Tokenizer::MACRO_STRING)) {
$action = $tokens->getAndNext(); $action = $tokens->getAndNext();
} else { } else {
return $this->_print($this->parseExp($tokens), $tokens).';'; // may be math and/or boolean expression return $this->out($this->parseExp($tokens), $tokens).';'; // may be math and/or boolean expression
} }
if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method
$tokens->back(); $tokens->back();
return $this->_print($this->parseExp($tokens), $tokens).";"; return $this->out($this->parseExp($tokens), $tokens).";";
} elseif($tokens->is('.')) { } elseif($tokens->is('.')) {
$name = $tokens->skip()->get(Tokenizer::MACRO_STRING); $name = $tokens->skip()->get(Tokenizer::MACRO_STRING);
if($action !== "macro") { if($action !== "macro") {
@ -499,11 +518,12 @@ class Template extends Render {
case Fenom::INLINE_COMPILER: case Fenom::INLINE_COMPILER:
return call_user_func($act["parser"], $tokens, $this); return call_user_func($act["parser"], $tokens, $this);
case Fenom::INLINE_FUNCTION: case Fenom::INLINE_FUNCTION:
return call_user_func($act["parser"], $act["function"], $tokens, $this); return $this->out(call_user_func($act["parser"], $act["function"], $tokens, $this));
case Fenom::BLOCK_FUNCTION: case Fenom::BLOCK_FUNCTION:
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body); $scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
$scope->setFuncName($act["function"]); $scope->setFuncName($act["function"]);
array_push($this->_stack, $scope); array_push($this->_stack, $scope);
$scope->escape = $this->_options & Fenom::AUTO_ESCAPE;
return $scope->open($tokens); return $scope->open($tokens);
default: default:
throw new \LogicException("Unknown function type"); throw new \LogicException("Unknown function type");