diff --git a/core/lib/Drupal/Core/Template/RemoveCheckToStringNodeVisitor.php b/core/lib/Drupal/Core/Template/RemoveCheckToStringNodeVisitor.php new file mode 100644 index 00000000000..568b22ace9d --- /dev/null +++ b/core/lib/Drupal/Core/Template/RemoveCheckToStringNodeVisitor.php @@ -0,0 +1,56 @@ +getNode('expr')); + // @todo https://www.drupal.org/project/drupal/issues/3488584 Update for + // Twig 4 as the spread attribute has been removed there. + if ($node->hasAttribute('spread')) { + $new->setAttribute('spread', $node->getAttribute('spread')); + } + return $new; + } + return $node; + } + + /** + * {@inheritdoc} + */ + public function leaveNode(Node $node, Environment $env): ?Node { + return $node; + } + + /** + * {@inheritdoc} + */ + public function getPriority() { + // Runs after sandbox visitor. + return 1; + } + +} diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 7ed77162080..97b2fae4f5e 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -158,10 +158,18 @@ class TwigExtension extends AbstractExtension { public function getNodeVisitors() { // The node visitor is needed to wrap all variables with // render_var -> TwigExtension->renderVar() function. - return [ + $visitors = [ new TwigNodeVisitor(), new TwigNodeVisitorCheckDeprecations(), ]; + if (\in_array('__toString', TwigSandboxPolicy::getMethodsAllowedOnAllObjects(), TRUE)) { + // When __toString is an allowed method, there is no point in running + // \Twig\Extension\SandboxExtension::ensureToStringAllowed, so we add a + // node visitor to remove any CheckToStringNode nodes added by the + // sandbox extension. + $visitors[] = new RemoveCheckToStringNodeVisitor(); + } + return $visitors; } /** diff --git a/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php b/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php index 059e91d68d8..f18c4e9da44 100644 --- a/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php +++ b/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php @@ -57,15 +57,7 @@ class TwigSandboxPolicy implements SecurityPolicyInterface { // Flip the array so we can check using isset(). $this->allowed_classes = array_flip($allowed_classes); - $allowed_methods = Settings::get('twig_sandbox_allowed_methods', [ - // Only allow idempotent methods. - 'id', - 'label', - 'bundle', - 'get', - '__toString', - 'toString', - ]); + $allowed_methods = static::getMethodsAllowedOnAllObjects(); // Flip the array so we can check using isset(). $this->allowed_methods = array_flip($allowed_methods); @@ -112,4 +104,22 @@ class TwigSandboxPolicy implements SecurityPolicyInterface { throw new SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); } + /** + * Gets the list of allowed methods on all objects. + * + * @return string[] + * The list of allowed methods on all objects. + */ + public static function getMethodsAllowedOnAllObjects(): array { + return Settings::get('twig_sandbox_allowed_methods', [ + // Only allow idempotent methods. + 'id', + 'label', + 'bundle', + 'get', + '__toString', + 'toString', + ]); + } + } diff --git a/core/lib/Drupal/Core/Template/TwigSimpleCheckToStringNode.php b/core/lib/Drupal/Core/Template/TwigSimpleCheckToStringNode.php new file mode 100644 index 00000000000..42f32a5d469 --- /dev/null +++ b/core/lib/Drupal/Core/Template/TwigSimpleCheckToStringNode.php @@ -0,0 +1,33 @@ +getNode('expr'); + $compiler + ->subcompile($expr); + } + +}