Deprecate the fact that the extends and use tags are always allowed in a sandboxed template

This commit is contained in:
Fabien Potencier
2024-08-24 10:06:44 +02:00
parent df63bead99
commit 5fca700cbd
61 changed files with 210 additions and 102 deletions
+3
View File
@@ -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()`
+3 -3
View File
@@ -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)
+7 -1
View File
@@ -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);
+18 -4
View File
@@ -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.
+2 -2
View File
@@ -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
@@ -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
+1 -1
View File
@@ -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"
+6 -2
View File
@@ -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;
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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);
@@ -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
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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);
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+17 -3
View File
@@ -42,9 +42,8 @@ class Node implements \Countable, \IteratorAggregate
* @param array<string|int, Node> $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);
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
@@ -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 (
+1
View File
@@ -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;
+7 -1
View File
@@ -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);
}
}
}
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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()
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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);
}
}
+60 -1
View File
@@ -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([]);
}
+3 -3
View File
@@ -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));
+2 -1
View File
@@ -57,7 +57,8 @@ EOF
public function testToStringWithTag()
{
$node = new Node([], [], 1, 'tag');
$node = new Node([], [], 1);
$node->setNodeTag('tag');
$this->assertEquals(<<<EOF
Twig\Node\Node
+1 -1
View File
@@ -227,7 +227,7 @@ class TestTokenParser extends AbstractTokenParser
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
return new Node([]);
return new Node([], [], 1);
}
public function getTag(): string