Commit e25e1870 by Seldaek

+ Syntax: Added support for method calls on objects i.e. {$foo->bar()}

+ Added support for smarty security features, see the DwooSecurityPolicy class and $dwoo->setSecurityPolicy() + API: Added a DwooCompiler->setLooseOpeningHandling() method that, if set to true, allows tags to * Fixed {elseif} bug that appeared when multiple elseif tags were used in a row * Syntax: Improved simple math support to work within variable variables (i.e. you can do {$array[$index+1]}) and within strings as well. To prevent this enclose the variables in backticks (i.e. {"$foo/$bar"} will do the math while {"`$foo`/$bar"} won't as $foo is properly delimited) git-svn-id: svn://dwoo.org/dwoo/trunk@4 0598d79b-80c4-4d41-97ba-ac86fbbd088b
parent e31da1c6
[2008--] 0.3.4
+ Syntax: Added support for method calls on objects i.e. {$foo->bar()}
+ Added support for smarty security features, see the DwooSecurityPolicy class
and $dwoo->setSecurityPolicy()
+ API: Added a DwooCompiler->setLooseOpeningHandling() method that, if set to
true, allows tags to
* Fixed {elseif} bug that appeared when multiple elseif tags were used in a row
* Syntax: Improved simple math support to work within variable variables
(i.e. you can do {$array[$index+1]}) and within strings as well. To prevent
this enclose the variables in backticks (i.e. {"$foo/$bar"} will do the math
while {"`$foo`/$bar"} won't as $foo is properly delimited)
[2008-03-19] 0.3.3
+ Syntax: Added support for $dwoo.const.CONSTANT and
$dwoo.const.Class::CONSTANT to read PHP constants from the template
......
......@@ -17,6 +17,10 @@ DwooLoader::loadPlugin('topLevelBlock');
/**
* main dwoo class, allows communication between the compiler, template and data classes
*
* requirements :
* php 5.2.0 or above
* php's mbstring extension for some plugins
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
*
......@@ -130,6 +134,13 @@ class Dwoo
*/
protected $cacheTime = 0;
/**
* security policy object
*
* @var DwooSecurityPolicy
*/
protected $securityPolicy = null;
/**
* stores the custom plugins callbacks
*
......@@ -159,12 +170,12 @@ class Dwoo
'file' => array
(
'class' => 'DwooTemplateFile',
'compiler' => array('DwooCompiler', 'compilerFactory')
'compiler' => null
),
'string' => array
(
'class' => 'DwooTemplateString',
'compiler' => array('DwooCompiler', 'compilerFactory')
'compiler' => null
)
);
......@@ -306,11 +317,11 @@ class Dwoo
{
$compiler = $this->resources[$tpl->getResourceName()]['compiler'];
if($compiler === null)
if($compiler === null || $compiler === array('DwooCompiler', 'compilerFactory'))
{
if(class_exists('DwooCompiler', false) === false)
include DWOO_PATH . 'DwooCompiler.php';
$compiler = DwooCompiler::getInstance();
$compiler = DwooCompiler::compilerFactory();
}
else
$compiler = call_user_func($compiler);
......@@ -511,9 +522,9 @@ class Dwoo
*
* @param string $name the resource name
* @param string $class the resource class (which must implement DwooITemplate)
* @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance used to compile this resource, if none is provided
* @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance used to compile this resource, if none is provided. by default it will produce a DwooCompiler object
*/
public function addResource($name, $class, $compilerFactory = array('DwooCompiler', 'compilerFactory'))
public function addResource($name, $class, $compilerFactory = null)
{
if(strlen($name) < 2)
throw new Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths', E_USER_NOTICE);
......@@ -534,7 +545,7 @@ class Dwoo
{
unset($this->resources[$name]);
if($name==='file')
$this->resources['file'] = array('class'=>'DwooTemplateFile', 'compiler'=>array('DwooCompiler', 'compilerFactory'));
$this->resources['file'] = array('class'=>'DwooTemplateFile', 'compiler'=>null);
}
/* public function addConfig(array $config)
......@@ -700,6 +711,28 @@ class Dwoo
return $this->resources[$resourceName]['compiler'];
}
/**
* sets the security policy object to enforce some php security settings
*
* use this if untrusted persons can modify templates
*
* @param DwooSecurityPolicy $policy the security policy object
*/
public function setSecurityPolicy(DwooSecurityPolicy $policy = null)
{
$this->securityPolicy = $policy;
}
/**
* returns the current security policy object or null by default
*
* @return DwooSecurityPolicy|null the security policy object if any
*/
public function getSecurityPolicy()
{
return $this->securityPolicy;
}
/*
* --------- util functions ---------
*/
......@@ -723,7 +756,8 @@ class Dwoo
*/
public function clearCache($olderThan=0)
{
$cache = new RecursiveDirectoryIterator($this->cacheDir);
$cacheDirs = new RecursiveDirectoryIterator($this->cacheDir);
$cache = new RecursiveIteratorIterator($cacheDirs);
$expired = time() - $olderThan;
$count = 0;
foreach($cache as $file)
......@@ -1485,4 +1519,204 @@ class DwooLoader
}
}
/**
* represents the security settings of a dwoo instance, it can be passed around to different dwoo instances
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
*
* This file is released under the LGPL
* "GNU Lesser General Public License"
* More information can be found here:
* {@link http://www.gnu.org/copyleft/lesser.html}
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @copyright Copyright (c) 2008, Jordi Boggiano
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @link http://dwoo.org/
* @version 0.3.3
* @date 2008-03-19
* @package Dwoo
*/
class DwooSecurityPolicy
{
/**#@+
* php handling constants, defaults to PHP_REMOVE
*
* PHP_REMOVE : remove all <?php ?> (+ short tags if your short tags option is on) from the input template
* PHP_ALLOW : leave them as they are
* PHP_ENCODE : run htmlentities over them
*
* @var int
*/
const PHP_ENCODE = 1;
const PHP_REMOVE = 2;
const PHP_ALLOW = 3;
/**#@-*/
/**#@+
* constant handling constants, defaults to CONST_DISALLOW
*
* CONST_DISALLOW : throw an error if {$dwoo.const.*} is used in the template
* CONST_ALLOW : allow {$dwoo.const.*} calls
*/
const CONST_DISALLOW = false;
const CONST_ALLOW = true;
/**#@-*/
/**
* php functions that are allowed to be used within the template
*
* @var array
*/
protected $allowedPhpFunctions = array
(
'str_repeat', 'count', 'number_format', 'htmlentities', 'htmlspecialchars',
'long2ip', 'strlen', 'list', 'empty', 'count', 'sizeof', 'in_array', 'is_array',
);
/**
* paths that are safe to use with include or other file-access plugins
*
* @var array
*/
protected $allowedDirectories = array();
/**
* stores the php handling level
*
* defaults to DwooSecurityPolicy::PHP_REMOVE
*
* @var int
*/
protected $phpHandling = self::PHP_REMOVE;
/**
* stores the constant handling level
*
* defaults to DwooSecurityPolicy::CONST_DISALLOW
*
* @var bool
*/
protected $constHandling = self::CONST_DISALLOW;
/**
* adds a php function to the allowed list
*
* @param mixed $func function name or array of function names
*/
public function allowPhpFunction($func)
{
if(is_array($func))
foreach($func as $fname)
$this->allowedPhpFunctions[strtolower($fname)] = true;
else
$this->allowedPhpFunctions[strtolower($func)] = true;
}
/**
* removes a php function from the allowed list
*
* @param mixed $func function name or array of function names
*/
public function disallowPhpFunction($func)
{
if(is_array($func))
foreach($func as $fname)
unset($this->allowedPhpFunctions[strtolower($fname)]);
else
unset($this->allowedPhpFunctions[strtolower($func)]);
}
/**
* returns the list of php functions allowed to run, note that the function names
* are stored in the array keys and not values
*
* @return array
*/
public function getAllowedPhpFunctions()
{
return $this->allowedPhpFunctions;
}
/**
* adds a directory to the safelist for includes and other file-access plugins
*
* @param mixed $path a path name or an array of paths
*/
public function allowDirectory($path)
{
if(is_array($path))
foreach($path as $dir)
$this->allowedDirectories[realpath($dir)] = true;
else
$this->allowedDirectories[realpath($path)] = true;
}
/**
* removes a directory from the safelist
*
* @param mixed $path a path name or an array of paths
*/
public function disallowDirectory($path)
{
if(is_array($path))
foreach($path as $dir)
unset($this->allowedDirectories[realpath($dir)]);
else
unset($this->allowedDirectories[realpath($path)]);
}
/**
* returns the list of safe paths, note that the paths are stored in the array
* keys and not values
*
* @return array
*/
public function getAllowedDirectories()
{
return $this->allowedPHPFunc;
}
/**
* sets the php handling level, defaults to REMOVE
*
* @param int $level one of the DwooSecurityPolicy::PHP_* constants
*/
public function setPhpHandling($level = self::PHP_REMOVE)
{
$this->phpHandling = $level;
}
/**
* returns the php handling level
*
* @return int the current level, one of the DwooSecurityPolicy::PHP_* constants
*/
public function getPhpHandling()
{
return $this->phpHandling;
}
/**
* sets the constant handling level, defaults to CONST_DISALLOW
*
* @param bool $level one of the DwooSecurityPolicy::CONST_* constants
*/
public function setConstantHandling($level = self::CONST_DISALLOW)
{
$this->constHandling = $level;
}
/**
* returns the constant handling level
*
* @return bool the current level, one of the DwooSecurityPolicy::CONST_* constants
*/
public function getConstantHandling()
{
return $this->constHandling;
}
}
?>
\ No newline at end of file
......@@ -75,21 +75,29 @@ class DwooCompiler implements DwooICompiler
protected $rdr = '\\}';
/**
* storage for parse errors/warnings
* defines whether opening and closing tags can contain spaces before valid data or not
*
* will be deprecated when proper exceptions are added
* turn to true if you want to be sloppy with the syntax, but when set to false it allows
* to skip javascript and css tags as long as they are in the form "{ something", which is
* nice. default is false.
*/
protected $allowLooseOpenings = false;
/**
* security policy object
*
* @var array
* @var DwooSecurityPolicy
*/
protected $errors = array();
protected $securityPolicy;
/**
* boolean flag determining whether to use smarty compatibility
* tweaks or not, will probably become deprecated at some point
* storage for parse errors/warnings
*
* @var bool
* will be deprecated when proper exceptions are added
*
* @var array
*/
public $smartyCompat = true;
protected $errors = array();
/**
* stores the custom plugins registered with this compiler
......@@ -186,18 +194,6 @@ class DwooCompiler implements DwooICompiler
* @var DwooCompiler
*/
protected static $instance;
/**
* php functions that are allowed to be used within the template
*
* @var array
*/
protected $allowedPHPFunc = array
(
// TODO move this into a security policy class
'str_repeat','count','number_format','htmlentities','htmlspecialchars',
'long2ip', 'strlen',
);
/**
* sets the delimiters to use in the templates
......@@ -226,30 +222,30 @@ class DwooCompiler implements DwooICompiler
return array($this->ld, $this->rd);
}
/**
* adds a php function to the allowed list
*
* @deprecated will be replaced by a security policy class
* @param callback $func function name
*/
public function addPhpFunction($func)
/**
* sets the tag openings handling strictness, if set to true, template tags can
* contain spaces before the first function/string/variable such as { $foo} is valid.
*
* if set to false (default setting), { $foo} is invalid but that is however a good thing
* as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering
* an error, same goes for javascript.
*
* @param bool $allow true to allow loose handling, false to restore default setting
*/
public function setLooseOpeningHandling($allow = false)
{
if(!is_string($func) || !function_exists($func))
throw new Exception('Callback must be a valid function name (string)', E_USER_NOTICE);
$this->allowedPHPFunc[] = strtolower($func);
$this->allowLooseOpenings = (bool) $allow;
}
/**
* removes a php function from the allowed list
*
* @deprecated will be replaced by a security policy class
* @param callback $func function name
/**
* returns the tag openings handling strictness setting
*
* @see setLooseOpeningHandling
* @return bool true if loose tags are allowed
*/
public function removePhpFunction($func)
public function getLooseOpeningHandling()
{
if(($index = array_search(strtolower($func), $this->allowedPHPFunc, true)) !== false)
unset($this->allowedPHPFunc[$index]);
return $this->allowLooseOpenings;
}
/**
......@@ -378,6 +374,28 @@ class DwooCompiler implements DwooICompiler
}
/**
* sets the security policy object to enforce some php security settings
*
* use this if untrusted persons can modify templates
*
* @param DwooSecurityPolicy $policy the security policy object
*/
public function setSecurityPolicy(DwooSecurityPolicy $policy = null)
{
$this->securityPolicy = $policy;
}
/**
* returns the current security policy object or null by default
*
* @return DwooSecurityPolicy|null the security policy object if any
*/
public function getSecurityPolicy()
{
return $this->securityPolicy;
}
/**
* compiles the provided string down to php code
*
* @param string $tpl the template to compile
......@@ -392,12 +410,6 @@ class DwooCompiler implements DwooICompiler
$this->scopeTree = array();
$this->stack = array();
// adds a smarty compat preprocessor if required
if($this->smartyCompat)
{
$this->addPreProcessor('smarty_compat', true);
}
if($this->debug) echo 'PROCESSING PREPROCESSORS<br>';
// runs preprocessors
......@@ -421,11 +433,29 @@ class DwooCompiler implements DwooICompiler
// strips comments
if(strstr($tpl, $this->ld.'*') !== false)
$tpl = preg_replace('#'.$this->ldr.'\*.*?\*'.$this->rdr.'#s', '', $tpl);
$tpl = preg_replace('{'.$this->ldr.'\*.*?\*'.$this->rdr.'}s', '', $tpl);
// strips php tags if required by the security policy
if($this->securityPolicy !== null)
{
$search = array('{<\?php.*?\?>}');
if(ini_get('short_open_tags'))
$search = array('{<\?.*?\?>}', '{<%.*?%>}');
switch($this->securityPolicy->getPhpHandling())
{
case DwooSecurityPolicy::PHP_ALLOW:
break;
case DwooSecurityPolicy::PHP_ENCODE:
$tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl);
break;
case DwooSecurityPolicy::PHP_REMOVE:
$tpl = preg_replace($search, '', $tpl);
}
}
// handles the built-in strip function
if(($pos = strpos($tpl, $this->ld.'strip'.$this->rd)) !== false && substr($tpl, $pos-1, 1) !== '\\')
$tpl = preg_replace_callback('#'.$this->ldr.'strip'.$this->rdr.'(.+?)'.$this->ldr.'/strip'.$this->rdr.'#s', array($this, 'stripPreprocessorHelper'), $tpl);
$tpl = preg_replace_callback('{'.$this->ldr.'strip'.$this->rdr.'(.+?)'.$this->ldr.'/strip'.$this->rdr.'}s', array($this, 'stripPreprocessorHelper'), $tpl);
while(true)
{
......@@ -468,15 +498,32 @@ class DwooCompiler implements DwooICompiler
$tpl = substr_replace($tpl, $this->rd, $endpos-1, 1+strlen($this->rd));
$endpos = strpos($tpl, $this->rd, $endpos);
}
$pos += strlen($this->ld);
if($this->allowLooseOpenings)
{
while(substr($tpl, $pos, 1) === ' ')
$pos+=1;
}
else
{
if(substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n")
{
$ptr = $pos;
$compiled .= $this->ld;
continue;
}
}
$ptr = $endpos+strlen($this->rd);
if(substr($tpl, $pos+strlen($this->ld), 1)==='/')
$compiled .= $this->removeBlock(substr($tpl, $pos+strlen($this->ld)+1, $endpos-$pos-1-strlen($this->ld)));
if(substr($tpl, $pos, 1)==='/')
$compiled .= $this->removeBlock(substr($tpl, $pos+1, $endpos-$pos-1));
else
$compiled .= $this->parse($tpl, $pos+strlen($this->ld), $endpos, false, 'root');
$compiled .= $this->parse($tpl, $pos, $endpos, false, 'root');
// adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break
if(substr($compiled, -2) === '?>' && preg_match('#^(([\r\n])([\r\n]?))#', substr($tpl, $ptr, 3), $m))
if(substr($compiled, -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m))
{
if($m[3] === '')
{
......@@ -536,17 +583,14 @@ class DwooCompiler implements DwooICompiler
throw new Exception('Type error for '.$plugin.' with type'.$type, E_USER_NOTICE);
}
}
if($this->smartyCompat)
{
$output .= '$_tag_stack = array();'."\n";
}
$output .= $compiled."\n?>";
$output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
if($this->debug) {
echo '<hr><pre>';
$lines = preg_split('#\r\n|\n#', htmlentities($output));
$lines = preg_split('{\r\n|\n}', htmlentities($output));
foreach($lines as $i=>$line)
echo ($i+1).'. '.$line."\r\n";
}
......@@ -897,7 +941,7 @@ class DwooCompiler implements DwooICompiler
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
*/
protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
......@@ -1007,7 +1051,7 @@ class DwooCompiler implements DwooICompiler
if($this->debug) echo 'FUNC ADDS '.((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : 0) + strlen($func)).' TO POINTER<br/>';
}
if(array_search($func, $this->allowedPHPFunc, true) !== false)
if($curBlock === 'method')
{
$pluginType = Dwoo::NATIVE_PLUGIN;
}
......@@ -1073,8 +1117,10 @@ class DwooCompiler implements DwooICompiler
$p = $p[0];
if($pluginType & Dwoo::NATIVE_PLUGIN)
{
$params = $params['*'];
$output = $func.'('.implode(', ', $params).')';
if(isset($params['*']))
$output = $func.'('.implode(', ', $params['*']).')';
else
$output = $func.'()';
}
elseif($pluginType & Dwoo::FUNC_PLUGIN)
{
......@@ -1153,6 +1199,8 @@ class DwooCompiler implements DwooICompiler
}
elseif($curBlock === 'namedparam')
return array($output, $output);
elseif($curBlock === 'method')
return $output;
else
return self::PHP_OPEN.'echo '.$output.';'.self::PHP_CLOSE;
}
......@@ -1245,26 +1293,42 @@ class DwooCompiler implements DwooICompiler
{
$substr = substr($in, $from, $to-$from);
if(preg_match('#(\$?[a-z0-9_:]+(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R))\]))*)'.
($curBlock==='root' || $curBlock==='function' ? '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+|[0-9.,]*))*)':'()').
($curBlock!=='modifier'? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^\s`"\']*))*))+)?':'(())').
if(preg_match('#(\$?[a-z0-9_:]+(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R))\]))*)' . // var key
($curBlock==='root' || $curBlock==='function' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='expression' ? '(\([^)]*?\)(?:->[a-z0-9_]+(?:\([^)]*?\))?)*)?' : '()') . // method call
($curBlock==='root' || $curBlock==='function' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='string' ? '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)':'()') . // simple math expressions
($curBlock!=='modifier'? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\5|:[^\s`"\']*))*))+)?':'(())') . // modifiers
'#i', $substr, $match))
{
$key = substr($match[1],1);
$matchedLength = strlen($match[0]);
$hasModifiers = isset($match[3]);
$hasExpression = isset($match[2]) && !empty($match[2]);
$hasModifiers = isset($match[4]) && !empty($match[4]);
$hasExpression = isset($match[3]) && !empty($match[3]);
$hasMethodCall = isset($match[2]) && !empty($match[2]);
if($hasMethodCall)
{
$key = substr($match[1], 1, strrpos($match[1], '->')-1);
$methodCall = substr($match[1], strrpos($match[1], '->')) . $match[2];
}
if($pointer !== null)
$pointer += $matchedLength;
// Replace useless brackets by dot accessed vars
// replace useless brackets by dot accessed vars
$key = preg_replace('#\[([^\[.->]+)\]#', '.$1', $key);
// prevent $foo->$bar calls because it doesn't seem worth the trouble
if(strpos($key, '->$') !== false)
throw new Exception('You can not access an object\'s property using a variable name.', E_USER_NOTICE);
if($this->debug) echo 'VAR FOUND (len:'.(strlen($key)+1).')<br />';
if($this->debug)
{
if($hasMethodCall)
echo 'METHOD CALL FOUND : $'.$key.$methodCall.'<br />';
else
echo 'VAR FOUND : $'.$key.'<br />';
}
$key = str_replace('"','\\"',$key);
......@@ -1316,9 +1380,30 @@ class DwooCompiler implements DwooICompiler
$output = $this->parseVarKey($key);
}
if($hasMethodCall)
{
preg_match_all('{->([a-z0-9_]+)(\([^)]*\))?}i', $methodCall, $calls);
foreach($calls[1] as $i=>$method)
{
$args = $calls[2][$i];
// property
if($args === '')
$output = '(property_exists($tmp = '.$output.', \''.$method.'\') ? $tmp->'.$method.' : null)';
// method
else
{
if($args === '()')
$parsedCall = '->'.$method.$args;
else
$parsedCall = '->'.$this->parseFunction($method.$args, 0, strlen($method.$args), false, 'method');
$output = '(is_object($tmp = '.$output.') ? (method_exists($tmp, \''.$method.'\') ? $tmp'.$parsedCall.' : $this->triggerError(\'Call to an undefined method : <em>\'.get_class($tmp).\'::'.$method.'()</em>\')) : $this->triggerError(\'Method <em>'.$method.'()</em> was called on a non-object (\'.var_export($tmp, true).\')\'))';
}
}
}
if($hasExpression)
{
preg_match_all('#(?:([+/*%-])(\$[a-z0-9.[\]>_:-]+|[0-9.,]*))#i', $match[2], $expMatch);
preg_match_all('#(?:([+/*%-])(\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))#i', $match[3], $expMatch);
foreach($expMatch[1] as $k=>$operator)
{
......@@ -1336,7 +1421,7 @@ class DwooCompiler implements DwooICompiler
// handle modifiers
if($curBlock !== 'modifier' && $hasModifiers)
{
$output = $this->replaceModifiers(array(null, null, $output, $match[3]), 'var');
$output = $this->replaceModifiers(array(null, null, $output, $match[4]), 'var');
}
if(is_array($parsingParams))
......@@ -1348,7 +1433,7 @@ class DwooCompiler implements DwooICompiler
return array($output, $key);
elseif($curBlock === 'string')
return array($matchedLength, $output);
elseif($curBlock === 'expression')
elseif($curBlock === 'expression' || $curBlock === 'variable')
return $output;
elseif(substr($output, 0, strlen(self::PHP_OPEN)) === self::PHP_OPEN)
return $output;
......@@ -1369,7 +1454,7 @@ class DwooCompiler implements DwooICompiler
/**
* parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time
*
* @param string $key the variable to parse
* @param string $key the variable to parse
* @return string parsed variable
*/
protected function parseVarKey($key)
......@@ -1386,6 +1471,8 @@ class DwooCompiler implements DwooICompiler
}
elseif(preg_match('#dwoo\.const\.([a-z0-9_:-]+)#i', $key, $m))
{
if($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === DwooSecurityPolicy::CONST_DISALLOW)
return 'null';
if(strpos($m[1], ':') !== false)
$output = '(defined("'.$m[1].'") ? constant("'.$m[1].'") : null)';
else
......@@ -1512,10 +1599,20 @@ class DwooCompiler implements DwooICompiler
{
$last = strrpos($key, '$');
}
preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*#i', substr($key, $last), $submatch);
preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'.
'((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
$len = strlen($submatch[0]);
$key = substr_replace($key, preg_replace_callback('#\$([a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)#i', array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)), $last, $len);
$key = substr_replace(
$key,
preg_replace_callback(
'#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'.
'((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i',
array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)
),
$last,
$len
);
if($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
}
......@@ -1539,7 +1636,7 @@ class DwooCompiler implements DwooICompiler
*/
protected function replaceVarKeyHelper($match)
{
return '".'.$this->parseVarKey($match[1]).'."';
return '".'.$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').'."';
}
/**
......@@ -1666,7 +1763,7 @@ class DwooCompiler implements DwooICompiler
$last = strrpos($string, '$');
}
if($string[$last-1]==='\\')
if(array_search($string[$last-1], array('\\', '/', '*', '+', '-', '%')) !== false)
continue;
$var = $this->parse($string, $last, null, false, $curBlock === 'modifier' ? 'modifier' : 'string');
......@@ -1931,9 +2028,10 @@ class DwooCompiler implements DwooICompiler
{
$pluginType = -1;
if(array_search(strtolower($name), $this->allowedPHPFunc, true) !== false)
if(($this->securityPolicy === null && function_exists($name)) ||
($this->securityPolicy !== null && in_array(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false))
{
return Dwoo::NATIVE_PLUGIN;
$phpFunc = true;
}
while($pluginType <= 0)
......@@ -1963,14 +2061,23 @@ class DwooCompiler implements DwooICompiler
else
{
if($pluginType===-1)
DwooLoader::loadPlugin($name);
{
try {
DwooLoader::loadPlugin($name);
} catch (Exception $e) {
if(isset($phpFunc))
$pluginType = Dwoo::NATIVE_PLUGIN;
else
throw $e;
}
}
else
throw new Exception('Plugin "'.$name.'" could not be found');
$pluginType++;
}
}
if(($pluginType & Dwoo::COMPILABLE_PLUGIN) === 0)
if(($pluginType & Dwoo::COMPILABLE_PLUGIN) === 0 && ($pluginType & Dwoo::NATIVE_PLUGIN) === 0)
$this->usedPlugins[$name] = $pluginType;
return $pluginType;
......@@ -1987,7 +2094,18 @@ class DwooCompiler implements DwooICompiler
// TODO make this into a separated plugin
return str_replace(array("\n","\r"), null, preg_replace('#^\s*(.+?)\s*$#m', '$1', $matches[1]));
}
/**
* runs htmlentities over the matched <?php ?> blocks when the security policy enforces that
*
* @param array $match matched php block
* @return string the htmlentities-converted string
*/
protected function phpTagEncodingHelper($match)
{
return htmlspecialchars($match[0]);
}
/**
* maps the parameters received from the template onto the parameters required by the given callback
*
......
......@@ -88,8 +88,7 @@ interface DwooITemplate
* returns the compiled template file name
*
* @param Dwoo $dwoo the dwoo instance that requests it
* @param DwooICompiler $compiler the compiler that must be used, if null a
* DwooCompiler will be used by default
* @param DwooICompiler $compiler the compiler that must be used
* @return string
*/
public function getCompiledTemplate(Dwoo $dwoo, DwooICompiler $compiler);
......
......@@ -16,7 +16,7 @@ if(class_exists('DwooCompiler', false) === false)
require dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DwooCompiler.php';
/**
* A Smarty compatibility layer for Dwoo
* a Smarty compatibility layer for Dwoo
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
......@@ -36,6 +36,7 @@ if(class_exists('DwooCompiler', false) === false)
*/
class DwooSmarty_Adapter extends Dwoo
{
// magic get/set/call functions that handle unsupported features
public function __set($p, $v)
{
if(array_key_exists($p, $this->compat['properties']) !== false)
......@@ -79,71 +80,76 @@ class DwooSmarty_Adapter extends Dwoo
}
}
// list of unsupported properties and methods
protected $compat = array
(
'methods' => array
(
'register_resource', 'unregister_resource', 'load_filter',
'register_resource', 'unregister_resource', 'load_filter', 'clear_compiled_tpl',
'register_object', 'unregister_object', 'get_registered_object',
'clear_compiled_tpl',
'clear_config', 'get_config_vars', 'config_load'
),
'properties' => array
(
'cache_handler_func' => null,
'debugging' => false,
'error_reporting' => null,
'debugging_ctrl' => 'NONE',
'request_vars_order' => 'EGPCS',
'request_use_auto_globals' => true,
'use_sub_dirs' => false,
'default_resource_type' => 'file',
'cache_handler_func' => null,
'autoload_filters' => array(),
'default_template_handler_func' => '',
'debug_tpl' => '',
'trusted_dir' => array(),
'cache_modified_check' => false,
'secure_dir' => array(),
'security_settings' => array(
'PHP_HANDLING' => false,
'IF_FUNCS' => array('array', 'list',
'isset', 'empty',
'count', 'sizeof',
'in_array', 'is_array',
'true', 'false', 'null'),
'INCLUDE_ANY' => false,
'PHP_TAGS' => false,
'MODIFIER_FUNCS' => array('count'),
'ALLOW_CONSTANTS' => false
),
'default_modifiers' => array(),
'default_resource_type' => 'file',
'config_overwrite' => true,
'config_booleanize' => true,
'config_read_hidden' => false,
'config_fix_newlines' => true,
'config_class' => 'Config_File',
'php_handling' => SMARTY_PHP_PASSTHRU,
'security' => false
),
);
// security vars
public $security = false;
public $trusted_dir = array();
public $secure_dir = array();
public $php_handling = SMARTY_PHP_PASSTHRU;
public $security_settings = array
(
'PHP_HANDLING' => false,
'IF_FUNCS' => array
(
'list', 'empty', 'count', 'sizeof',
'in_array', 'is_array',
),
'INCLUDE_ANY' => false,
'PHP_TAGS' => false,
'MODIFIER_FUNCS' => array(),
'ALLOW_CONSTANTS' => false
);
// paths
public $template_dir = 'templates';
public $compile_dir = 'templates_c';
public $config_dir = 'configs';
public $cache_dir = 'cache';
public $plugins_dir = array();
// misc options
public $left_delimiter = '{';
public $right_delimiter = '}';
public $compile_check = true;
public $force_compile = false;
public $caching = 0;
public $cache_lifetime = 3600;
public $compile_id = null;
public $compile_id = null;
public $compiler_file = null;
public $compiler_class = null;
// dwoo/smarty compat layer
public $show_compat_errors = false;
protected $dataProvider;
protected $_filters = array('pre'=>array(), 'post'=>array(), 'output'=>array());
......@@ -166,6 +172,40 @@ class DwooSmarty_Adapter extends Dwoo
public function fetch($filename, $cacheId=null, $compileId=null, $display=false)
{
if($this->security)
{
$policy = new DwooSecurityPolicy();
$policy->addPhpFunction(array_merge($this->security_settings['IF_FUNCS'], $this->security_settings['MODIFIER_FUNCS']));
$phpTags = $this->security_settings['PHP_HANDLING'] ? SMARTY_PHP_ALLOW : $this->php_handling;
if($this->security_settings['PHP_TAGS'])
$phpTags = SMARTY_PHP_ALLOW;
switch($phpTags)
{
case SMARTY_PHP_ALLOW:
case SMARTY_PHP_PASSTHRU:
$phpTags = DwooSecurityPolicy::PHP_ALLOW;
break;
case SMARTY_PHP_QUOTE:
$phpTags = DwooSecurityPolicy::PHP_ENCODE;
break;
case SMARTY_PHP_REMOVE:
default:
$phpTags = DwooSecurityPolicy::PHP_REMOVE;
break;
}
$policy->setPhpHandling($phpTags);
$policy->setConstantHandling($this->security_settings['ALLOW_CONSTANTS']);
if($this->security_settings['INCLUDE_ANY'])
$policy->allowDirectory(preg_replace('{^((?:[a-z]:)?[\\\\/]).*}i', '$1', __FILE__));
else
$policy->allowDirectory($this->secure_dir);
$this->setSecurityPolicy($policy);
}
if(!empty($this->plugins_dir))
foreach($this->plugins_dir as $dir)
DwooLoader::addDirectory(rtrim($dir, '\\/'));
......@@ -184,7 +224,12 @@ class DwooSmarty_Adapter extends Dwoo
if($this->compiler_file !== null && !class_exists($this->compiler_class, false))
include $this->compiler_file;
$this->compiler = new $this->compiler_class;
}
}
else
{
$this->compiler->addPreProcessor('smarty_compat', true);
$this->compiler->setLooseOpeningHandling(true);
}
$this->compiler->setDelimiters($this->left_delimiter, $this->right_delimiter);
......
......@@ -69,8 +69,7 @@ class DwooTemplateFile extends DwooTemplateString
* returns the compiled template file name
*
* @param Dwoo $dwoo the dwoo instance that requests it
* @param DwooICompiler $compiler the compiler that must be used, if null a
* DwooCompiler will be used by default
* @param DwooICompiler $compiler the compiler that must be used
* @return string
*/
public function getCompiledTemplate(Dwoo $dwoo, DwooICompiler $compiler)
......@@ -94,6 +93,7 @@ class DwooTemplateFile extends DwooTemplateString
$this->compiler = $compiler;
$compiler->setCustomPlugins($dwoo->getCustomPlugins());
$compiler->setSecurityPolicy($dwoo->getSecurityPolicy());
file_put_contents($compiledFile, $compiler->compile(file_get_contents($this->file)));
touch($compiledFile, max($_SERVER['REQUEST_TIME'], filemtime($this->file)));
......@@ -105,7 +105,7 @@ class DwooTemplateFile extends DwooTemplateString
/**
* returns the resource name for this template class
*
*
* @return string
*/
public function getResourceName()
......@@ -122,7 +122,7 @@ class DwooTemplateFile extends DwooTemplateString
{
return $this->file;
}
/**
* returns a new template object from the given include name, null if no include is
* possible (resource not found), or false if include is not permitted by this resource type
......@@ -143,20 +143,25 @@ class DwooTemplateFile extends DwooTemplateString
{
$resourceId = str_replace(array("\t", "\n", "\r"), array('\\t', '\\n', '\\r'), $resourceId);
// TODO check for recursion here if security is enabled
if(file_exists($resourceId))
return new self($resourceId, $cacheTime, $cacheId, $compileId);
if(file_exists($resourceId) === false)
{
$tpl = $dwoo->getCurrentTemplate();
if($tpl instanceof DwooTemplateFile)
{
$resourceId = dirname($tpl->getFilename()).DIRECTORY_SEPARATOR.$resourceId;
if(file_exists($resourceId) === false)
return null;
}
}
$tpl = $dwoo->getCurrentTemplate();
if($tpl instanceof DwooTemplateFile)
if($policy = $dwoo->getSecurityPolicy())
{
$file = dirname($tpl->getFilename()).DIRECTORY_SEPARATOR.$resourceId;
if(file_exists($file))
return new self($file, $cacheTime, $cacheId, $compileId);
$resourceId = realpath($resourceId);
if($resourceId === $this->file)
return $dwoo->triggerError('You can not include a template into itself', E_USER_WARNING);
}
return null;
return new self($resourceId, $cacheTime, $cacheId, $compileId);
}
}
......
......@@ -49,9 +49,9 @@ class DwooTemplateString implements DwooITemplate
/**
* validity duration of the generated cache file (in seconds)
*
*
* set to -1 for infinite cache, 0 to disable and null to inherit the Dwoo instance's cache time
*
*
* @var int
*/
protected $cacheTime;
......@@ -72,10 +72,10 @@ class DwooTemplateString implements DwooITemplate
* @var array
*/
protected static $cache = array('cached'=>array(), 'compiled'=>array());
/**
* holds the compiler that built this template
*
*
* @var DwooICompiler
*/
protected $compiler;
......@@ -135,17 +135,17 @@ class DwooTemplateString implements DwooITemplate
{
return $this->name;
}
/**
* returns the resource name for this template class
*
*
* @return string
*/
public function getResourceName()
{
return 'string';
}
/**
* returns the compiler used by this template, if it was just compiled, or null
*
......@@ -219,11 +219,11 @@ class DwooTemplateString implements DwooITemplate
self::$cache['cached'][$this->cacheId] = true;
}
/**
* clears the cached template if it's older than the given time
*
* @param int $olderThan minimum time (in seconds) required for the cache to be cleared
* @param int $olderThan minimum time (in seconds) required for the cache to be cleared
* @return bool true if the cache was not present or if it was deleted, false if it remains there
*/
public function clearCache($olderThan=0)
......@@ -232,13 +232,12 @@ class DwooTemplateString implements DwooITemplate
return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile));
}
/**
* returns the compiled template file name
*
* @param Dwoo $dwoo the dwoo instance that requests it
* @param DwooICompiler $compiler the compiler that must be used, if null a
* DwooCompiler will be used by default
* @param DwooICompiler $compiler the compiler that must be used
* @return string
*/
public function getCompiledTemplate(Dwoo $dwoo, DwooICompiler $compiler)
......@@ -262,6 +261,7 @@ class DwooTemplateString implements DwooITemplate
$this->compiler = $compiler;
$compiler->setCustomPlugins($dwoo->getCustomPlugins());
$compiler->setSecurityPolicy($dwoo->getSecurityPolicy());
file_put_contents($compiledFile, $compiler->compile($this->template));
touch($compiledFile, $_SERVER['REQUEST_TIME']);
......
......@@ -35,9 +35,12 @@ class DwooPlugin_elseif extends DwooPlugin_if implements DwooICompilableBlock
$currentBlock =& $compiler->getCurrentBlock();
$currentBlock['params']['postOutput'] = DwooCompiler::PHP_OPEN."\n}".DwooCompiler::PHP_CLOSE;
$out = substr($out, 0, -strlen(DwooCompiler::PHP_CLOSE));
if($out === '')
$out = DwooCompiler::PHP_OPEN."\n}";
else
$out = substr($out, 0, -strlen(DwooCompiler::PHP_CLOSE));
return $out . "elseif(".implode(' ', self::replaceKeywords($params, $compiler)).") {\n" . DwooCompiler::PHP_CLOSE;
return $out . " elseif(".implode(' ', self::replaceKeywords($params, $compiler)).") {\n" . DwooCompiler::PHP_CLOSE;
}
public static function postProcessing(DwooCompiler $compiler, array $params, $prepend='', $append='')
......
......@@ -28,8 +28,6 @@ class DwooPlugin_for extends DwooBlockPlugin implements DwooICompilableBlock
public static function preProcessing(DwooCompiler $compiler, array $params, $prepend='', $append='', $type)
{
// TODO pad the range to 1000 or something if security is enabled
$params = $compiler->getCompiledParams($params);
$tpl = $compiler->getTemplateSource(true);
......
......@@ -48,7 +48,7 @@ class DwooPlugin_smartyinterface extends DwooPlugin
$compiler->curBlock['params']['postOut'] = DwooCompiler::PHP_OPEN.' $_block_content = ob_get_clean(); $_block_repeat=false; echo '.$callback.'$_tag_stack[count($_tag_stack)-1], $_block_content, $this, $_block_repeat); } array_pop($_tag_stack);'.DwooCompiler::PHP_CLOSE;
return DwooCompiler::PHP_OPEN.$prepend.' $_tag_stack[] = '.var_export($params,true).'; $_block_repeat=true; '.$callback.'$_tag_stack[count($_tag_stack)-1], null, $this, $_block_repeat); while ($_block_repeat) { ob_start();'.DwooCompiler::PHP_CLOSE;
return DwooCompiler::PHP_OPEN.$prepend.' if(!isset($_tag_stack)){ $_tag_stack = array(); } $_tag_stack[] = '.var_export($params,true).'; $_block_repeat=true; '.$callback.'$_tag_stack[count($_tag_stack)-1], null, $this, $_block_repeat); while ($_block_repeat) { ob_start();'.DwooCompiler::PHP_CLOSE;
}
public static function postProcessing(DwooCompiler $compiler, array $params, $prepend='', $append='')
......
......@@ -24,7 +24,23 @@ function DwooPlugin_fetch(Dwoo $dwoo, $file, $assign = null)
if($file === '')
return;
// TODO check for security here on http/ftp + secure dirs
if($policy = $dwoo->getSecurityPolicy())
{
while(true)
{
if(preg_match('{^([a-z]+?)://}i', $file))
return $dwoo->triggerError('The security policy prevents you to read files from external sources.', E_USER_WARNING);
$file = realpath($file);
$dirs = $policy->getAllowedDirectories();
foreach($dirs as $dir=>$dummy)
{
if(strpos($file, $dir) === 0)
break 2;
}
return $dwoo->triggerError('The security policy prevents you to read <em>'.$file.'</em>', E_USER_WARNING);
}
}
$file = str_replace(array("\t", "\n", "\r"), array('\\t', '\\n', '\\r'), $file);
$out = file_get_contents($file);
......
......@@ -26,13 +26,35 @@ function DwooPlugin_include(Dwoo $dwoo, $file, $cache_time = null, $cache_id = n
if(preg_match('#^([a-z]{2,}):(.*)#i', $file, $m))
{
$include = $dwoo->getTemplate($m[1], $m[2], $cache_time, $cache_id, $compile_id);
$resource = $m[1];
$identifier = $m[2];
}
else
{
$include = $dwoo->getTemplate('file', $file, $cache_time, $cache_id, $compile_id);
$resource = 'file';
$identifier = $file;
}
if($resource === 'file' && $policy = $dwoo->getSecurityPolicy())
{
while(true)
{
if(preg_match('{^([a-z]+?)://}i', $identifier))
return $dwoo->triggerError('The security policy prevents you to read files from external sources.', E_USER_WARNING);
$identifier = realpath($identifier);
$dirs = $policy->getAllowedDirectories();
foreach($dirs as $dir=>$dummy)
{
if(strpos($identifier, $dir) === 0)
break 2;
}
return $dwoo->triggerError('The security policy prevents you to read <em>'.$identifier.'</em>', E_USER_WARNING);
}
}
$include = $dwoo->getTemplate($resource, $identifier, $cache_time, $cache_id, $compile_id);
if($include === null)
return;
elseif($include === false)
......
......@@ -159,6 +159,13 @@ class BlockTests extends PHPUnit_Framework_TestCase
public function testForeachDwoo()
{
// Item only, key arg is mapped to it just as foreach($foo as $item)
$tpl = new DwooTemplateString('{foreach $sub item}{$item}{/foreach}');
$tpl->forceCompilation();
$this->assertEquals('foobar', $this->dwoo->get($tpl, array('sub'=>array('foo','bar')), $this->compiler));
// Item and key used, key is second just as foreach($foo as $key=>$item)
$tpl = new DwooTemplateString('{foreach $sub key item}{$key}.{$item}{/foreach}');
$tpl->forceCompilation();
......
......@@ -4,199 +4,254 @@ require_once dirname(dirname(__FILE__)).'/DwooCompiler.php';
class CompilerTests extends PHPUnit_Framework_TestCase
{
protected $compiler;
protected $dwoo;
public function __construct()
{
// extend this class and override this in your constructor to test a modded compiler
$this->compiler = new DwooCompiler();
$this->dwoo = new Dwoo();
}
protected $compiler;
protected $dwoo;
public function __construct()
{
// extend this class and override this in your constructor to test a modded compiler
$this->compiler = new DwooCompiler();
$this->dwoo = new Dwoo();
}
public function testVarReplacement()
{
$tpl = new DwooTemplateString('{$foo}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{$foo}');
$tpl->forceCompilation();
$this->assertEquals('bar', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testModifier()
{
$tpl = new DwooTemplateString('{$foo|upper}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{$foo|upper}');
$tpl->forceCompilation();
$this->assertEquals('BAR', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testModifierArgs()
{
$tpl = new DwooTemplateString('{$foo|spacify:"-"|upper}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{$foo|spacify:"-"|upper}');
$tpl->forceCompilation();
$this->assertEquals('B-A-R', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testDwooFunc()
{
$tpl = new DwooTemplateString('{upper($foo)}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{upper($foo)}');
$tpl->forceCompilation();
$this->assertEquals('BAR', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testDwooLoose()
{
$tpl = new DwooTemplateString('{upper $foo}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{upper $foo}');
$tpl->forceCompilation();
$this->assertEquals('BAR', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testNamedParameter()
{
$tpl = new DwooTemplateString('{upper value=$foo}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{upper value=$foo}');
$tpl->forceCompilation();
$this->assertEquals('BAR', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testNamedParameter2()
{
$tpl = new DwooTemplateString('{replace value=$foo search="BAR"|lower replace="BAR"}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{replace value=$foo search="BAR"|lower replace="BAR"}');
$tpl->forceCompilation();
$this->assertEquals('BAR', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testNamedParameter3()
{
$tpl = new DwooTemplateString('{assign value=reverse(array(foo=3,boo=5, 3=4)) var=arr}{foreach $arr k v}{$k}{$v}{/foreach}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{assign value=reverse(array(foo=3,boo=5, 3=4)) var=arr}{foreach $arr k v}{$k}{$v}{/foreach}');
$tpl->forceCompilation();
$this->assertEquals('34boo5foo3', $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testRecursiveCall()
{
$tpl = new DwooTemplateString('{lower(reverse(upper($foo)))}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{lower(reverse(upper($foo)))}');
$tpl->forceCompilation();
$this->assertEquals('rab', $this->dwoo->get($tpl, array('foo'=>'bar'), $this->compiler));
}
public function testComplexRecursiveCall()
{
$tpl = new DwooTemplateString('{lower reverse($foo|reverse|upper)}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{lower reverse($foo|reverse|upper)}');
$tpl->forceCompilation();
$this->assertEquals('bar', $this->dwoo->get($tpl, array('foo'=>'BaR'), $this->compiler));
}
public function testComplexRecursiveCall2()
{
$tpl = new DwooTemplateString('{str_repeat "AB`$foo|reverse|spacify:o`CD" 3}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{str_repeat "AB`$foo|reverse|spacify:o`CD" 3}');
$tpl->forceCompilation();
$this->assertEquals('AB3o2o1CDAB3o2o1CDAB3o2o1CD', $this->dwoo->get($tpl, array('foo'=>'123'), $this->compiler));
}
public function testStrip()
{
$tpl = new DwooTemplateString("{strip}a\nb\nc{/strip}a\nb\nc");
$tpl->forceCompilation();
$tpl = new DwooTemplateString("{strip}a\nb\nc{/strip}a\nb\nc");
$tpl->forceCompilation();
$this->assertEquals("abca\nb\nc", $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testWhitespace()
{
$tpl = new DwooTemplateString("{\$foo}{\$foo}\n{\$foo}\n\n{\$foo}\n\n\n{\$foo}");
$tpl->forceCompilation();
$tpl = new DwooTemplateString("{\$foo}{\$foo}\n{\$foo}\n\n{\$foo}\n\n\n{\$foo}");
$tpl->forceCompilation();
$this->assertEquals("aa\na\n\na\n\n\na", $this->dwoo->get($tpl, array('foo'=>'a'), $this->compiler));
}
public function testLiteral()
{
$tpl = new DwooTemplateString('{literal}{$foo}{hurray}{/literal}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{literal}{$foo}{hurray}{/literal}');
$tpl->forceCompilation();
$this->assertEquals('{$foo}{hurray}', $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testEscaping()
{
$tpl = new DwooTemplateString('\{foo\{bar\\\\{$var}}{"tes\}t"}{"foo\"lol\"bar"}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('\{foo\{bar\\\\{$var}}{"tes\}t"}{"foo\"lol\"bar"}');
$tpl->forceCompilation();
$this->assertEquals('{foo{bar\\1}tes}tfoo"lol"bar', $this->dwoo->get($tpl, array('var'=>1), $this->compiler));
}
public function testFunctions()
{
$tpl = new DwooTemplateString('{dump()}{dump( )}{dump}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{dump()}{dump( )}{dump}');
$tpl->forceCompilation();
$this->assertEquals('<div style="background:#aaa; padding:5px; margin:5px;">data (current scope): <div style="background:#ccc;"></div></div><div style="background:#aaa; padding:5px; margin:5px;">data (current scope): <div style="background:#ccc;"></div></div><div style="background:#aaa; padding:5px; margin:5px;">data (current scope): <div style="background:#ccc;"></div></div>', $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testExpressions()
{
$tpl = new DwooTemplateString('{$foo+5} {$foo+$foo} {$foo+3*$foo} {$foo*$foo+4*$foo} {$foo*2/2|number_format} {$foo*2/3|number_format:1} {number_format $foo*2/3 1}');
$tpl->forceCompilation();
$this->assertEquals("10 10 40 145 5 3.3 3.3", $this->dwoo->get($tpl, array('foo'=>5), $this->compiler));
}
public function testExpressions()
{
$tpl = new DwooTemplateString('{$foo+5} {$foo+$foo} {$foo+3*$foo} {$foo*$foo+4*$foo} {$foo*2/2|number_format} {$foo*2/3|number_format:1} {number_format $foo*2/3 1}');
$tpl->forceCompilation();
public function testNonExpressions()
{
$tpl = new DwooTemplateString('{"$foo/$foo"}');
$tpl->forceCompilation();
$this->assertEquals("10 10 40 145 5 3.3 3.3", $this->dwoo->get($tpl, array('foo'=>5), $this->compiler));
$this->assertEquals("5/5", $this->dwoo->get($tpl, array('foo'=>5), $this->compiler));
}
$tpl = new DwooTemplateString('{"$foo/$foo"}');
$tpl->forceCompilation();
public function testConstants()
{
if(!defined('TEST'))
define('TEST', 'Test');
$tpl = new DwooTemplateString('{$dwoo.const.TEST} {$dwoo.const.Dwoo::FUNC_PLUGIN*$dwoo.const.Dwoo::BLOCK_PLUGIN}');
$tpl->forceCompilation();
$this->assertEquals("1", $this->dwoo->get($tpl, array('foo'=>5), $this->compiler));
}
$this->assertEquals(TEST.' '.(Dwoo::FUNC_PLUGIN*Dwoo::BLOCK_PLUGIN), $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testNonExpressions()
{
$tpl = new DwooTemplateString('{"`$foo`/`$foo`"}');
$tpl->forceCompilation();
public function testAltDelimiters()
{
$tpl = new DwooTemplateString('{"test"} <%"test"%> <%"foo{lol}\%>"%>');
$tpl->forceCompilation();
$this->compiler->setDelimiters('<%', '%>');
$this->assertEquals('{"test"} test foo{lol}%>', $this->dwoo->get($tpl, array(), $this->compiler));
$this->assertEquals("5/5", $this->dwoo->get($tpl, array('foo'=>5), $this->compiler));
}
$tpl = new DwooTemplateString('d"O"b');
$tpl->forceCompilation();
$this->compiler->setDelimiters('d', 'b');
$this->assertEquals('O', $this->dwoo->get($tpl, array(), $this->compiler));
public function testConstants()
{
if(!defined('TEST'))
define('TEST', 'Test');
$tpl = new DwooTemplateString('{$dwoo.const.TEST} {$dwoo.const.Dwoo::FUNC_PLUGIN*$dwoo.const.Dwoo::BLOCK_PLUGIN}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('<!-- "O" --> \<!-- ');
$tpl->forceCompilation();
$this->compiler->setDelimiters('<!-- ', ' -->');
$this->assertEquals('O <!-- ', $this->dwoo->get($tpl, array(), $this->compiler));
$this->assertEquals(TEST.' '.(Dwoo::FUNC_PLUGIN*Dwoo::BLOCK_PLUGIN), $this->dwoo->get($tpl, array(), $this->compiler));
}
$this->compiler->setDelimiters('{', '}');
}
public function testAltDelimiters()
{
$tpl = new DwooTemplateString('{"test"} <%"test"%> <%"foo{lol}\%>"%>');
$tpl->forceCompilation();
$this->compiler->setDelimiters('<%', '%>');
$this->assertEquals('{"test"} test foo{lol}%>', $this->dwoo->get($tpl, array(), $this->compiler));
$tpl = new DwooTemplateString('d"O"b');
$tpl->forceCompilation();
$this->compiler->setDelimiters('d', 'b');
$this->assertEquals('O', $this->dwoo->get($tpl, array(), $this->compiler));
$tpl = new DwooTemplateString('<!-- "O" --> \<!-- ');
$tpl->forceCompilation();
$this->compiler->setDelimiters('<!-- ', ' -->');
$this->assertEquals('O <!-- ', $this->dwoo->get($tpl, array(), $this->compiler));
$this->compiler->setDelimiters('{', '}');
}
public function testNumberedIndexes()
{
$tpl = new DwooTemplateString('{$100}-{$150}-{$0}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{$100}-{$150}-{$0}');
$tpl->forceCompilation();
$this->assertEquals('bar-foo-', $this->dwoo->get($tpl, array('100'=>'bar', 150=>'foo'), $this->compiler));
}
public function testParseBool()
{
$tpl = new DwooTemplateString('{if (true === yes && true === on) && (false===off && false===no)}okay{/if}');
$tpl->forceCompilation();
$tpl = new DwooTemplateString('{if (true === yes && true === on) && (false===off && false===no)}okay{/if}');
$tpl->forceCompilation();
$this->assertEquals('okay', $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testMethodCalls()
{
$tpl = new DwooTemplateString('{$a} {$a->foo()} {$b[$c]->foo()} {$a->bar()+$a->bar()} {$a->baz(5, $foo)} {$a->make(5)->getInt()} {$a->make(5)->getInt()/2}');
$tpl->forceCompilation();
$a = new MethodCallsHelper();
$this->assertEquals('obj 0 1 7 10bar 5 2.5', $this->dwoo->get($tpl, array('a'=>$a, 'b'=>array('test'=>$a), 'c'=>'test', 'foo'=>'bar'), $this->compiler));
}
public function testLooseTagHandling()
{
$this->compiler->setLooseOpeningHandling(true);
$this->assertEquals($this->compiler->getLooseOpeningHandling(), true);
$tpl = new DwooTemplateString('{ $a }{$a }{ $a}{$a}');
$tpl->forceCompilation();
$this->assertEquals('moomoomoomoo', $this->dwoo->get($tpl, array('a'=>'moo'), $this->compiler));
$this->compiler->setLooseOpeningHandling(false);
$tpl = new DwooTemplateString('{ $a }{$a }{ $a}{$a}');
$tpl->forceCompilation();
$this->assertEquals('{ $a }moo{ $a}moo', $this->dwoo->get($tpl, array('a'=>'moo'), $this->compiler));
}
}
class MethodCallsHelper {
public function __construct($int=0) {
$this->int = $int;
}
public function getInt() {
return $this->int;
}
public function make($a=0) {
return new self($a);
}
public function foo() {
static $a=0;
return $a++;
}
public function bar() {
static $a=3;
return $a++;
}
public function baz($int, $str) {
return ($int+5).$str;
}
public function __toString() { return 'obj'; }
}
?>
\ No newline at end of file
......@@ -75,7 +75,6 @@ class CoreTests extends PHPUnit_Framework_TestCase
$tpl = new DwooTemplateString('{"foo"|strtoupper}');
$tpl->forceCompilation();
$this->compiler->addPhpFunction('strtoupper');
$this->assertEquals("FOO", $this->dwoo->get($tpl, array(), $this->compiler));
$tpl = new DwooTemplateString('{foreach $foo|count p}{$p}{/foreach}');
......
......@@ -41,10 +41,12 @@ SNIPPET
public function testSmartyCompat()
{
$tpl = new DwooTemplateString('{ldelim}{$smarty.version}{rdelim}');
$tpl = new DwooTemplateString('{ldelim}{$smarty.version}{rdelim}');
$tpl->forceCompilation();
$cmp = new DwooCompiler();
$cmp->addPreProcessor('smarty_compat', true);
$this->assertEquals('{'.Dwoo::VERSION.'}', $this->dwoo->get($tpl, array(), $this->compiler));
$this->assertEquals('{'.Dwoo::VERSION.'}', $this->dwoo->get($tpl, array(), $cmp));
}
}
......
......@@ -279,7 +279,7 @@ class FuncTests extends PHPUnit_Framework_TestCase
$this->assertEquals((string)(3+5+100+20), $this->dwoo->get($tpl, array(), $this->compiler));
$tpl = new DwooTemplateString('{math equation="3+5+$a+b" b="20"}');
$tpl = new DwooTemplateString('{math equation="3+5+`$a`+b" b="20"}');
$tpl->forceCompilation();
$this->assertEquals((string)(3+5+100+20), $this->dwoo->get($tpl, array('a'=>100), $this->compiler));
......
<?php
require_once dirname(dirname(__FILE__)).'/DwooCompiler.php';
function testphpfunc($input) { return $input.'OK'; }
class SecurityTests extends PHPUnit_Framework_TestCase
{
protected $compiler;
protected $dwoo;
protected $policy;
public function __construct()
{
$this->compiler = new DwooCompiler();
$this->dwoo = new Dwoo();
$this->policy = new DwooSecurityPolicy();
$this->dwoo->setSecurityPolicy($this->policy);
}
public function testConstantHandling()
{
$tpl = new DwooTemplateString('{$dwoo.const.DWOO_PATH}');
$tpl->forceCompilation();
$this->assertEquals("", $this->dwoo->get($tpl, array(), $this->compiler));
$this->policy->setConstantHandling(DwooSecurityPolicy::CONST_ALLOW);
$tpl = new DwooTemplateString('{$dwoo.const.DWOO_PATH}');
$tpl->forceCompilation();
$this->assertEquals(DWOO_PATH, $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testPhpHandling()
{
$this->policy->setPhpHandling(DwooSecurityPolicy::PHP_ALLOW);
$tpl = new DwooTemplateString('<?php echo "moo"; ?>');
$tpl->forceCompilation();
$this->assertEquals("moo", $this->dwoo->get($tpl, array(), $this->compiler));
$this->policy->setPhpHandling(DwooSecurityPolicy::PHP_ENCODE);
$tpl = new DwooTemplateString('<?php echo "moo"; ?>');
$tpl->forceCompilation();
$this->assertEquals(htmlspecialchars('<?php echo "moo"; ?>'), $this->dwoo->get($tpl, array(), $this->compiler));
$this->policy->setPhpHandling(DwooSecurityPolicy::PHP_REMOVE);
$tpl = new DwooTemplateString('<?php echo "moo"; ?>');
$tpl->forceCompilation();
$this->assertEquals('', $this->dwoo->get($tpl, array(), $this->compiler));
}
public function testAllowPhpFunction()
{
$this->policy->allowPhpFunction('testphpfunc');
$tpl = new DwooTemplateString('{testphpfunc("foo")}');
$tpl->forceCompilation();
$this->assertEquals("fooOK", $this->dwoo->get($tpl, array(), $this->compiler));
}
}
?>
\ No newline at end of file
{include file="test.tpl" foo=3 bar=4}
\ No newline at end of file
{include file="test.html" foo=3 bar=4}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment