From 7fc5927721e0e68dd3461f1bc77e165fce9a3e5f Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sun, 19 Dec 2010 11:42:50 +0100
Subject: [PATCH] made macro return their value instead of echoing directly
---
CHANGELOG | 1 +
lib/Twig/Extension/Core.php | 4 +++
lib/Twig/Markup.php | 31 +++++++++++++++++++++
lib/Twig/Node/Macro.php | 3 ++
lib/Twig/Sandbox/SecurityPolicy.php | 2 +-
lib/Twig/Template.php | 8 +++++-
test/Twig/Tests/Extension/SandboxTest.php | 43 ++++++++++++++++++-----------
test/Twig/Tests/Node/MacroTest.php | 3 ++
8 files changed, 77 insertions(+), 18 deletions(-)
create mode 100644 lib/Twig/Markup.php
diff --git a/CHANGELOG b/CHANGELOG
index 945352d..efa3759 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@ Backward incompatibilities:
Changes:
+ * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode)
* added the "from" tag to import macros as functions
* added support for functions (a function is just syntactic sugar for a getAttribute() call)
* made macros callable when sandbox mode is enabled
diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php
index 74517b5..056f696 100644
--- a/lib/Twig/Extension/Core.php
+++ b/lib/Twig/Extension/Core.php
@@ -287,6 +287,10 @@ function twig_strtr($pattern, $replacements)
*/
function twig_escape_filter(Twig_Environment $env, $string, $type = 'html')
{
+ if (is_object($string) && $string instanceof Twig_Markup) {
+ return $string;
+ }
+
if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
return $string;
}
diff --git a/lib/Twig/Markup.php b/lib/Twig/Markup.php
new file mode 100644
index 0000000..878ee07
--- /dev/null
+++ b/lib/Twig/Markup.php
@@ -0,0 +1,31 @@
+
+ */
+class Twig_Markup extends Exception
+{
+ protected $content;
+
+ public function __construct($content)
+ {
+ $this->content = $content;
+ }
+
+ public function __toString()
+ {
+ return $this->content;
+ }
+}
diff --git a/lib/Twig/Node/Macro.php b/lib/Twig/Node/Macro.php
index 070c3c7..7b9d31d 100644
--- a/lib/Twig/Node/Macro.php
+++ b/lib/Twig/Node/Macro.php
@@ -54,7 +54,10 @@ class Twig_Node_Macro extends Twig_Node
$compiler
->outdent()
->write(");\n\n")
+ ->write("ob_start();\n")
->subcompile($this->getNode('body'))
+ ->raw("\n")
+ ->write("return ob_get_clean();\n")
->outdent()
->write("}\n\n")
;
diff --git a/lib/Twig/Sandbox/SecurityPolicy.php b/lib/Twig/Sandbox/SecurityPolicy.php
index 567780b..b85b42d 100644
--- a/lib/Twig/Sandbox/SecurityPolicy.php
+++ b/lib/Twig/Sandbox/SecurityPolicy.php
@@ -67,7 +67,7 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac
public function checkMethodAllowed($obj, $method)
{
- if ($obj instanceof Twig_TemplateInterface) {
+ if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) {
return true;
}
diff --git a/lib/Twig/Template.php b/lib/Twig/Template.php
index 6073e35..0a38087 100644
--- a/lib/Twig/Template.php
+++ b/lib/Twig/Template.php
@@ -186,6 +186,12 @@ abstract class Twig_Template implements Twig_TemplateInterface
$this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
}
- return call_user_func_array(array($object, $method), $arguments);
+ $ret = call_user_func_array(array($object, $method), $arguments);
+
+ if ($object instanceof Twig_TemplateInterface) {
+ return new Twig_Markup($ret);
+ }
+
+ return $ret;
}
}
diff --git a/test/Twig/Tests/Extension/SandboxTest.php b/test/Twig/Tests/Extension/SandboxTest.php
index e66cac6..34cc961 100644
--- a/test/Twig/Tests/Extension/SandboxTest.php
+++ b/test/Twig/Tests/Extension/SandboxTest.php
@@ -34,64 +34,64 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
public function testSandboxGloballySet()
{
- $twig = $this->getEnvironment(false, self::$templates);
+ $twig = $this->getEnvironment(false, array(), self::$templates);
$this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally');
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('1_basic1')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');
} catch (Twig_Sandbox_SecurityError $e) {
}
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('1_basic2')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');
} catch (Twig_Sandbox_SecurityError $e) {
}
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('1_basic3')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
} catch (Twig_Sandbox_SecurityError $e) {
}
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('1_basic4')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template');
} catch (Twig_Sandbox_SecurityError $e) {
}
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('1_basic5')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
} catch (Twig_Sandbox_SecurityError $e) {
}
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('1_basic6')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
} catch (Twig_Sandbox_SecurityError $e) {
}
- $twig = $this->getEnvironment(true, self::$templates, array(), array(), array('Object' => 'foo'));
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('Object' => 'foo'));
$this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods');
- $twig = $this->getEnvironment(true, self::$templates, array(), array(), array('Object' => '__toString'));
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('Object' => '__toString'));
$this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods');
- $twig = $this->getEnvironment(true, self::$templates, array(), array('upper'));
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper'));
$this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters');
- $twig = $this->getEnvironment(true, self::$templates, array('if'));
+ $twig = $this->getEnvironment(true, array(), self::$templates, array('if'));
$this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags');
- $twig = $this->getEnvironment(true, self::$templates, array(), array(), array(), array('Object' => 'bar'));
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('Object' => 'bar'));
$this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties');
}
@@ -102,7 +102,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
'2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
);
- $twig = $this->getEnvironment(false, self::$templates);
+ $twig = $this->getEnvironment(false, array(), self::$templates);
$this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
self::$templates = array(
@@ -110,7 +110,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
'3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
);
- $twig = $this->getEnvironment(true, self::$templates);
+ $twig = $this->getEnvironment(true, array(), self::$templates);
try {
$twig->loadTemplate('3_basic')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');
@@ -118,10 +118,21 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
}
}
- protected function getEnvironment($sandboxed, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array())
+ public function testMacrosInASandbox()
+ {
+ $twig = $this->getEnvironment(true, array('autoescape' => true), array('index' => <<{{ text }}
{% endmacro %}
+{{ _self.test('username') }}
+EOF
+ ), array('macro'), array('escape'));
+
+ $this->assertEquals('username
', $twig->loadTemplate('index')->render(array()));
+ }
+
+ protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array())
{
$loader = new Twig_Loader_Array($templates);
- $twig = new Twig_Environment($loader, array('debug' => true, 'cache' => false, 'autoescape' => false));
+ $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options));
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties);
$twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed));
diff --git a/test/Twig/Tests/Node/MacroTest.php b/test/Twig/Tests/Node/MacroTest.php
index 4a1e1ca..c0dbda9 100644
--- a/test/Twig/Tests/Node/MacroTest.php
+++ b/test/Twig/Tests/Node/MacroTest.php
@@ -50,7 +50,10 @@ public function getfoo(\$foo = null)
"foo" => \$foo,
);
+ ob_start();
echo "foo";
+
+ return ob_get_clean();
}
EOF
),
--
1.7.2.5