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 DISABLE_CACHE = 0x1F0;
const AUTO_ESCAPE = 0x200;
const FORCE_VALIDATE = 0x400;
/* Default parsers */
const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose';
@ -43,7 +44,7 @@ class Fenom {
/**
* @var int[] of possible options, as associative array
* @see setOptions, addOptions, delOptions
* @see setOptions
*/
private static $_option_list = array(
"disable_methods" => self::DENY_METHODS,
@ -52,6 +53,8 @@ class Fenom {
"force_compile" => self::FORCE_COMPILE,
"auto_reload" => self::AUTO_RELOAD,
"force_include" => self::FORCE_INCLUDE,
"auto_escape" => self::AUTO_ESCAPE,
"force_validate" => self::FORCE_VALIDATE
);
/**
@ -213,6 +216,15 @@ class Fenom {
'cycle' => array(
'type' => self::INLINE_COMPILER,
'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");
}
$fenom = new static($provider);
/* @var Fenom $fytro */
$fenom->setCompileDir($compile_dir);
if($options) {
$fenom->setOptions($options);

View File

@ -588,7 +588,7 @@ class Compiler {
* @return string
*/
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();
}
}
return "echo $function(".implode(", ", $args).');';
return "$function(".implode(", ", $args).')';
}
/**
@ -643,7 +643,7 @@ class Compiler {
* @return string
*/
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));
}
/**
* 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 $is_compiler = true;
public $is_closed = false;
public $escape = false;
private $_action;
private $_body;
private $_offset;
private $_global_escape = false;
/**
* Creating cope
@ -56,6 +58,8 @@ class Scope extends \ArrayObject {
public function setFuncName($function) {
$this["function"] = $function;
$this->is_compiler = false;
$this->_global_escape = $this->tpl->escape;
$this->tpl->escape = false;
}
/**
@ -104,6 +108,9 @@ class Scope extends \ArrayObject {
* @return string
*/
public function close($tokenizer) {
if(!$this->is_compiler) {
$this->tpl->escape = $this->_global_escape;
}
return call_user_func($this->_action["close"], $tokenizer, $this);
}

View File

@ -58,6 +58,11 @@ class Template extends Render {
public $parents = array();
/**
* Escape output value
* @var bool
*/
public $escape = false;
public $_extends;
public $_extended = false;
public $_compatible;
@ -159,6 +164,7 @@ class Template extends Render {
*/
public function compile() {
$end = $pos = 0;
$this->escape = $this->_options & Fenom::AUTO_ESCAPE;
while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-symbol of tags
switch($this->_src[$start + 1]) { // check next character
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
@ -397,11 +403,11 @@ class Template extends Render {
* @param $data
* @return string
*/
private function _print($data) {
if($this->_options & Fenom::AUTO_ESCAPE) {
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8')";
public function out($data) {
if($this->escape) {
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');";
} else {
return "echo $data";
return "echo $data;";
}
}
/**
@ -420,14 +426,14 @@ class Template extends Render {
$tokens->next();
return '';
} else {
return $this->_parseAct($tokens);
return $this->parseAct($tokens);
}
} elseif ($tokens->is('/')) {
return $this->_end($tokens);
} elseif ($tokens->is('#')) {
return $this->_print($this->parseConst($tokens), $tokens).';';
return $this->out($this->parseConst($tokens), $tokens).';';
} else {
return $code = $this->_print($this->parseExp($tokens), $tokens).";";
return $code = $this->out($this->parseExp($tokens), $tokens).";";
}
} catch (InvalidUsageException $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
*
* @param Tokenizer $tokens
* @return mixed
* @return string
* @throws TokenizeException
*/
private function _end(Tokenizer $tokens) {
@ -457,7 +463,20 @@ class Template extends Render {
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})");
}
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
* @return string
*/
private function _parseAct(Tokenizer $tokens) {
public function parseAct(Tokenizer $tokens) {
if($tokens->is(Tokenizer::MACRO_STRING)) {
$action = $tokens->getAndNext();
} 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
$tokens->back();
return $this->_print($this->parseExp($tokens), $tokens).";";
return $this->out($this->parseExp($tokens), $tokens).";";
} elseif($tokens->is('.')) {
$name = $tokens->skip()->get(Tokenizer::MACRO_STRING);
if($action !== "macro") {
@ -499,11 +518,12 @@ class Template extends Render {
case Fenom::INLINE_COMPILER:
return call_user_func($act["parser"], $tokens, $this);
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:
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
$scope->setFuncName($act["function"]);
array_push($this->_stack, $scope);
$scope->escape = $this->_options & Fenom::AUTO_ESCAPE;
return $scope->open($tokens);
default:
throw new \LogicException("Unknown function type");