From 5fca700cbde4474e1fc73f24d9260d43e9d20bf4 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 24 Aug 2024 10:06:44 +0200 Subject: [PATCH] Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template --- CHANGELOG | 3 + doc/advanced.rst | 6 +- doc/api.rst | 8 ++- doc/deprecated.rst | 22 +++++-- extra/cache-extra/Node/CacheNode.php | 4 +- .../TokenParser/CacheTokenParser.php | 4 +- extra/cache-extra/composer.json | 2 +- src/ExpressionParser.php | 8 ++- src/Node/AutoEscapeNode.php | 4 +- src/Node/BlockNode.php | 4 +- src/Node/BlockReferenceNode.php | 4 +- src/Node/CaptureNode.php | 4 +- src/Node/CheckToStringNode.php | 2 +- src/Node/DeprecatedNode.php | 4 +- src/Node/DoNode.php | 4 +- src/Node/EmbedNode.php | 4 +- .../Expression/ArrowFunctionExpression.php | 4 +- .../Expression/BlockReferenceExpression.php | 4 +- src/Node/Expression/Filter/DefaultFilter.php | 4 +- src/Node/Expression/Filter/RawFilter.php | 4 +- src/Node/Expression/FilterExpression.php | 4 +- src/Node/Expression/ParentExpression.php | 4 +- src/Node/FlushNode.php | 4 +- src/Node/ForLoopNode.php | 4 +- src/Node/ForNode.php | 6 +- src/Node/IfNode.php | 4 +- src/Node/ImportNode.php | 2 +- src/Node/IncludeNode.php | 4 +- src/Node/MacroNode.php | 4 +- src/Node/Node.php | 20 +++++- src/Node/PrintNode.php | 4 +- src/Node/SandboxNode.php | 4 +- src/Node/SetNode.php | 4 +- src/Node/WithNode.php | 4 +- .../MacroAutoImportNodeVisitor.php | 2 +- src/Parser.php | 1 + src/Sandbox/SecurityPolicy.php | 8 ++- src/TokenParser/ApplyTokenParser.php | 8 +-- src/TokenParser/AutoEscapeTokenParser.php | 2 +- src/TokenParser/BlockTokenParser.php | 2 +- src/TokenParser/DeprecatedTokenParser.php | 2 +- src/TokenParser/DoTokenParser.php | 2 +- src/TokenParser/EmbedTokenParser.php | 2 +- src/TokenParser/ExtendsTokenParser.php | 2 +- src/TokenParser/FlushTokenParser.php | 2 +- src/TokenParser/ForTokenParser.php | 2 +- src/TokenParser/FromTokenParser.php | 2 +- src/TokenParser/IfTokenParser.php | 2 +- src/TokenParser/ImportTokenParser.php | 2 +- src/TokenParser/IncludeTokenParser.php | 2 +- src/TokenParser/MacroTokenParser.php | 4 +- src/TokenParser/SandboxTokenParser.php | 2 +- src/TokenParser/SetTokenParser.php | 2 +- src/TokenParser/UseTokenParser.php | 2 +- src/TokenParser/WithTokenParser.php | 2 +- tests/EnvironmentTest.php | 2 +- tests/ExpressionParserTest.php | 8 +-- tests/Extension/SandboxTest.php | 61 ++++++++++++++++++- tests/Node/DeprecatedTest.php | 6 +- tests/Node/NodeTest.php | 3 +- tests/ParserTest.php | 2 +- 61 files changed, 210 insertions(+), 102 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8d27a072d..4d54148ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # 3.12.0 (2024-XX-XX) + * Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template. + This behavior will change in 4.0 where these tags will need to be explicitly allowed like any other tag. + * Deprecate the "tag" constructor argument of the "Twig\Node\Node" class as the tag is now automatically set by the Parser when needed * Fix precedence of two-word tests when the first word is a valid test * Deprecate the `spaceless` filter * Deprecate some internal methods from `Parser`: `getBlockStack()`, `hasBlock()`, `getBlock()`, `hasMacro()`, `hasTraits()`, `getParent()` diff --git a/doc/advanced.rst b/doc/advanced.rst index 1e89694e7..b3eaa1a2e 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -487,7 +487,7 @@ Now, let's see the actual code of this class:: $value = $parser->getExpressionParser()->parseExpression(); $stream->expect(\Twig\Token::BLOCK_END_TYPE); - return new CustomSetNode($name, $value, $token->getLine(), $this->getTag()); + return new CustomSetNode($name, $value, $token->getLine()); } public function getTag() @@ -534,9 +534,9 @@ The ``CustomSetNode`` class itself is quite short:: class CustomSetNode extends \Twig\Node\Node { - public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line, $tag = null) + public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line) { - parent::__construct(['value' => $value], ['name' => $name], $line, $tag); + parent::__construct(['value' => $value], ['name' => $name], $line); } public function compile(\Twig\Compiler $compiler) diff --git a/doc/api.rst b/doc/api.rst index 7e2fd9a7f..09c553175 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -467,7 +467,7 @@ The ``sandbox`` extension can be used to evaluate untrusted code. Access to unsafe attributes and methods is prohibited. The sandbox security is managed by a policy instance. By default, Twig comes with one policy class: ``\Twig\Sandbox\SecurityPolicy``. This class allows you to white-list some -tags, filters, properties, and methods:: +tags, filters, functions, properties, and methods:: $tags = ['if']; $filters = ['upper']; @@ -486,6 +486,12 @@ able to call the ``getTitle()`` and ``getBody()`` methods on ``Article`` objects, and the ``title`` and ``body`` public properties. Everything else won't be allowed and will generate a ``\Twig\Sandbox\SecurityError`` exception. +.. caution:: + + The ``extends`` and ``use`` tags are always allowed in a sandboxed + template. That behavior will change in 4.0 where these tags will need to be + explicitly allowed like any other tag. + The policy object is the first argument of the sandbox constructor:: $sandbox = new \Twig\Extension\SandboxExtension($policy); diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 950d961dd..affdd2ba9 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -35,6 +35,13 @@ Extensions Nodes ----- +* The "tag" constructor parameter of the ``Twig\Node\Node`` class is deprecated + as of Twig 3.12 as the tag is now automatically set by the Parser when + needed. + +* Passing a second argument to "ExpressionParser::parseFilterExpressionRaw()" + is deprecated as of Twig 3.12. + * The following ``Twig\Node\Node`` methods will take a string or an integer (instead of just a string) in Twig 4.0 for their "name" argument: ``getNode()``, ``hasNode()``, ``setNode()``, ``removeNode()``, and @@ -89,9 +96,9 @@ Nodes class NotReadyFilterExpression extends FilterExpression { - public function __construct(Node $node, ConstantExpression $filter, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(Node $node, ConstantExpression $filter, Node $arguments, int $lineno) { - parent::__construct($node, $filter, $arguments, $lineno, $tag); + parent::__construct($node, $filter, $arguments, $lineno); } } @@ -117,9 +124,9 @@ Nodes class ReadyFilterExpression extends FilterExpression { #[FirstClassTwigCallableReady] - public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { - parent::__construct($node, $filter, $arguments, $lineno, $tag); + parent::__construct($node, $filter, $arguments, $lineno); } } @@ -186,3 +193,10 @@ Filters * The ``spaceless`` filter is deprecated as of Twig 3.12 and will be removed in Twig 4.0. + +Sandbox +------- + +* Having the ``extends`` and ``use`` tags allowed by default in a sandbox is + deprecated as of Twig 3.12. You will need to explicitly allow them if needed + in 4.0. diff --git a/extra/cache-extra/Node/CacheNode.php b/extra/cache-extra/Node/CacheNode.php index 5cb73c592..7308b6bc9 100644 --- a/extra/cache-extra/Node/CacheNode.php +++ b/extra/cache-extra/Node/CacheNode.php @@ -18,7 +18,7 @@ use Twig\Node\Node; class CacheNode extends AbstractExpression { - public function __construct(AbstractExpression $key, ?AbstractExpression $ttl, ?AbstractExpression $tags, Node $body, int $lineno, string $tag) + public function __construct(AbstractExpression $key, ?AbstractExpression $ttl, ?AbstractExpression $tags, Node $body, int $lineno) { $body = new CaptureNode($body, $lineno); $body->setAttribute('raw', true); @@ -31,7 +31,7 @@ class CacheNode extends AbstractExpression $nodes['tags'] = $tags; } - parent::__construct($nodes, [], $lineno, $tag); + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void diff --git a/extra/cache-extra/TokenParser/CacheTokenParser.php b/extra/cache-extra/TokenParser/CacheTokenParser.php index 61d5d2877..a5ea4840c 100644 --- a/extra/cache-extra/TokenParser/CacheTokenParser.php +++ b/extra/cache-extra/TokenParser/CacheTokenParser.php @@ -56,9 +56,9 @@ class CacheTokenParser extends AbstractTokenParser $body = $this->parser->subparse([$this, 'decideCacheEnd'], true); $stream->expect(Token::BLOCK_END_TYPE); - $body = new CacheNode($key, $ttl, $tags, $body, $token->getLine(), $this->getTag()); + $body = new CacheNode($key, $ttl, $tags, $body, $token->getLine()); - return new PrintNode(new RawFilter($body), $token->getLine(), $this->getTag()); + return new PrintNode(new RawFilter($body), $token->getLine()); } public function decideCacheEnd(Token $token): bool diff --git a/extra/cache-extra/composer.json b/extra/cache-extra/composer.json index 09b67186e..74e377e95 100644 --- a/extra/cache-extra/composer.json +++ b/extra/cache-extra/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=8.0.2", "symfony/cache": "^5.4|^6.4|^7.0", - "twig/twig": "^3.11" + "twig/twig": "^3.12" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 3b1d751a3..7ddbb9370 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -568,8 +568,12 @@ class ExpressionParser return $this->parseFilterExpressionRaw($node); } - public function parseFilterExpressionRaw($node, $tag = null) + public function parseFilterExpressionRaw($node) { + if (func_num_args() > 1) { + trigger_deprecation('twig/twig', '3.12', 'Passing a second argument to "%s()" is deprecated.', __METHOD__); + } + while (true) { $token = $this->parser->getStream()->expect(Token::NAME_TYPE); @@ -590,7 +594,7 @@ class ExpressionParser trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); } - $node = new $class($node, $ready ? $filter : new ConstantExpression($filter->getName(), $token->getLine()), $arguments, $token->getLine(), $tag); + $node = new $class($node, $ready ? $filter : new ConstantExpression($filter->getName(), $token->getLine()), $arguments, $token->getLine()); if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { break; diff --git a/src/Node/AutoEscapeNode.php b/src/Node/AutoEscapeNode.php index f9bc17e07..ee806396e 100644 --- a/src/Node/AutoEscapeNode.php +++ b/src/Node/AutoEscapeNode.php @@ -28,9 +28,9 @@ use Twig\Compiler; #[YieldReady] class AutoEscapeNode extends Node { - public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape') + public function __construct($value, Node $body, int $lineno) { - parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); + parent::__construct(['body' => $body], ['value' => $value], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/BlockNode.php b/src/Node/BlockNode.php index 15973a343..d2cfc3bd8 100644 --- a/src/Node/BlockNode.php +++ b/src/Node/BlockNode.php @@ -23,9 +23,9 @@ use Twig\Compiler; #[YieldReady] class BlockNode extends Node { - public function __construct(string $name, Node $body, int $lineno, ?string $tag = null) + public function __construct(string $name, Node $body, int $lineno) { - parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); + parent::__construct(['body' => $body], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/BlockReferenceNode.php b/src/Node/BlockReferenceNode.php index 23c73eabe..7c313a04c 100644 --- a/src/Node/BlockReferenceNode.php +++ b/src/Node/BlockReferenceNode.php @@ -23,9 +23,9 @@ use Twig\Compiler; #[YieldReady] class BlockReferenceNode extends Node implements NodeOutputInterface { - public function __construct(string $name, int $lineno, ?string $tag = null) + public function __construct(string $name, int $lineno) { - parent::__construct([], ['name' => $name], $lineno, $tag); + parent::__construct([], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/CaptureNode.php b/src/Node/CaptureNode.php index b1cb357f5..0162113c1 100644 --- a/src/Node/CaptureNode.php +++ b/src/Node/CaptureNode.php @@ -22,9 +22,9 @@ use Twig\Compiler; #[YieldReady] class CaptureNode extends Node { - public function __construct(Node $body, int $lineno, ?string $tag = null) + public function __construct(Node $body, int $lineno) { - parent::__construct(['body' => $body], ['raw' => false], $lineno, $tag); + parent::__construct(['body' => $body], ['raw' => false], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/CheckToStringNode.php b/src/Node/CheckToStringNode.php index 81fb92404..937240c1d 100644 --- a/src/Node/CheckToStringNode.php +++ b/src/Node/CheckToStringNode.php @@ -30,7 +30,7 @@ class CheckToStringNode extends AbstractExpression { public function __construct(AbstractExpression $expr) { - parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag()); + parent::__construct(['expr' => $expr], [], $expr->getTemplateLine()); } public function compile(Compiler $compiler): void diff --git a/src/Node/DeprecatedNode.php b/src/Node/DeprecatedNode.php index c4c4a8aec..0772adfc3 100644 --- a/src/Node/DeprecatedNode.php +++ b/src/Node/DeprecatedNode.php @@ -24,9 +24,9 @@ use Twig\Node\Expression\ConstantExpression; #[YieldReady] class DeprecatedNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno, ?string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno) { - parent::__construct(['expr' => $expr], [], $lineno, $tag); + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/DoNode.php b/src/Node/DoNode.php index 445016ab2..1593fd050 100644 --- a/src/Node/DoNode.php +++ b/src/Node/DoNode.php @@ -23,9 +23,9 @@ use Twig\Node\Expression\AbstractExpression; #[YieldReady] class DoNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno, ?string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno) { - parent::__construct(['expr' => $expr], [], $lineno, $tag); + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/EmbedNode.php b/src/Node/EmbedNode.php index 545509462..4cd3b38f2 100644 --- a/src/Node/EmbedNode.php +++ b/src/Node/EmbedNode.php @@ -25,9 +25,9 @@ use Twig\Node\Expression\ConstantExpression; class EmbedNode extends IncludeNode { // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) - public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, ?string $tag = null) + public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) { - parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); + parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno); $this->setAttribute('name', $name); $this->setAttribute('index', $index); diff --git a/src/Node/Expression/ArrowFunctionExpression.php b/src/Node/Expression/ArrowFunctionExpression.php index eaad03c9c..2bae4edd7 100644 --- a/src/Node/Expression/ArrowFunctionExpression.php +++ b/src/Node/Expression/ArrowFunctionExpression.php @@ -21,9 +21,9 @@ use Twig\Node\Node; */ class ArrowFunctionExpression extends AbstractExpression { - public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, Node $names, $lineno) { - parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag); + parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/Expression/BlockReferenceExpression.php b/src/Node/Expression/BlockReferenceExpression.php index 629382233..acd231e15 100644 --- a/src/Node/Expression/BlockReferenceExpression.php +++ b/src/Node/Expression/BlockReferenceExpression.php @@ -22,14 +22,14 @@ use Twig\Node\Node; */ class BlockReferenceExpression extends AbstractExpression { - public function __construct(Node $name, ?Node $template, int $lineno, ?string $tag = null) + public function __construct(Node $name, ?Node $template, int $lineno) { $nodes = ['name' => $name]; if (null !== $template) { $nodes['template'] = $template; } - parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag); + parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/Expression/Filter/DefaultFilter.php b/src/Node/Expression/Filter/DefaultFilter.php index e309c9331..75b6d18c2 100644 --- a/src/Node/Expression/Filter/DefaultFilter.php +++ b/src/Node/Expression/Filter/DefaultFilter.php @@ -34,7 +34,7 @@ use Twig\TwigTest; class DefaultFilter extends FilterExpression { #[FirstClassTwigCallableReady] - public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { if ($filter instanceof TwigFilter) { $name = $filter->getName(); @@ -53,7 +53,7 @@ class DefaultFilter extends FilterExpression $node = $default; } - parent::__construct($node, $filter, $arguments, $lineno, $tag); + parent::__construct($node, $filter, $arguments, $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/Expression/Filter/RawFilter.php b/src/Node/Expression/Filter/RawFilter.php index 12809ff4f..e115ab194 100644 --- a/src/Node/Expression/Filter/RawFilter.php +++ b/src/Node/Expression/Filter/RawFilter.php @@ -24,9 +24,9 @@ use Twig\TwigFilter; class RawFilter extends FilterExpression { #[FirstClassTwigCallableReady] - public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0, ?string $tag = null) + public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0) { - parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new Node(), $lineno ?: $node->getTemplateLine(), $tag ?: $node->getNodeTag()); + parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new Node(), $lineno ?: $node->getTemplateLine()); } public function compile(Compiler $compiler): void diff --git a/src/Node/Expression/FilterExpression.php b/src/Node/Expression/FilterExpression.php index 82a4e4a2a..efc91193e 100644 --- a/src/Node/Expression/FilterExpression.php +++ b/src/Node/Expression/FilterExpression.php @@ -21,7 +21,7 @@ use Twig\TwigFilter; class FilterExpression extends CallExpression { #[FirstClassTwigCallableReady] - public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { if ($filter instanceof TwigFilter) { $name = $filter->getName(); @@ -32,7 +32,7 @@ class FilterExpression extends CallExpression trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFilter" when creating a "%s" filter of type "%s" is deprecated.', $name, static::class); } - parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $name, 'type' => 'filter'], $lineno, $tag); + parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $name, 'type' => 'filter'], $lineno); if ($filter instanceof TwigFilter) { $this->setAttribute('twig_callable', $filter); diff --git a/src/Node/Expression/ParentExpression.php b/src/Node/Expression/ParentExpression.php index 59d833ac9..22fe38f6a 100644 --- a/src/Node/Expression/ParentExpression.php +++ b/src/Node/Expression/ParentExpression.php @@ -21,9 +21,9 @@ use Twig\Compiler; */ class ParentExpression extends AbstractExpression { - public function __construct(string $name, int $lineno, ?string $tag = null) + public function __construct(string $name, int $lineno) { - parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); + parent::__construct([], ['output' => false, 'name' => $name], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/FlushNode.php b/src/Node/FlushNode.php index 3f5a21114..ff3bd1cf1 100644 --- a/src/Node/FlushNode.php +++ b/src/Node/FlushNode.php @@ -22,9 +22,9 @@ use Twig\Compiler; #[YieldReady] class FlushNode extends Node { - public function __construct(int $lineno, string $tag) + public function __construct(int $lineno) { - parent::__construct([], [], $lineno, $tag); + parent::__construct([], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/ForLoopNode.php b/src/Node/ForLoopNode.php index 503687c2b..3e044bbb0 100644 --- a/src/Node/ForLoopNode.php +++ b/src/Node/ForLoopNode.php @@ -22,9 +22,9 @@ use Twig\Compiler; #[YieldReady] class ForLoopNode extends Node { - public function __construct(int $lineno, ?string $tag = null) + public function __construct(int $lineno) { - parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); + parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/ForNode.php b/src/Node/ForNode.php index 5222cf9bf..d8af89624 100644 --- a/src/Node/ForNode.php +++ b/src/Node/ForNode.php @@ -27,16 +27,16 @@ class ForNode extends Node { private $loop; - public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, ?string $tag = null) + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno) { - $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); + $body = new Node([$body, $this->loop = new ForLoopNode($lineno)]); $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; if (null !== $else) { $nodes['else'] = $else; } - parent::__construct($nodes, ['with_loop' => true], $lineno, $tag); + parent::__construct($nodes, ['with_loop' => true], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/IfNode.php b/src/Node/IfNode.php index 1b883305a..2af48fa81 100644 --- a/src/Node/IfNode.php +++ b/src/Node/IfNode.php @@ -23,14 +23,14 @@ use Twig\Compiler; #[YieldReady] class IfNode extends Node { - public function __construct(Node $tests, ?Node $else, int $lineno, ?string $tag = null) + public function __construct(Node $tests, ?Node $else, int $lineno) { $nodes = ['tests' => $tests]; if (null !== $else) { $nodes['else'] = $else; } - parent::__construct($nodes, [], $lineno, $tag); + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/ImportNode.php b/src/Node/ImportNode.php index db47bfe61..e80b1fd1c 100644 --- a/src/Node/ImportNode.php +++ b/src/Node/ImportNode.php @@ -26,7 +26,7 @@ class ImportNode extends Node { public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, ?string $tag = null, bool $global = true) { - parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/IncludeNode.php b/src/Node/IncludeNode.php index 7073fa4ac..1c18292c5 100644 --- a/src/Node/IncludeNode.php +++ b/src/Node/IncludeNode.php @@ -24,14 +24,14 @@ use Twig\Node\Expression\AbstractExpression; #[YieldReady] class IncludeNode extends Node implements NodeOutputInterface { - public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, ?string $tag = null) + public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) { $nodes = ['expr' => $expr]; if (null !== $variables) { $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno, $tag); + parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/MacroNode.php b/src/Node/MacroNode.php index ffd6b628d..d54b8ac72 100644 --- a/src/Node/MacroNode.php +++ b/src/Node/MacroNode.php @@ -28,7 +28,7 @@ class MacroNode extends Node /** * @param BodyNode $body */ - public function __construct(string $name, Node $body, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(string $name, Node $body, Node $arguments, int $lineno) { if (!$body instanceof BodyNode) { trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); @@ -40,7 +40,7 @@ class MacroNode extends Node } } - parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag); + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/Node.php b/src/Node/Node.php index a93ac2913..38ebdfa8c 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -42,9 +42,8 @@ class Node implements \Countable, \IteratorAggregate * @param array $nodes An array of named nodes * @param array $attributes An array of attributes (should not be nodes) * @param int $lineno The line number - * @param string $tag The tag name associated with the Node */ - public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, ?string $tag = null) + public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0) { foreach ($nodes as $name => $node) { if (!$node instanceof self) { @@ -54,7 +53,10 @@ class Node implements \Countable, \IteratorAggregate $this->nodes = $nodes; $this->attributes = $attributes; $this->lineno = $lineno; - $this->tag = $tag; + + if (func_num_args() > 3) { + trigger_deprecation('twig/twig', '3.12', sprintf('The "tag" constructor argument of the "%s" class is deprecated and ignored (check which TokenParser class set it to "%s"), the tag is now automatically set by the Parser when needed.', static::class, func_get_arg(3) ?: 'null')); + } } public function __toString() @@ -117,6 +119,18 @@ class Node implements \Countable, \IteratorAggregate return $this->tag; } + /** + * @internal + */ + public function setNodeTag(string $tag): void + { + if ($this->tag) { + throw new \LogicException('The tag of a node can only be set once.'); + } + + $this->tag = $tag; + } + public function hasAttribute(string $name): bool { return \array_key_exists($name, $this->attributes); diff --git a/src/Node/PrintNode.php b/src/Node/PrintNode.php index da442d852..e3c23bbfa 100644 --- a/src/Node/PrintNode.php +++ b/src/Node/PrintNode.php @@ -24,9 +24,9 @@ use Twig\Node\Expression\AbstractExpression; #[YieldReady] class PrintNode extends Node implements NodeOutputInterface { - public function __construct(AbstractExpression $expr, int $lineno, ?string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno) { - parent::__construct(['expr' => $expr], [], $lineno, $tag); + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/SandboxNode.php b/src/Node/SandboxNode.php index 80aecbdba..d51cea44b 100644 --- a/src/Node/SandboxNode.php +++ b/src/Node/SandboxNode.php @@ -22,9 +22,9 @@ use Twig\Compiler; #[YieldReady] class SandboxNode extends Node { - public function __construct(Node $body, int $lineno, ?string $tag = null) + public function __construct(Node $body, int $lineno) { - parent::__construct(['body' => $body], [], $lineno, $tag); + parent::__construct(['body' => $body], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/SetNode.php b/src/Node/SetNode.php index 0900f1542..67725104e 100644 --- a/src/Node/SetNode.php +++ b/src/Node/SetNode.php @@ -23,7 +23,7 @@ use Twig\Node\Expression\ConstantExpression; #[YieldReady] class SetNode extends Node implements NodeCaptureInterface { - public function __construct(bool $capture, Node $names, Node $values, int $lineno, ?string $tag = null) + public function __construct(bool $capture, Node $names, Node $values, int $lineno) { /* * Optimizes the node when capture is used for a large block of text. @@ -41,7 +41,7 @@ class SetNode extends Node implements NodeCaptureInterface } } - parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno, $tag); + parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/Node/WithNode.php b/src/Node/WithNode.php index a7b7e70d9..f9104948b 100644 --- a/src/Node/WithNode.php +++ b/src/Node/WithNode.php @@ -22,14 +22,14 @@ use Twig\Compiler; #[YieldReady] class WithNode extends Node { - public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, ?string $tag = null) + public function __construct(Node $body, ?Node $variables, bool $only, int $lineno) { $nodes = ['body' => $body]; if (null !== $variables) { $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => $only], $lineno, $tag); + parent::__construct($nodes, ['only' => $only], $lineno); } public function compile(Compiler $compiler): void diff --git a/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/src/NodeVisitor/MacroAutoImportNodeVisitor.php index d6a7781ba..556d9dfbb 100644 --- a/src/NodeVisitor/MacroAutoImportNodeVisitor.php +++ b/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -46,7 +46,7 @@ final class MacroAutoImportNodeVisitor implements NodeVisitorInterface if ($node instanceof ModuleNode) { $this->inAModule = false; if ($this->hasMacroCalls) { - $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true)); + $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, null, true)); } } elseif ($this->inAModule) { if ( diff --git a/src/Parser.php b/src/Parser.php index e3273230c..8923d4db6 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -176,6 +176,7 @@ class Parser if (!$node) { trigger_deprecation('twig/twig', '3.12', 'Returning "null" from "%s" is deprecated and forbidden by "TokenParserInterface".', $subparser::class); } else { + $node->setNodeTag($subparser->getTag()); $rv[] = $node; } break; diff --git a/src/Sandbox/SecurityPolicy.php b/src/Sandbox/SecurityPolicy.php index 417d38a8d..f7e269626 100644 --- a/src/Sandbox/SecurityPolicy.php +++ b/src/Sandbox/SecurityPolicy.php @@ -68,7 +68,13 @@ final class SecurityPolicy implements SecurityPolicyInterface { foreach ($tags as $tag) { if (!\in_array($tag, $this->allowedTags)) { - throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); + if ('extends' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "extends" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitely in your sandbox policy if needed.'); + } elseif ('use' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "use" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitely in your sandbox policy if needed.'); + } else { + throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); + } } } diff --git a/src/TokenParser/ApplyTokenParser.php b/src/TokenParser/ApplyTokenParser.php index 4dbf30406..0a6c1afb5 100644 --- a/src/TokenParser/ApplyTokenParser.php +++ b/src/TokenParser/ApplyTokenParser.php @@ -36,16 +36,16 @@ final class ApplyTokenParser extends AbstractTokenParser $ref = new TempNameExpression($name, $lineno); $ref->setAttribute('always_defined', true); - $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); return new Node([ - new SetNode(true, $ref, $body, $lineno, $this->getTag()), - new PrintNode($filter, $lineno, $this->getTag()), - ]); + new SetNode(true, $ref, $body, $lineno), + new PrintNode($filter, $lineno), + ], [], $lineno); } public function decideApplyEnd(Token $token): bool diff --git a/src/TokenParser/AutoEscapeTokenParser.php b/src/TokenParser/AutoEscapeTokenParser.php index 46790454a..b50b29e65 100644 --- a/src/TokenParser/AutoEscapeTokenParser.php +++ b/src/TokenParser/AutoEscapeTokenParser.php @@ -43,7 +43,7 @@ final class AutoEscapeTokenParser extends AbstractTokenParser $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); $stream->expect(Token::BLOCK_END_TYPE); - return new AutoEscapeNode($value, $body, $lineno, $this->getTag()); + return new AutoEscapeNode($value, $body, $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/src/TokenParser/BlockTokenParser.php b/src/TokenParser/BlockTokenParser.php index b3768742d..81d675db0 100644 --- a/src/TokenParser/BlockTokenParser.php +++ b/src/TokenParser/BlockTokenParser.php @@ -60,7 +60,7 @@ final class BlockTokenParser extends AbstractTokenParser $this->parser->popBlockStack(); $this->parser->popLocalScope(); - return new BlockReferenceNode($name, $lineno, $this->getTag()); + return new BlockReferenceNode($name, $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/src/TokenParser/DeprecatedTokenParser.php b/src/TokenParser/DeprecatedTokenParser.php index c17c4aadc..164ef26ee 100644 --- a/src/TokenParser/DeprecatedTokenParser.php +++ b/src/TokenParser/DeprecatedTokenParser.php @@ -35,7 +35,7 @@ final class DeprecatedTokenParser extends AbstractTokenParser $stream = $this->parser->getStream(); $expressionParser = $this->parser->getExpressionParser(); $expr = $expressionParser->parseExpression(); - $node = new DeprecatedNode($expr, $token->getLine(), $this->getTag()); + $node = new DeprecatedNode($expr, $token->getLine()); while ($stream->test(Token::NAME_TYPE)) { $k = $stream->getCurrent()->getValue(); diff --git a/src/TokenParser/DoTokenParser.php b/src/TokenParser/DoTokenParser.php index 6b5c30498..8afd48559 100644 --- a/src/TokenParser/DoTokenParser.php +++ b/src/TokenParser/DoTokenParser.php @@ -28,7 +28,7 @@ final class DoTokenParser extends AbstractTokenParser $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new DoNode($expr, $token->getLine(), $this->getTag()); + return new DoNode($expr, $token->getLine()); } public function getTag(): string diff --git a/src/TokenParser/EmbedTokenParser.php b/src/TokenParser/EmbedTokenParser.php index f42f07afc..7bf3233e2 100644 --- a/src/TokenParser/EmbedTokenParser.php +++ b/src/TokenParser/EmbedTokenParser.php @@ -58,7 +58,7 @@ final class EmbedTokenParser extends IncludeTokenParser $stream->expect(Token::BLOCK_END_TYPE); - return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine()); } public function decideBlockEnd(Token $token): bool diff --git a/src/TokenParser/ExtendsTokenParser.php b/src/TokenParser/ExtendsTokenParser.php index 7fba7da04..86ddfdfba 100644 --- a/src/TokenParser/ExtendsTokenParser.php +++ b/src/TokenParser/ExtendsTokenParser.php @@ -39,7 +39,7 @@ final class ExtendsTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - return new Node(); + return new Node([], [], $token->getLine()); } public function getTag(): string diff --git a/src/TokenParser/FlushTokenParser.php b/src/TokenParser/FlushTokenParser.php index 03e98abb4..0d2388745 100644 --- a/src/TokenParser/FlushTokenParser.php +++ b/src/TokenParser/FlushTokenParser.php @@ -28,7 +28,7 @@ final class FlushTokenParser extends AbstractTokenParser { $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new FlushNode($token->getLine(), $this->getTag()); + return new FlushNode($token->getLine()); } public function getTag(): string diff --git a/src/TokenParser/ForTokenParser.php b/src/TokenParser/ForTokenParser.php index f939c10b3..cf655f842 100644 --- a/src/TokenParser/ForTokenParser.php +++ b/src/TokenParser/ForTokenParser.php @@ -58,7 +58,7 @@ final class ForTokenParser extends AbstractTokenParser } $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); - return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag()); + return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno); } public function decideForFork(Token $token): bool diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php index 1619dd3d4..b54fadba8 100644 --- a/src/TokenParser/FromTokenParser.php +++ b/src/TokenParser/FromTokenParser.php @@ -50,7 +50,7 @@ final class FromTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); - $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + $node = new ImportNode($macro, $var, $token->getLine(), null, $this->parser->isMainScope()); foreach ($targets as $name => $alias) { $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); diff --git a/src/TokenParser/IfTokenParser.php b/src/TokenParser/IfTokenParser.php index acb074d95..4ea6f3df9 100644 --- a/src/TokenParser/IfTokenParser.php +++ b/src/TokenParser/IfTokenParser.php @@ -69,7 +69,7 @@ final class IfTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - return new IfNode(new Node($tests), $else, $lineno, $this->getTag()); + return new IfNode(new Node($tests), $else, $lineno); } public function decideIfFork(Token $token): bool diff --git a/src/TokenParser/ImportTokenParser.php b/src/TokenParser/ImportTokenParser.php index 595875f94..808da58cc 100644 --- a/src/TokenParser/ImportTokenParser.php +++ b/src/TokenParser/ImportTokenParser.php @@ -34,7 +34,7 @@ final class ImportTokenParser extends AbstractTokenParser $this->parser->addImportedSymbol('template', $var->getAttribute('name')); - return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + return new ImportNode($macro, $var, $token->getLine(), null, $this->parser->isMainScope()); } public function getTag(): string diff --git a/src/TokenParser/IncludeTokenParser.php b/src/TokenParser/IncludeTokenParser.php index 9c3bba042..466f2288c 100644 --- a/src/TokenParser/IncludeTokenParser.php +++ b/src/TokenParser/IncludeTokenParser.php @@ -33,7 +33,7 @@ class IncludeTokenParser extends AbstractTokenParser [$variables, $only, $ignoreMissing] = $this->parseArguments(); - return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine()); } protected function parseArguments() diff --git a/src/TokenParser/MacroTokenParser.php b/src/TokenParser/MacroTokenParser.php index 3def0e734..c7762075c 100644 --- a/src/TokenParser/MacroTokenParser.php +++ b/src/TokenParser/MacroTokenParser.php @@ -49,9 +49,9 @@ final class MacroTokenParser extends AbstractTokenParser $this->parser->popLocalScope(); $stream->expect(Token::BLOCK_END_TYPE); - $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno)); - return new Node(); + return new Node([], [], $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/src/TokenParser/SandboxTokenParser.php b/src/TokenParser/SandboxTokenParser.php index f628b29fd..70869fbc5 100644 --- a/src/TokenParser/SandboxTokenParser.php +++ b/src/TokenParser/SandboxTokenParser.php @@ -51,7 +51,7 @@ final class SandboxTokenParser extends AbstractTokenParser } } - return new SandboxNode($body, $token->getLine(), $this->getTag()); + return new SandboxNode($body, $token->getLine()); } public function decideBlockEnd(Token $token): bool diff --git a/src/TokenParser/SetTokenParser.php b/src/TokenParser/SetTokenParser.php index 71cd977c0..bb43907bd 100644 --- a/src/TokenParser/SetTokenParser.php +++ b/src/TokenParser/SetTokenParser.php @@ -58,7 +58,7 @@ final class SetTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); } - return new SetNode($capture, $names, $values, $lineno, $this->getTag()); + return new SetNode($capture, $names, $values, $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/src/TokenParser/UseTokenParser.php b/src/TokenParser/UseTokenParser.php index abb647a59..1b96b4047 100644 --- a/src/TokenParser/UseTokenParser.php +++ b/src/TokenParser/UseTokenParser.php @@ -63,7 +63,7 @@ final class UseTokenParser extends AbstractTokenParser $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); - return new Node(); + return new Node([], [], $token->getLine()); } public function getTag(): string diff --git a/src/TokenParser/WithTokenParser.php b/src/TokenParser/WithTokenParser.php index 8c89a046b..8ce4f02b2 100644 --- a/src/TokenParser/WithTokenParser.php +++ b/src/TokenParser/WithTokenParser.php @@ -41,7 +41,7 @@ final class WithTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag()); + return new WithNode($body, $variables, $only, $token->getLine()); } public function decideWithEnd(Token $token): bool diff --git a/tests/EnvironmentTest.php b/tests/EnvironmentTest.php index 6311fdfca..9338e78f2 100644 --- a/tests/EnvironmentTest.php +++ b/tests/EnvironmentTest.php @@ -533,7 +533,7 @@ class EnvironmentTest_TokenParser extends AbstractTokenParser { $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new EnvironmentTest_LegacyEchoingNode(); + return new EnvironmentTest_LegacyEchoingNode([], [], 1); } public function getTag(): string diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index 49e42dcec..3f28cca1e 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -605,9 +605,9 @@ class NotReadyFunctionExpression extends FunctionExpression class NotReadyFilterExpression extends FilterExpression { - public function __construct(Node $node, ConstantExpression $filter, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(Node $node, ConstantExpression $filter, Node $arguments, int $lineno) { - parent::__construct($node, $filter, $arguments, $lineno, $tag); + parent::__construct($node, $filter, $arguments, $lineno); } } @@ -643,9 +643,9 @@ class ReadyFunctionExpression extends FunctionExpression class ReadyFilterExpression extends FilterExpression { #[FirstClassTwigCallableReady] - public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno, ?string $tag = null) + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { - parent::__construct($node, $filter, $arguments, $lineno, $tag); + parent::__construct($node, $filter, $arguments, $lineno); } } diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index cbe617578..611eec541 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -12,6 +12,7 @@ namespace Twig\Tests\Extension; */ use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Twig\Environment; use Twig\Error\SyntaxError; use Twig\Extension\SandboxExtension; @@ -28,6 +29,8 @@ use Twig\Source; class SandboxTest extends TestCase { + use ExpectDeprecationTrait; + protected static $params; protected static $templates; @@ -60,15 +63,71 @@ class SandboxTest extends TestCase '1_syntax_error' => '{% syntax error }}', '1_childobj_parentmethod' => '{{ child_obj.ParentMethod() }}', '1_childobj_childmethod' => '{{ child_obj.ChildMethod() }}', + '1_empty' => '', ]; } + /** + * @dataProvider getSandboxedForCoreTagsTests + */ + public function testSandboxForCoreTags(string $tag, string $template) + { + $this->expectException(SecurityError::class); + $this->expectExceptionMessageMatches(sprintf('/Tag "%s" is not allowed in "index \(string template .+?\)" at line 1/', $tag)); + + $twig = $this->getEnvironment(true, [], self::$templates, []); + $twig->createTemplate($template, 'index')->render([]); + } + + public function getSandboxedForCoreTagsTests() + { + yield ['apply', '{% apply upper %}foo{% endapply %}']; + yield ['autoescape', '{% autoescape %}foo{% endautoescape %}']; + yield ['block', '{% block foo %}foo{% endblock %}']; + yield ['deprecated', '{% deprecated "message" %}']; + yield ['do', '{% do 1 + 2 %}']; + yield ['embed', '{% embed "base.twig" %}{% endembed %}']; + // To be uncommented in 4.0 + //yield ['extends', '{% extends "base.twig" %}']; + yield ['flush', '{% flush %}']; + yield ['for', '{% for i in 1..2 %}{% endfor %}']; + yield ['from', '{% from "macros" import foo %}']; + yield ['if', '{% if false %}{% endif %}']; + yield ['import', '{% import "macros" as macros %}']; + yield ['include', '{% include "macros" %}']; + yield ['macro', '{% macro foo() %}{% endmacro %}']; + yield ['sandbox', '{% sandbox %}{% endsandbox %}']; + yield ['set', '{% set foo = 1 %}']; + // To be uncommented in 4.0 + //yield ['use', '{% use "1_empty" %}']; + yield ['with', '{% with foo %}{% endwith %}']; + } + + /** + * @dataProvider getSandboxedForExtendsAndUseTagsTests + * + * @group legacy + */ + public function testSandboxForExtendsAndUseTags(string $tag, string $template) + { + $this->expectDeprecation(sprintf('Since twig/twig 3.12: The "%s" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitely in your sandbox policy if needed.', $tag)); + + $twig = $this->getEnvironment(true, [], self::$templates, []); + $twig->createTemplate($template, 'index')->render([]); + } + + public function getSandboxedForExtendsAndUseTagsTests() + { + yield ['extends', '{% extends "1_empty" %}']; + yield ['use', '{% use "1_empty" %}']; + } + public function testSandboxWithInheritance() { $this->expectException(SecurityError::class); $this->expectExceptionMessage('Filter "json_encode" is not allowed in "1_child" at line 3.'); - $twig = $this->getEnvironment(true, [], self::$templates, ['block']); + $twig = $this->getEnvironment(true, [], self::$templates, ['extends', 'block']); $twig->load('1_child')->render([]); } diff --git a/tests/Node/DeprecatedTest.php b/tests/Node/DeprecatedTest.php index de72ee701..2bc23a696 100644 --- a/tests/Node/DeprecatedTest.php +++ b/tests/Node/DeprecatedTest.php @@ -37,7 +37,7 @@ class DeprecatedTest extends NodeTestCase $tests = []; $expr = new ConstantExpression('This section is deprecated', 1); - $node = new DeprecatedNode($expr, 1, 'deprecated'); + $node = new DeprecatedNode($expr, 1); $node->setSourceContext(new Source('', 'foo.twig')); $node->setNode('package', new ConstantExpression('twig/twig', 1)); $node->setNode('version', new ConstantExpression('1.1', 1)); @@ -50,7 +50,7 @@ EOF $t = new Node([ new ConstantExpression(true, 1), - $dep = new DeprecatedNode($expr, 2, 'deprecated'), + $dep = new DeprecatedNode($expr, 2), ], [], 1); $node = new IfNode($t, null, 1); $node->setSourceContext(new Source('', 'foo.twig')); @@ -70,7 +70,7 @@ EOF $environment->addFunction($function = new TwigFunction('foo', 'Twig\Tests\Node\foo', [])); $expr = new FunctionExpression($function, new Node(), 1); - $node = new DeprecatedNode($expr, 1, 'deprecated'); + $node = new DeprecatedNode($expr, 1); $node->setSourceContext(new Source('', 'foo.twig')); $node->setNode('package', new ConstantExpression('twig/twig', 1)); $node->setNode('version', new ConstantExpression('1.1', 1)); diff --git a/tests/Node/NodeTest.php b/tests/Node/NodeTest.php index 2a5924034..4bb913ecf 100644 --- a/tests/Node/NodeTest.php +++ b/tests/Node/NodeTest.php @@ -57,7 +57,8 @@ EOF public function testToStringWithTag() { - $node = new Node([], [], 1, 'tag'); + $node = new Node([], [], 1); + $node->setNodeTag('tag'); $this->assertEquals(<<parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new Node([]); + return new Node([], [], 1); } public function getTag(): string